ObjectStackObjectStack

Automation Api schema — API Protocol reference

Defines REST CRUD endpoint schemas for managing automation flows, triggering executions, and querying execution history.

Automation API Protocol

Defines REST CRUD endpoint schemas for managing automation flows, triggering executions, and querying execution history.

Base path: /api/v1/automation

The wire paths the platform serves: the dispatcher mounts this door at its prefix (default /api/v1, the one objectstack serve uses) plus /automation. A drift pin in @objectstack/runtime (automation-api-contract-mounts.test.ts) holds every path in AutomationApiContracts to that mount table.

The flow LIST is not on this door. Flows are metadata (ADR-0106), and the governed read of them is GET /api/v1/meta/flow (client.meta.getItems); the former GET /api/v1/automation list route, its request/response schemas and client.automation.list are retired (ADR-0087 semantic entry automation-flow-list-route-retired).

The toggle door switches PACKAGED flows only: a flow a code package ships. It records the installation's choice in the packaged-metadata activation ledger (ADR-0126 §7.2). A flow authored in the deployment is not switched there. Its switch is its own status: 'obsolete' disarms it and 'active' arms it, published with the complete definition through PUT /api/v1/automation/:name. The toggle door refuses such a flow with 409 RESOURCE_CONFLICT, names that switch, and changes nothing.

Endpoints

GET    /api/v1/automation/:name                — Get flow
POST   /api/v1/automation                      — Create flow
PUT    /api/v1/automation/:name                — Update flow
DELETE /api/v1/automation/:name                — Delete flow
POST   /api/v1/automation/:name/trigger        — Trigger flow execution
POST   /api/v1/automation/:name/toggle         — Enable/disable a packaged flow
GET    /api/v1/automation/:name/runs           — List execution runs
GET    /api/v1/automation/:name/runs/:runId    — Get single execution run

Source: packages/spec/src/api/automation-api.zod.ts

TypeScript Usage

import { AutomationApiErrorCode, AutomationFlowPathParamsSchema, AutomationRunPathParamsSchema, CreateFlowRequestSchema, CreateFlowResponseSchema, DeleteFlowRequestSchema, DeleteFlowResponseSchema, GetFlowRequestSchema, GetFlowResponseSchema, GetRunRequestSchema, GetRunResponseSchema, ListRunsRequestSchema, ListRunsResponseSchema, ResumeFailureDetailsSchema, ToggleFlowRequestSchema, ToggleFlowResponseSchema, TriggerFlowRequestSchema, TriggerFlowResponseSchema, UpdateFlowRequestSchema, UpdateFlowResponseSchema } from '@objectstack/spec/api';
import type { AutomationApiErrorCode, AutomationFlowPathParams, AutomationRunPathParams, CreateFlowRequest, CreateFlowResponse, DeleteFlowRequest, DeleteFlowResponse, GetFlowRequest, GetFlowResponse, GetRunRequest, GetRunResponse, ListRunsRequest, ListRunsResponse, ResumeFailureDetails, ToggleFlowRequest, ToggleFlowResponse, TriggerFlowRequest, TriggerFlowResponse, UpdateFlowRequest, UpdateFlowResponse } from '@objectstack/spec/api';

// Validate data
const result = AutomationApiErrorCode.parse(data);

AutomationApiErrorCode

Allowed Values

  • flow_not_found
  • flow_already_exists
  • flow_validation_failed
  • flow_disabled
  • execution_not_found
  • execution_failed
  • execution_timeout
  • node_executor_not_found
  • concurrent_execution_limit

AutomationFlowPathParams

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)

AutomationRunPathParams

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)
runIdstring✅Execution run ID

CreateFlowRequest

Properties

