diff --git a/docs/API/core/app-actions-v3.md b/docs/API/core/app-actions-v3.md index 40ba0058..1b2fb7d4 100644 --- a/docs/API/core/app-actions-v3.md +++ b/docs/API/core/app-actions-v3.md @@ -45,6 +45,7 @@ Write and run JavaScript code directly on the server or client to perform automa 8. Scheduled app actions only run the **published (production)** version of an action. On-demand actions run the version from the same environment they are fired from (e.g., Fliplet Viewer runs the master version, live apps run the production version). 9. An action must have `active` set to `true` to be executed. Inactive actions do **not** run regardless of whether they are on-demand, scheduled, or triggered by events. 10. If a scheduled action fails, the error is logged and the execution is skipped. Scheduled actions do **not** retry on failure — they wait for the next cron tick. +11. Every **create**, **update**, and **restore** snapshots the action's configuration to a **version history**. Up to **100** versions are retained per action; older snapshots are pruned automatically. Snapshotting is best-effort and never blocks the create/update response. See [Version history](#version-history). ### Execution environments @@ -1079,6 +1080,204 @@ await Fliplet.App.V3.Actions.unpublish(12345); // The master version still exists and can be edited and republished ``` +## Version history + +Every time a V3 action is **created**, **updated**, or **restored**, the platform stores a snapshot of its configuration in a version history. This lets you browse how an action changed over time and roll back to an earlier configuration. + +Version history is accessed through the REST API. Call the endpoints directly, or from app code via [`Fliplet.API.request()`](https://developers.fliplet.com/API/core/api.html). All examples below use `Fliplet.API.request()`. + +The endpoints are served from your region's API host, the same hosts used elsewhere: + +- `EU` `https://api.fliplet.com` +- `US` `https://us.api.fliplet.com` +- `CA` `https://ca.api.fliplet.com` + +All three endpoints require **editor** permissions on the **master** app, and the action must be a V3 action. + +### How snapshots are created + +| When | `action` value on the snapshot | +|------|--------------------------------| +| `create()` | `create` | +| `update()` | `update` | +| `restore` (before applying the old config) | `pre-restore` | + +- Snapshots are taken **after** the create/update succeeds. If snapshotting itself fails, the error is logged to the platform's error tracking but the API still returns success — versioning never blocks a write. +- A maximum of **100** snapshots are kept per action. When a new snapshot pushes the count over 100, the **oldest** snapshots are pruned. +- Restoring an action first takes a `pre-restore` snapshot of the current configuration, so a restore is itself undo-able (restore the `pre-restore` version to get back to where you were). +- Deleting an action cascades to its version history — all snapshots for that action are removed. + +### What a snapshot contains + +Each snapshot stores the full action configuration at that point in time: + +`name`, `description`, `active`, `frequency`, `timezone`, `triggers`, `environment`, `code`, `actionVersion`, `dependencies`, `assets`, plus the internal `functions`, `widgetInstanceIds`, `masterTaskId`, and `productionTaskId` fields. For V3 actions, `functions` and `widgetInstanceIds` are empty. The snapshot also records the `action` reason (`create`, `update`, or `pre-restore`). + +### List version history + +Returns the snapshots for an action, most recent first. The list payload is a lightweight **summary** — only the fields listed in the table below are included; all other fields (`code`, `dependencies`, `assets`, `timezone`, `triggers`, `functions`, `widgetInstanceIds`, `masterTaskId`, `productionTaskId`, `actionVersion`) are omitted. Fetch a single version to get the full snapshot. + +``` +GET /v3/apps/:appId/actions/:actionId/versions +``` + +**Query parameters:** + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `limit` | Number | No | 50 | Maximum number of versions to return (max 100) | +| `offset` | Number | No | 0 | Number of versions to skip (for pagination) | + +**Summary fields returned per version:** + +| Field | Type | Description | +|-------|------|-------------| +| `id` | Number | Unique version ID. Use this with the get/restore endpoints | +| `createdAt` | String (ISO 8601) | When the snapshot was taken | +| `userId` | Number or null | ID of the user who triggered the snapshot | +| `user` | Object or null | `{ id, firstName, lastName, email }` of that user, when available | +| `action` | String | Reason for the snapshot: `create`, `update`, or `pre-restore` | +| `name` | String | Action name at the time of the snapshot | +| `description` | String or null | Description at the time of the snapshot | +| `active` | Boolean | Whether the action was active | +| `environment` | String | `server`, `client`, or `any` | +| `frequency` | String or null | Cron expression, if scheduled | + +```js +var response = await Fliplet.API.request({ + url: 'v3/apps/' + appId + '/actions/' + actionId + '/versions', + method: 'GET' +}); +// response.status — "VERSIONS_LISTED" +// response.versions — array of version summaries (most recent first) +// response.pagination — { limit, offset, total, hasMore } + +response.versions.forEach(function (version) { + console.log(version.id, version.action, version.createdAt); +}); +``` + +Example response: + +```json +{ + "status": "VERSIONS_LISTED", + "versions": [ + { + "id": 5012, + "createdAt": "2026-05-21T13:07:15.000Z", + "userId": 409996, + "user": { "id": 409996, "firstName": "Nick", "lastName": "Smith", "email": "nick@company.com" }, + "action": "update", + "name": "confirm-booking", + "description": "Marks a booking as confirmed and notifies the customer", + "active": true, + "environment": "server", + "frequency": null + } + ], + "pagination": { "limit": 50, "offset": 0, "total": 12, "hasMore": false } +} +``` + +### Get a single version + +Returns the **full** snapshot for one version, including `code`, `dependencies`, and `assets`. + +``` +GET /v3/apps/:appId/actions/:actionId/versions/:versionId +``` + +```js +var response = await Fliplet.API.request({ + url: 'v3/apps/' + appId + '/actions/' + actionId + '/versions/' + versionId, + method: 'GET' +}); +// response.status — "VERSION_RETRIEVED" +// response.version.data — the full configuration snapshot +``` + +Example response: + +```json +{ + "status": "VERSION_RETRIEVED", + "version": { + "id": 5012, + "createdAt": "2026-05-21T13:07:15.000Z", + "userId": 409996, + "user": { "id": 409996, "firstName": "Nick", "lastName": "Smith", "email": "nick@company.com" }, + "data": { + "action": "update", + "name": "confirm-booking", + "description": "Marks a booking as confirmed and notifies the customer", + "active": true, + "frequency": null, + "timezone": null, + "functions": [], + "triggers": [{ "trigger": "manual" }], + "assets": [ + { + "name": "fliplet-datasources", + "url": "https://cdn.fliplet.com/assets/fliplet-datasources/1.0/datasources.js", + "path": "assets/fliplet-datasources/1.0/datasources.js" + } + ], + "environment": "server", + "widgetInstanceIds": [], + "masterTaskId": null, + "productionTaskId": null, + "code": "async function execute(context) { return { success: true }; }", + "actionVersion": "v3", + "dependencies": ["fliplet-datasources"] + } + } +} +``` + +
A versionId that does not exist for this action returns a 404 with status VERSION_NOT_FOUND.
You can not restore a published (production) action. Restoring returns a 403 with status CANNOT_RESTORE_PRODUCTION — restore the master action and republish instead.
If the snapshot's name now collides with a different action in the same app, the restore returns a 409 with status NAME_ALREADY_EXISTS. Rename or remove the conflicting action first.
Rate limits are per app, not per action. Exceeding the limit results in a 429 HTTP status code.