You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit df8bbf9
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: content/docs/api/declarative-endpoints.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -101,7 +101,7 @@ declaration to shadow a built-in one. Match → policy chain (`rateLimit` → `a
101
101
|`type`| Delegates to | Request shape |
102
102
|:---|:---|:---|
103
103
|`object_operation`| the same `callData` binding that serves `/api/v1/data/{object}`|`find` reads its criteria from the query string; `get` / `update` / `delete` take the record id from `query.id`; `create` / `update` take the body. `create` answers `201`, the rest `200`|
104
-
|`flow`| the same automation pipeline as `POST /api/v1/automation/{name}/trigger`| the request body is the flow input. A refused or failed run answers a real status: `404` unknown flow, `409``FLOW_DISABLED`, `422``FLOW_NO_START_NODE`, `422``FLOW_INPUT_SCHEMA_INVALID`, `400``FLOW_FAILED`, and `403``PERMISSION_DENIED`, nothing run, when the target is declared `runAs: 'system'` with a self-triggered type (`autolaunched`, `record_change`, `schedule`), exactly as the trigger door answers |
104
+
|`flow`| the same automation pipeline as `POST /api/v1/automation/{name}/trigger`| the request body is the flow input. A refused or failed run answers a real status: `404` unknown flow, `409``FLOW_DISABLED`, `422``FLOW_NO_START_NODE`, `422``FLOW_INPUT_SCHEMA_INVALID`, `400``FLOW_FAILED`, and `403``PERMISSION_DENIED`, nothing run, when the target is declared `runAs: 'system'` with a self-triggered type (`autolaunched`, `record_change`, `schedule`) or declares `requiredPermissions` the caller does not hold, exactly as the trigger door answers |
105
105
106
106
The frozen vocabulary has **no path-template syntax**, so an endpoint cannot express
107
107
`/leads/{id}` — a record id travels as `?id=…`. A method that no declaration claims
Copy file name to clipboardExpand all lines: content/docs/automation/flows.mdx
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -74,6 +74,7 @@ const approvalFlow = {
74
74
|`status`|`enum`| optional |`'draft'`, `'active'`, `'obsolete'`, `'invalid'` (defaults to `'draft'`) |
75
75
|`type`|`FlowType`| ✅ | Flow trigger type (see below) |
76
76
|`runAs`|`enum`| optional |`'system'` (elevated, bypasses RLS; after an approval decision its attribution user is the decider) or `'user'` (the default, RLS-respecting: the trigger user until a human decision, the decider after it, with the decider's permissions). A `'user'` run that resolved **no** trigger user has nothing to scope to, so its data operations are refused — declare `'system'` for schedule / time-relative / API triggers and for record-change flows fired by a write that carried no user |
77
+
|`requiredPermissions`|`string[]`| optional | Capabilities a caller must hold, every one listed, to START the flow. Checked at every door that starts a flow by name (the trigger route, a `type: 'flow'` action over REST and MCP `run_action`, a declared `type: 'flow'` endpoint): anyone else but the system principal is refused `403``PERMISSION_DENIED` and nothing runs. An action that starts the flow inherits the list. A parent flow's `subflow` node is not a door and is not checked again. This is how to say who may start an elevated `screen` or `api` flow; `current_user.positions` is not an authorization input |
|`edges`|`FlowEdge[]`| ✅ | Connections between nodes |
@@ -2291,20 +2292,21 @@ curl -b cookies.txt -X POST \
2291
2292
|`POST /api/v1/automation/:name/runs/:runId/restore-suspension`| Re-arm a run stranded by a failed resume — and, for a nested run, every ancestor whose pause that strand's cascade consumed, deepest first, from whichever member you name. Same gate as `cancel`; body `{ reason? }`. `501 NOT_IMPLEMENTED` when the service does not declare the verb |
2292
2293
2293
2294
**Failures answer real HTTP status codes.** A flow that does not run is not a
2294
-
`200`. The route classifies six outcomes. Five of them it reads off the engine's
2295
-
own verdict — never the message text; the `403` is the door's own admission
2295
+
`200`. The route classifies seven outcomes. Five of them it reads off the engine's
2296
+
own verdict — never the message text; the two `403`s are the door's own admission
2296
2297
check, made before anything is dispatched:
2297
2298
2298
2299
| Status |`error.code`| What happened | What to do |
2299
2300
|:---|:---|:---|:---|
2300
2301
|`404`| — | No flow by that name in this deployment | Check the name; nothing was dispatched |
2301
2302
|`403`|`PERMISSION_DENIED`| The caller is not the system principal, and the flow is declared `runAs: 'system'` with a self-triggered `type` (`autolaunched`, `record_change`, `schedule`). The same answer at every door that starts a flow by name: this route, a `type: 'flow'` action (REST and MCP `run_action`) and a declared `type: 'flow'` endpoint | Let it run on its own trigger, or call it from a parent flow's `subflow` node. If members are meant to start it, declare it `type: 'screen'` (or `'api'` for a signed inbound hook) |
2303
+
|`403`|`PERMISSION_DENIED`| The flow declares `requiredPermissions` and the caller, not the system principal, does not hold every capability it names. Checked after the row above, at the same doors; the message names the declared and the missing capabilities | Grant the capability through a permission set's `systemPermissions`, or start the flow as someone who holds it |
2302
2304
|`409`|`FLOW_DISABLED`| The flow exists but is switched off | Enable it — the identical request then succeeds |
2303
2305
|`422`|`FLOW_NO_START_NODE`| The stored definition has no `start` node | Fix the flow; retrying cannot help |
2304
2306
|`422`|`FLOW_INPUT_SCHEMA_INVALID`| A node's `config` violates the `inputSchema` its own definition declares | Fix the flow; the refusal is non-retryable — even under `errorHandling.strategy: 'retry'` the engine refuses once and never enters the retry loop |
2305
2307
|`400`|`FLOW_FAILED`| The flow **ran** and was rejected | Read `error.details.summary` for the failing node |
2306
2308
2307
-
The first five never dispatched anything: no node executed, no record was
2309
+
The first six never dispatched anything: no node executed, no record was
2308
2310
written, and there is no run to look up. Only `400` describes a run. The
2309
2311
`FLOW_INPUT_SCHEMA_INVALID` verdict is a pure function of the flow definition —
2310
2312
re-running the guard cannot change the answer — so a `retry` strategy does not
Copy file name to clipboardExpand all lines: content/docs/ui/actions.mdx
+8-4Lines changed: 8 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -407,9 +407,13 @@ is a spec proposal for a properly named key, not a values map under this one.
407
407
-**`requiredPermissions: ['can_close_tickets']`** is a **dual-surface gate**
408
408
(one declaration, two enforcement points): the server rejects unauthorized
409
409
calls with 403, and the UI hides or disables the button for the same users.
410
-
Unset means no gate beyond object CRUD permissions — except that a `flow`
411
-
action whose target is an elevated self-triggered flow is refused to every
412
-
caller but the system principal either way (the `flow` row below). Referenced
410
+
Unset means no gate beyond object CRUD permissions — except at a `flow`
411
+
action, whose target flow is admitted by its own rules either way (the
412
+
`flow` row below): an elevated self-triggered target is refused to every
413
+
caller but the system principal, and a target that declares its own
414
+
`requiredPermissions` is refused to a caller who does not hold them. The
415
+
action inherits the flow's list; when both declare one, the caller needs
416
+
both. Referenced
413
417
capabilities must exist — `os lint` checks that.
414
418
-**`visible`** is a CEL predicate evaluated **fail-closed**: an expression
415
419
that throws hides the action silently. Two rules save real debugging time:
@@ -546,7 +550,7 @@ The endpoint dispatches on the **declared `type`**, exactly like the MCP
546
550
|`type`| Over REST|
547
551
|:---|:---|
548
552
|`script`| Runs the registered handler / inline body. |
549
-
|`flow`| Runs `target` on the automation engine, with your identity forwarded (a `runAs: 'user'` flow enforces RLS as you). Dispatches the same flow as `POST /api/v1/automation/:target/trigger`, without having to know the flow name — **and answers the same way**: a run that ran and was rejected is **400**`FLOW_FAILED`, while a dispatch that never happened is separated out (**404** unknown flow /**409**`FLOW_DISABLED`/**422**`FLOW_NO_START_NODE`/**422**`FLOW_INPUT_SCHEMA_INVALID`). A target declared `runAs: 'system'` whose type is self-triggered (`autolaunched`, `record_change`, `schedule`) is refused **403**`PERMISSION_DENIED` to every caller but the system principal, exactly as at the trigger door, and nothing runs; holding the action's `requiredPermissions` does not open it. See [Run a flow via API](/docs/automation/flows#run-a-flow-via-api) for the full table — it is one table, read by both doors. |
553
+
| `flow` | Runs `target` on the automation engine, with your identity forwarded (a `runAs: 'user'` flow enforces RLS as you). Dispatches the same flow as `POST /api/v1/automation/:target/trigger`, without having to know the flow name — **and answers the same way**: a run that ran and was rejected is **400** `FLOW_FAILED`, while a dispatch that never happened is separated out (**404** unknown flow / **409** `FLOW_DISABLED` / **422** `FLOW_NO_START_NODE` / **422** `FLOW_INPUT_SCHEMA_INVALID`). A target declared `runAs: 'system'` whose type is self-triggered (`autolaunched`, `record_change`, `schedule`) is refused **403** `PERMISSION_DENIED` to every caller but the system principal, exactly as at the trigger door, and nothing runs; holding the action's `requiredPermissions` does not open it. A target that declares its own `requiredPermissions` is refused **403** `PERMISSION_DENIED` to a caller who does not hold every capability it names, even when the action declares none. See [Run a flow via API](/docs/automation/flows#run-a-flow-via-api) for the full table — it is one table, read by both doors. |
550
554
|`api`|**400** — it dispatches on `target`; call that endpoint directly. |
551
555
|`url`/`modal`/`form`|**400** — client-side navigation; there is nothing for the server to run. |
0 commit comments