PropertyTypeRequiredDescription
namestring✅Machine name
labelstring✅Flow label
descriptionstringoptional
successMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of a generic "Done".
errorMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of the raw error.
versionintegeroptional (default: 1)Version number
statusEnum<'draft' | 'active' | 'obsolete' | 'invalid'>optional (default: "draft")Deployment status
templateneveroptional[REMOVED] flow.template was removed in @objectstack/spec 17.0.0 (audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
typeEnum<'autolaunched' | 'record_change' | 'schedule' | 'screen' | 'api'>✅Flow type
variables{ name: string; type: string; isInput?: boolean; isOutput?: boolean; … }[]optionalFlow variables
nodes{ id: string; type: string; label: string; config?: Record<string, any>; … }[]✅Flow nodes
edges{ id: string; source: string; target: string; condition?: string | object; … }[]✅Flow connections
activeneveroptional[REMOVED] flow.active was removed in @objectstack/spec 17.0.0 (audit close-out) — it never had an effect: the engine arms flows from status, and active: false did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use status: 'obsolete' (or 'invalid') to unbind and disable a flow, status: 'active' to arm it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
runAsEnum<'system' | 'user'>optional (default: "user")Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user.
errorHandling{ strategy?: Enum<'fail' | 'retry' | 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … }optionalFlow-level error handling configuration. A durable pause ends the retry-governed segment: strategy: 'retry' describes one synchronous dispatch, so a run that parks on an approval/screen/wait node and later resumes gets one attempt for anything that fails after the pause. Protect the post-pause half with its own failure handling in the flow — a try_catch node's retry around the post-resume work, or fault edges to a handler node.
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this flow.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

Nested Shape: CreateFlowRequest.variables[number]

PropertyTypeRequiredDescription
namestring✅Variable name
typestring✅Data type (text, number, boolean, object, list)
isInputbooleanoptional (default: false)Is input parameter
isOutputbooleanoptional (default: false)Is output parameter
defaultValueanyoptionalValue bound at run start when no parameter supplies one — this is what makes a declared variable always bound. An explicitly supplied param wins, including false and null; the boundary is params[name] !== undefined.

Nested Shape: CreateFlowRequest.nodes[number]

PropertyTypeRequiredDescription
idstring✅Node unique ID
typestring✅Action type — a built-in FlowNodeAction id or a plugin-registered node type. Validated against the live action registry at registerFlow() (ADR-0018), not by a closed enum.
labelstring✅Node label
configRecord<string, any>optionalNode configuration
connectorConfig{ connectorId: string; actionId: string; input?: Record<string, any> }optional
position{ x: number; y: number }optional
timeoutMsintegeroptionalMaximum execution time for this node in milliseconds
inputSchemaRecord<string, { type: Enum<'string' | 'number' | 'boolean' | 'object' | 'array'>; required?: boolean; description?: string }>optionalInput parameter schema for this node
outputSchemaneveroptional[REMOVED] flow.nodes[].outputSchema was removed in @objectstack/spec 17.0.0 (audit close-out) — it was never validated: the engine does not check node outputs against it, so it documented a contract nothing enforced. Delete the key. Downstream nodes read prior outputs via expressions ({{nodeId.field}}) regardless of any declaration. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
waitEventConfig{ eventType: Enum<'timer' | 'signal' | 'webhook' | 'manual' | 'condition'>; timerDuration?: string; signalName?: string }optionalConfiguration for wait node event resumption. REQUIRED on a type: 'wait' node — the block is the whole contract, and a wait node without one parses into a run that parks forever reporting success. Under eventType: 'timer' a timerDuration is required too.
boundaryConfig{ attachedToNodeId: string; eventType: Enum<'error' | 'timer' | 'signal' | 'cancel'>; interrupting?: boolean; errorCode?: string; … }optionalConfiguration for boundary events attached to host nodes. REQUIRED on a type: 'boundary_event' node — without it the node names neither the activity it watches nor what fires it.

Nested Shape: CreateFlowRequest.edges[number]

PropertyTypeRequiredDescription
idstring✅Edge unique ID
sourcestring✅Source Node ID
targetstring✅Target Node ID
conditionstring | { dialect: Enum<'cel' | 'cron' | 'template'>; source: string; ast?: any; meta?: object }optionalPredicate (CEL) returning boolean used for branching. An evaluated slot: a bare non-blank CEL string, or an envelope carrying a non-blank source — an ast-only envelope, and a source that is blank after trimming, are refused at authoring because the engine evaluates source alone and would otherwise answer a silent false.
typeEnum<'default' | 'fault' | 'conditional' | 'back'>optional (default: "default")Connection type: default (normal flow), fault (error path), conditional (expression-guarded), or back (ADR-0044 declared back-edge — traversed normally at run time, but excluded from DAG cycle validation so a revise/rework loop can re-enter an earlier node)
labelstringoptionalLabel on the connector
isDefaultbooleanoptional (default: false)BPMN default flow: traverse this edge only when no sibling conditional edge of the same source node matched. Mutually exclusive with condition; at most one per source node.

Nested Shape: CreateFlowRequest.errorHandling

PropertyTypeRequiredDescription
strategyEnum<'fail' | 'retry' | 'continue'>optional (default: "fail")How to handle node execution errors. 'retry' governs ONE synchronous dispatch: a durable pause (approval/screen/wait) ends the retry-governed segment, so a failure after the run resumes is not retried.
maxRetriesintegeroptional (default: 0)Retry attempts after the initial one. Read only under strategy: 'retry', which requires >= 1; 0 (the default) means no retry.
backoffMsintegeroptional (default: 1000)Base delay before the first retry (ms); subsequent delays multiply by backoffMultiplier
backoffMultipliernumberoptional (default: 1)Exponential backoff multiplier; 1 (the default) keeps the delay flat
maxRetryDelayMsintegeroptional (default: 30000)Ceiling for a single backoff delay (ms)
jitterbooleanoptional (default: false)Randomize each delay within [50%, 100%] of its computed value — spreads a thundering herd of simultaneous retries
retryDelayMsneveroptional[REMOVED] retryDelayMs was removed in @objectstack/spec 17.0.0 — the retry policy now has ONE spelling for its base delay across every surface that carries it: job.retryPolicy, a try_catch node's retry and flow.errorHandling. Rename the key to backoffMs; the value (milliseconds before the first retry) is unchanged. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
fallbackNodeIdneveroptional[REMOVED] flow.errorHandling.fallbackNodeId was removed in @objectstack/spec 17.0.0 (audit close-out) — the engine routes unrecoverable node errors via per-node fault edges (an edge with type: 'fault'), and never read this key: a fallback configured here silently did not exist. Delete the key and draw a fault edge from the failing node to the handler node instead. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.

Nested Shape: CreateFlowRequest.protection

PropertyTypeRequiredDescription
lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>✅Lock policy — none | no-overlay | no-delete | full.
reasonstring✅User-visible reason shown when the lock blocks an action.
docsUrlstringoptionalOptional URL the Studio banner links to for more context.

CreateFlowResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ name: string; label: string; description?: string; successMessage?: string; … }✅The created flow, canonicalized — the parsed shape the engine stored, identical to what a subsequent GET answers

Nested Shape: CreateFlowResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: CreateFlowResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: CreateFlowResponse.data

PropertyTypeRequiredDescription
namestring✅Machine name
labelstring✅Flow label
descriptionstringoptional
successMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of a generic "Done".
errorMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of the raw error.
versionintegeroptional (default: 1)Version number
statusEnum<'draft' | 'active' | 'obsolete' | 'invalid'>optional (default: "draft")Deployment status
templateneveroptional[REMOVED] flow.template was removed in @objectstack/spec 17.0.0 (audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
typeEnum<'autolaunched' | 'record_change' | 'schedule' | 'screen' | 'api'>✅Flow type
variables{ name: string; type: string; isInput?: boolean; isOutput?: boolean; … }[]optionalFlow variables
nodes{ id: string; type: string; label: string; config?: Record<string, any>; … }[]✅Flow nodes
edges{ id: string; source: string; target: string; condition?: string | object; … }[]✅Flow connections
activeneveroptional[REMOVED] flow.active was removed in @objectstack/spec 17.0.0 (audit close-out) — it never had an effect: the engine arms flows from status, and active: false did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use status: 'obsolete' (or 'invalid') to unbind and disable a flow, status: 'active' to arm it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
runAsEnum<'system' | 'user'>optional (default: "user")Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user.
errorHandling{ strategy?: Enum<'fail' | 'retry' | 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … }optionalFlow-level error handling configuration. A durable pause ends the retry-governed segment: strategy: 'retry' describes one synchronous dispatch, so a run that parks on an approval/screen/wait node and later resumes gets one attempt for anything that fails after the pause. Protect the post-pause half with its own failure handling in the flow — a try_catch node's retry around the post-resume work, or fault edges to a handler node.
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this flow.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

DeleteFlowRequest

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)

DeleteFlowResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ name: string; deleted: boolean }✅

Nested Shape: DeleteFlowResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: DeleteFlowResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: DeleteFlowResponse.data

PropertyTypeRequiredDescription
namestring✅Name of the deleted flow
deletedboolean✅Whether the flow was deleted

GetFlowRequest

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)

GetFlowResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ name: string; label: string; description?: string; successMessage?: string; … }✅Full flow definition

Nested Shape: GetFlowResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: GetFlowResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: GetFlowResponse.data

PropertyTypeRequiredDescription
namestring✅Machine name
labelstring✅Flow label
descriptionstringoptional
successMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of a generic "Done".
errorMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of the raw error.
versionintegeroptional (default: 1)Version number
statusEnum<'draft' | 'active' | 'obsolete' | 'invalid'>optional (default: "draft")Deployment status
templateneveroptional[REMOVED] flow.template was removed in @objectstack/spec 17.0.0 (audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
typeEnum<'autolaunched' | 'record_change' | 'schedule' | 'screen' | 'api'>✅Flow type
variables{ name: string; type: string; isInput?: boolean; isOutput?: boolean; … }[]optionalFlow variables
nodes{ id: string; type: string; label: string; config?: Record<string, any>; … }[]✅Flow nodes
edges{ id: string; source: string; target: string; condition?: string | object; … }[]✅Flow connections
activeneveroptional[REMOVED] flow.active was removed in @objectstack/spec 17.0.0 (audit close-out) — it never had an effect: the engine arms flows from status, and active: false did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use status: 'obsolete' (or 'invalid') to unbind and disable a flow, status: 'active' to arm it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
runAsEnum<'system' | 'user'>optional (default: "user")Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user.
errorHandling{ strategy?: Enum<'fail' | 'retry' | 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … }optionalFlow-level error handling configuration. A durable pause ends the retry-governed segment: strategy: 'retry' describes one synchronous dispatch, so a run that parks on an approval/screen/wait node and later resumes gets one attempt for anything that fails after the pause. Protect the post-pause half with its own failure handling in the flow — a try_catch node's retry around the post-resume work, or fault edges to a handler node.
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this flow.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

GetRunRequest

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)
runIdstring✅Execution run ID

GetRunResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ id: string; flowName: string; flowVersion?: integer; status: Enum<'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | …>; … }✅Full execution log with step details

Nested Shape: GetRunResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: GetRunResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: GetRunResponse.data

PropertyTypeRequiredDescription
idstring✅Execution instance ID
flowNamestring✅Machine name of the executed flow
flowVersionintegeroptionalVersion of the flow that was executed
statusEnum<'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | …>✅Current execution status
refusalMessagestringoptionalRendered end node message when status is refused — the per-record text the flow refused with. Absent on every other status.
trigger{ type: string; recordId?: string; object?: string; userId?: string; … }✅What triggered this execution
steps{ nodeId: string; nodeType: string; nodeLabel?: string; status: Enum<'success' | 'failure' | 'skipped'>; … }[]✅Ordered list of executed steps
summary{ selected: integer; acted: integer; skipped: integer; unmeasured?: integer; … }optionalPer-run rollup: records selected / acted on, gate skips, per-node status
variablesRecord<string, any>optionalFinal state of flow variables
startedAtstring✅Execution start timestamp
completedAtstringoptionalExecution completion timestamp
durationMsintegeroptionalTotal execution duration in milliseconds
runAsEnum<'system' | 'user'>optionalExecution context identity
tenantIdstringoptionalTenant ID for multi-tenant isolation

ListRunsRequest

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)
statusEnum<'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | 'timed_out' | 'retrying' | 'refused'>optionalFilter by execution status
limitintegeroptional (default: 20)Maximum number of runs to return
cursorneveroptional[REMOVED] cursor was removed from GET /api/v1/automation/:name/runs in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it was VALIDATED at the boundary and then read by nothing: the option reached the service and the engine never looked at it, no emit site has ever written the response half nextCursor, and the only ordering this door has is a required but non-unique startedAt timestamp that nothing ever minted a resume point from — so a caller looping "until the cursor runs out" re-read the first and only window forever, with no error. Delete the key. limit is the real window and STAYS: it is read end to end (boundary to service to store) and bounded to 1..100, so ask for a wider window instead of a next page. Read the response hasMore to learn whether the window was short — it is now COMPUTED from the engine rather than the constant false it used to be.

ListRunsResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ runs: object[]; total?: integer; nextCursor?: string; hasMore: boolean }✅

Nested Shape: ListRunsResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: ListRunsResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: ListRunsResponse.data

PropertyTypeRequiredDescription
runs{ id: string; flowName: string; flowVersion?: integer; status: Enum<'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | …>; … }[]✅Execution run logs
totalintegeroptionalTotal matching runs
nextCursorstringoptionalCursor for the next page
hasMoreboolean✅Whether more runs matched than this response carries — widen limit to see them. Under status, false means no further match within the scanned window rather than none at all: the window is taken before the filter is applied.

ResumeFailureDetails

Properties

PropertyTypeRequiredDescription
runIdstring✅The run the resume was addressed to - the run that failed, and on the stranded arm the run an operator verb can re-arm. Named so a caller acts on an identifier instead of parsing one out of the message
statusEnum<'failed' | 'stranded'>optionalThe engine's own lifecycle verdict for the run, forwarded verbatim when the producer stamped one and never synthesised by the door - absent when the engine reported no status (a subflow child that failed terminally, an engine that predates the discriminator). stranded is the terminally-failed-but-repairable run of AutomationResult.status; failed says the run ran and was rejected. These two terminal-failure members of that union are the only ones that can reach a 400
repairableboolean✅Whether the engine says this run can still be re-armed by an operator verb. Answered from the engine, two ways, and never from the message text: where the engine stamped a status, that word decides (stranded is the run whose OWN pause a resume consumed before a downstream node threw); where it stamped none, the door asks the engine's read-only inspection (IAutomationService.inspectConsumedSuspension) and relays its verdict. The second half is not a fallback but the honest answer for a real exit: a subflow DELEGATION failure carries no status, because nothing re-arms an ancestor by resuming it, and yet the ancestor's consumed pause is journalled and the restore verb re-arms that chain as one unit. Always present on this arm: an absent member would be indistinguishable from a server that predates this field. Every way of not getting an answer is fail-closed - a service that declares no inspection member, and a store the inspection could not read - because promising a repair verb that will refuse is worse than promising nothing

ToggleFlowRequest

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)
enabledboolean✅Whether to enable (true) or disable (false) the flow

ToggleFlowResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ name: string; enabled: boolean }✅

Nested Shape: ToggleFlowResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: ToggleFlowResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: ToggleFlowResponse.data

PropertyTypeRequiredDescription
namestring✅Flow name
enabledboolean✅New enabled state

TriggerFlowRequest

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)
recordRecord<string, any>optionalRecord that triggered the automation
objectstringoptionalObject name the record belongs to
eventstringoptionalTrigger event type
userIdstringoptionalUser who triggered the automation
paramsRecord<string, any>optionalAdditional contextual data

TriggerFlowResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ success: boolean; output?: any; error?: string; durationMs?: number; … }✅

Nested Shape: TriggerFlowResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: TriggerFlowResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: TriggerFlowResponse.data

PropertyTypeRequiredDescription
successboolean✅Whether the automation completed successfully
outputanyoptionalOutput data from the automation
errorstringoptionalError message if execution failed
durationMsnumberoptionalExecution duration in milliseconds
codeEnum<'PERMISSION_DENIED' | 'INVALID_SIGNAL' | 'RUN_NOT_FOUND' | 'STORE_UNAVAILABLE' | …>optionalMachine-readable failure classification, set alongside error when the caller must distinguish WHY it failed. A closed union - the members and their transport mappings are documented on the contract (AutomationResult.code, contracts/automation-service.ts).
statusEnum<'completed' | 'paused' | 'failed' | 'stranded' | 'refused'>optionalLifecycle status. paused means the run suspended at a node and can be continued with the resume route. Absent or completed/failed/stranded/refused means the run reached a terminal state. refused is a first-class refusal: the flow reached an end node declaring outcome: 'refused' — a successful evaluation that said no, so success is true, successMessage is absent and the per-record reason is on refusalMessage; a runner shows it with Close only. stranded is the terminally-failed-but-repairable run: a resume consumed the suspension and a downstream node threw, so the run is recorded as failed and can be re-armed only by an explicit operator verb - never by the resume route, which answers RUN_NOT_FOUND for it.
runIdstringoptionalRun id - set when status is paused, so callers can resume it
screen{ nodeId: string; title?: string; description?: string; fields: object[]; … }optionalThe screen to render - set when the run paused at a screen node awaiting user input. The client collects values for screen.fields and resumes the run with them.
successMessagestringoptionalFriendly terminal message copied from the flow definition on terminal success, so a screen-flow runner can show a meaningful toast
errorMessagestringoptionalFriendly terminal message copied from the flow definition on failure
flowLabelstringoptionalThe flow definition's authored label, copied verbatim so a runner can name the flow (header, completion toast) and translate it against flows.<flow>.label. Set on every result of an evaluation of a registered flow (paused and terminal alike); absent on a refusal carrying code. For a subflow chain it is the addressed (parent) run's flow. Never defaulted to the API name
refusalMessagestringoptionalRendered refusal, set when status is refused - the end node's message template interpolated against the run's variables, so it names the record. Authored per-record text (not a flow-level copy like the two above); absent on every other status. A runner shows it with Close only
summary{ selected: integer; acted: integer; skipped: integer; unmeasured?: integer; … }optionalWhat the run did - records selected / acted on, gate skips, per-node status. Set on a TERMINAL result (a paused run has not finished doing it yet).

UpdateFlowRequest

Properties

PropertyTypeRequiredDescription
namestring✅Flow machine name (snake_case)
definition{ name: string; label: string; description?: string; successMessage?: string; … }✅Complete flow definition to store — the engine requires a full flow; partial update is not implemented

Nested Shape: UpdateFlowRequest.definition

PropertyTypeRequiredDescription
namestring✅Machine name
labelstring✅Flow label
descriptionstringoptional
successMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of a generic "Done".
errorMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of the raw error.
versionintegeroptional (default: 1)Version number
statusEnum<'draft' | 'active' | 'obsolete' | 'invalid'>optional (default: "draft")Deployment status
templateneveroptional[REMOVED] flow.template was removed in @objectstack/spec 17.0.0 (audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
typeEnum<'autolaunched' | 'record_change' | 'schedule' | 'screen' | 'api'>✅Flow type
variables{ name: string; type: string; isInput?: boolean; isOutput?: boolean; … }[]optionalFlow variables
nodes{ id: string; type: string; label: string; config?: Record<string, any>; … }[]✅Flow nodes
edges{ id: string; source: string; target: string; condition?: string | object; … }[]✅Flow connections
activeneveroptional[REMOVED] flow.active was removed in @objectstack/spec 17.0.0 (audit close-out) — it never had an effect: the engine arms flows from status, and active: false did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use status: 'obsolete' (or 'invalid') to unbind and disable a flow, status: 'active' to arm it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
runAsEnum<'system' | 'user'>optional (default: "user")Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user.
errorHandling{ strategy?: Enum<'fail' | 'retry' | 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … }optionalFlow-level error handling configuration. A durable pause ends the retry-governed segment: strategy: 'retry' describes one synchronous dispatch, so a run that parks on an approval/screen/wait node and later resumes gets one attempt for anything that fails after the pause. Protect the post-pause half with its own failure handling in the flow — a try_catch node's retry around the post-resume work, or fault edges to a handler node.
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this flow.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

UpdateFlowResponse

Properties

PropertyTypeRequiredDescription
successboolean✅Operation success status
error{ code: Enum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>; declaredCode?: string; message: string; userMessage?: string; … }optionalError details if success is false
meta{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }optionalResponse metadata
data{ name: string; label: string; description?: string; successMessage?: string; … }✅The updated flow, canonicalized — the parsed shape the engine stored, identical to what a subsequent GET answers

Nested Shape: UpdateFlowResponse.error

PropertyTypeRequiredDescription
codeEnum<'VALIDATION_ERROR' | 'INVALID_FIELD' | 'MISSING_REQUIRED_FIELD' | …>✅Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages)
declaredCodestringoptionalThe producer-declared code, verbatim, when it is not a member of the closed code vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112)
messagestring✅Readable error message
userMessagestringoptionalProducer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces message.
refusaltrueoptionalProducer-declared: the 5xx this envelope carries is a deliberate refusal whose message is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose message is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — true is the only value.
categorystringoptionalError category (e.g. validation, authorization)
httpStatusintegeroptionalHTTP status of the response carrying this error
detailsanyoptionalAdditional error context (e.g. field validation errors)
requestIdstringoptionalRequest ID for tracking

Nested Shape: UpdateFlowResponse.meta

PropertyTypeRequiredDescription
timestampstring✅
durationintegeroptionalServer-side processing duration in milliseconds
requestIdstringoptional
traceIdstringoptional

Nested Shape: UpdateFlowResponse.data

PropertyTypeRequiredDescription
namestring✅Machine name
labelstring✅Flow label
descriptionstringoptional
successMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of a generic "Done".
errorMessagestringoptionalMessage carried on AutomationResult for every terminal run (not only screen flows); the screen-flow UI shows it as a toast instead of the raw error.
versionintegeroptional (default: 1)Version number
statusEnum<'draft' | 'active' | 'obsolete' | 'invalid'>optional (default: "draft")Deployment status
templateneveroptional[REMOVED] flow.template was removed in @objectstack/spec 17.0.0 (audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
typeEnum<'autolaunched' | 'record_change' | 'schedule' | 'screen' | 'api'>✅Flow type
variables{ name: string; type: string; isInput?: boolean; isOutput?: boolean; … }[]optionalFlow variables
nodes{ id: string; type: string; label: string; config?: Record<string, any>; … }[]✅Flow nodes
edges{ id: string; source: string; target: string; condition?: string | object; … }[]✅Flow connections
activeneveroptional[REMOVED] flow.active was removed in @objectstack/spec 17.0.0 (audit close-out) — it never had an effect: the engine arms flows from status, and active: false did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use status: 'obsolete' (or 'invalid') to unbind and disable a flow, status: 'active' to arm it. Run os migrate meta --from 16 to list the mechanical edits for existing sources; --write applies the ones it can prove, and you apply the rest by hand.
runAsEnum<'system' | 'user'>optional (default: "user")Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user.
errorHandling{ strategy?: Enum<'fail' | 'retry' | 'continue'>; maxRetries?: integer; backoffMs?: integer; backoffMultiplier?: number; … }optionalFlow-level error handling configuration. A durable pause ends the retry-governed segment: strategy: 'retry' describes one synchronous dispatch, so a run that parks on an approval/screen/wait node and later resumes gets one attempt for anything that fails after the pause. Protect the post-pause half with its own failure handling in the flow — a try_catch node's retry around the post-resume work, or fault edges to a handler node.
protection{ lock: Enum<'none' | 'no-overlay' | 'no-delete' | 'full'>; reason: string; docsUrl?: string }optionalPackage author protection block — lock policy for this flow.
_lockEnum<'none' | 'no-overlay' | 'no-delete' | 'full'>optionalItem-level lock — controls overlay & delete (ADR-0010).
_lockReasonstringoptionalHuman-readable reason shown when a write is refused by _lock.
_lockSourceEnum<'artifact' | 'package' | 'env-forced'>optionalLayer that set _lock (artifact | package | env-forced).
_provenanceEnum<'package' | 'org' | 'env-forced'>optionalOrigin of the item (package | org | env-forced).
_packageIdstringoptionalOwning package machine id.
_packageVersionstringoptionalOwning package version.
_lockDocsUrlstringoptionalOptional documentation link surfaced next to _lockReason.

On this page

TypeScript UsageAutomationApiErrorCodeAllowed ValuesAutomationFlowPathParamsPropertiesAutomationRunPathParamsPropertiesCreateFlowRequestPropertiesNested Shape: CreateFlowRequest.variables[number]Nested Shape: CreateFlowRequest.nodes[number]Nested Shape: CreateFlowRequest.edges[number]Nested Shape: CreateFlowRequest.errorHandlingNested Shape: CreateFlowRequest.protectionCreateFlowResponsePropertiesNested Shape: CreateFlowResponse.errorNested Shape: CreateFlowResponse.metaNested Shape: CreateFlowResponse.dataDeleteFlowRequestPropertiesDeleteFlowResponsePropertiesNested Shape: DeleteFlowResponse.errorNested Shape: DeleteFlowResponse.metaNested Shape: DeleteFlowResponse.dataGetFlowRequestPropertiesGetFlowResponsePropertiesNested Shape: GetFlowResponse.errorNested Shape: GetFlowResponse.metaNested Shape: GetFlowResponse.dataGetRunRequestPropertiesGetRunResponsePropertiesNested Shape: GetRunResponse.errorNested Shape: GetRunResponse.metaNested Shape: GetRunResponse.dataListRunsRequestPropertiesListRunsResponsePropertiesNested Shape: ListRunsResponse.errorNested Shape: ListRunsResponse.metaNested Shape: ListRunsResponse.dataResumeFailureDetailsPropertiesToggleFlowRequestPropertiesToggleFlowResponsePropertiesNested Shape: ToggleFlowResponse.errorNested Shape: ToggleFlowResponse.metaNested Shape: ToggleFlowResponse.dataTriggerFlowRequestPropertiesTriggerFlowResponsePropertiesNested Shape: TriggerFlowResponse.errorNested Shape: TriggerFlowResponse.metaNested Shape: TriggerFlowResponse.dataUpdateFlowRequestPropertiesNested Shape: UpdateFlowRequest.definitionUpdateFlowResponsePropertiesNested Shape: UpdateFlowResponse.errorNested Shape: UpdateFlowResponse.metaNested Shape: UpdateFlowResponse.data