Skip to main content
These routes create the things that start workflow runs without a caller: a compose draft the agent can run, a schedule that fires on a cadence, and a goal that repeats a workflow until a judge says the objective is met. They also read a run’s Job-tier workspace and step view, and list runs across an organization. Composing, scheduling, and creating or editing goals need workflows:write on the agent; pausing, resuming, and closing goals and releasing a workspace need workflows:execute; reads need workflows:read. Run routes, recovery, and the error codes are on Workflows, which also holds the base URL, scopes, and envelope pointer. The CLI equivalents on this page require lua-cli 3.36.0 or later.

Compose

POST /workflows/:agentId/compose

Validates a dynamic definition and, unless dryRun, saves it as a draft that POST .../runs can start by composeId. This is what the agent’s own compose tool calls.
string
required
graph or script.
object
The graph document, for form: "graph".
string
The script source (at most 256 KB), for form: "script".
any
A sample input to validate against.
string
Save under this name: lowercase kebab-case, at most 64 characters.
boolean
Validate only; write no draft.
string
The originating chat thread, when composed from chat.
Response 200 with the compose result; validation problems are returned in issues[], never as a 4xx. 400 VALIDATION_FAILED for a malformed body; 422 COMPOSE_FORM_FORBIDDEN when the organization allows one form only; 503 CONTROL_UNAVAILABLE.

Schedules

A schedule is a job that starts a workflow on a cadence. Each schedule row is { jobId, workflowId, trigger: [{ type, expression?, executeAt?, seconds?, timezone?, triggerId? }], status, paused, nextRunAt, lastFiredAt?, consecutiveFailures, autoDisabled?, lastError?, idempotencyKeyTemplate?, goalId? } with status one of active, paused, inactive, completed, missed, and nextRunAt null unless the schedule is live. A schedule that fails on consecutive fires is disabled by the platform and reports autoDisabled { at, consecutiveFailures, lastRunId?, lastReason }.

GET /workflows/:agentId/schedules

Lists the agent’s schedules as { "items": [...] }. Equivalent: lua workflows schedules list.

GET /workflows/:agentId/schedules/:jobId

Returns one schedule. 404 SCHEDULE_NOT_FOUND, also for a job that is not a workflow schedule.

POST /workflows/:agentId/schedules

Creates a schedule, or replaces the one for the same workflow.
string
required
The definition; must be on this agent (400 WORKFLOW_NOT_ON_AGENT).
array
required
From 1 to 5 slots of { type: "cron" | "once" | "interval", expression?, executeAt?, seconds?, timezone?, triggerId? }: a 5-field cron expression with an IANA timezone, an ISO 8601 instant, or whole minutes of at least 60 seconds. A sixth slot is 409 SCHEDULE_CAP.
string
Display name, at most 120 characters; defaults to the workflow name.
any
The run input on every fire; frozen at create.
object
{ maxCredits?, maxSteps?, maxDurationSeconds? } per run.
string
emailApp, email, app, or off.
string
Pin a version.
string
Rendered per fire, for example with {{occurrenceIso}}; at most 128 characters.
string
Rendered per fire into the run’s idempotency key from ${input.<path>} and ${scheduledTime} (at most 128 characters); a fire whose key is still held replays that run instead of starting one. Refused with 400 VALIDATION_FAILED when it has no placeholder or does not render against the input.
string[]
At most 10 of at most 40 characters.
object
Seeds every run.
object
{ maxOccurrences }: fire missed occurrences when re-enabled.
Response 201 with { "jobId", "workflowId", "schedules", "nextRunAt", "notify" }. 400 VALIDATION_FAILED with schedules-required or cadence-invalid at /schedules/<i>/<field>, or WORKFLOW_NOT_ON_AGENT; 404 WORKFLOW_NOT_FOUND; 409 SCHEDULE_CAP; 503 SCHEDULE_PROVISION_FAILED. Equivalent: lua workflows schedules create <workflow> --cadence '0 9 * * 1' --timezone Europe/London.

PATCH /workflows/:agentId/schedules/:jobId

Pauses or resumes a schedule and sets its backfill options.
boolean
false re-enables the schedule.
object or null
{ maxOccurrences } to persist; null to clear.
boolean
One-shot backfill on this re-enable; needs paused: false in the same body (400 VALIDATION_FAILED with backfill-now-requires-enable otherwise).
string or null
Set or clear the fire-time template; refused on a goal’s cadence job.
Response 200 with { "jobId", "workflowId", "status", "backfillOnEnable"?, "idempotencyKeyTemplate"?, "backfill"? }. 400 VALIDATION_FAILED; 404 SCHEDULE_NOT_FOUND. Equivalent: lua workflows schedules pause <jobId>, lua workflows schedules resume <jobId> --backfill-now.

DELETE /workflows/:agentId/schedules/:jobId

Deletes a schedule and answers { "success": true }. 404 SCHEDULE_NOT_FOUND; 409 GOAL_SCHEDULE with goalId for a goal’s cadence, which is stopped by pausing or closing the goal. Equivalent: lua workflows schedules delete <jobId> --yes.

Goals

A goal runs a workflow repeatedly until a judge says the objective is met, within maxRuns and an optional credit cap. A goal is { goalId, orgId, agentId, workflowId, workflowVersionId?, objective, judge, cadence, evaluation, maxRuns, budget?, maxTotalCredits?, initialState?, input?, status, runsUsed, iteration, currentRunId?, lineageId, jobId?, lastRunId?, verdict?, pauseReason?, consecutiveContinues, createdBy, createdAt, updatedAt, doneAt?, closedBy?, closeNote?, note?, raises?, rearmed?, job? } with status one of active, paused, done, closed, and pauseReason one of user, budget, max_runs, strikes.

POST /workflows/:agentId/goals

Creates a goal.
string
required
The definition; must be on this agent.
string
required
At most 2,000 characters.
object
required
Either a predicate judge, { predicate: { path, op, value? } }, where path is a dotted path (at most 256 characters) into the finished run (output, state, steps.<stepId>, iteration, runStatus) and op is one of eq, neq, gt, gte, lt, lte, exists, truthy; or an agent judge, { agentId, schema, role? }, where agentId is a sub-agent id or $self, schema is a JSON Schema with a boolean done at the root, and role ({ name, instructions, tools }) is required for $self.
integer
required
From 1 to 100.
array
At most 5 schedule slots in the schedule grammar; empty means evaluation.mode: "immediate".
object
{ mode: "immediate" | "cadence", delaySeconds? }; delaySeconds only with immediate.
object
{ maxCredits?, maxSteps?, maxDurationSeconds? } per iteration.
integer
Cap across every iteration; the goal parks when it is met.
object
Seeds every iteration.
any
The run input on every iteration, at most 64 KB.
string
Pin a version.
string
At most 128 characters; a replay answers 200 with the existing goal.
Response 201 with the goal. 400 VALIDATION_FAILED (goal-judge-schema-missing-done, goal-predicate-invalid, goal-cadence-invalid, goal-evaluation-invalid, goal-objective-too-long, cadence-invalid), GOAL_MAX_RUNS_INVALID, or WORKFLOW_NOT_ON_AGENT; 409 GOAL_CAP (cap) past the organization’s active-goal cap, 20 by default; 503 CONTROL_UNAVAILABLE. Equivalent: lua workflows goals create <workflow> --objective "Reach 50 signups" --judge-predicate 'output.signups gte 50', Workflows.setGoal().

GET /workflows/:agentId/goals

Lists goals as { "items": [...], "nextCursor"? }.
string
active, paused, done, or closed.
string
Goals of one definition.
string
Page cursor.
integer
From 1 to 100.
400 BAD_CURSOR or VALIDATION_FAILED. Equivalent: lua workflows goals list <workflow> --status active, Workflows.goals.list().

GET /workflows/:agentId/goals/:goalId

Returns the goal with its run history, runs: [{ runId, status, iteration?, verdict?, creditsUsed, createdAt, purged? }]. 404 GOAL_NOT_FOUND. Equivalent: lua workflows goals get <goalId>.

PATCH /workflows/:agentId/goals/:goalId

Edits an active or paused goal: objective, judge, cadence, evaluation (the mode is fixed at create; delaySeconds: null clears the delay), maxRuns, maxTotalCredits (null clears), budget (null clears), note (at most 2,000 characters; null clears), and ifMatch (the updatedAt you read; a mismatch is 409 GOAL_VERSION_CONFLICT with the current updatedAt). Answers the goal, with rearmed: true when the edit cleared a budget or run-count park. 400 VALIDATION_FAILED (an empty edit, or the create-time codes) or GOAL_MAX_RUNS_INVALID; 404 GOAL_NOT_FOUND; 409 GOAL_NOT_ACTIVE on a done or closed goal. Equivalent: lua workflows goals edit <goalId> --max-credits 500 --every 30m.

POST /workflows/:agentId/goals/:goalId/raise

Raises maxTotalCredits or maxRuns, increases only, with an optional note and ifMatch; the same value twice is a 200 no-op, and the raise is recorded in raises[]. 400 GOAL_RAISE_BELOW_SPENT (field, value, spent), VALIDATION_FAILED (nothing to raise, or a value below the current cap), or GOAL_MAX_RUNS_INVALID; 404 GOAL_NOT_FOUND; 409 GOAL_NOT_ACTIVE or GOAL_VERSION_CONFLICT. Equivalent: lua workflows goals raise <goalId> --max-runs 24.

POST /workflows/:agentId/goals/:goalId/pause

Pauses an active goal; 409 GOAL_NOT_ACTIVE otherwise. POST .../resume resumes a paused goal without backfill, and refuses with 409 GOAL_BUDGET_EXHAUSTED (spent, cap) while a credit cap is met. POST .../close closes it for good with an optional note of at most 2,000 characters. Each answers the goal; 404 GOAL_NOT_FOUND. Equivalent: lua workflows goals pause <goalId>, Workflows.goals.pause(), resume(), close().

Workspaces and Job-tier steps

GET /workflows/:agentId/runs/:runId/workspace

Returns the run’s workspace view, { runId, kind: "git" | "empty", backend, status, repo?, ref?, credentialsRef?, sizeGb, ttlHours, keepArtefacts?, branch?, baseSha?, headSha?, pushed?, bytesUsed?, filesChanged?, ... }; credentialsRef is an id, never a token. 404 RUN_NOT_FOUND or WORKSPACE_NOT_FOUND. Equivalent: lua workflows workspace <runId>.

POST /workflows/:agentId/runs/:runId/workspace/release

Releases the workspace early, with an optional note (at most 512 characters). Answers { "released": true, "status": "released" | "held" | "expired" } or { "released": false, "reason": "not_provisioned" | "already_released" | "held_empty" }; 404 RUN_NOT_FOUND; 409 WORKSPACE_IN_USE with the stepId still using it; 503 CONTROL_UNAVAILABLE. Equivalent: lua workflows workspace <runId> --release.

GET /workflows/:agentId/runs/:runId/steps/:stepId/job

Returns a Job-tier step’s view: segment of segmentsTotalMax, checkpoint, pod phase and timings, and a bounded log tail.
integer
One attempt instead of the current one.
integer
default:"200"
Log lines, from 1 to 2,000 (and at most 64 KB).
400 VALIDATION_FAILED for a non-numeric attempt or tail; 404 RUN_NOT_FOUND, JOB_NOT_FOUND, or JOB_LOGS_NOT_PERSISTED for a finished attempt that kept nothing; 503 CONTROL_UNAVAILABLE only for a live attempt. Equivalent: lua workflows jobs <runId>, lua workflows job-logs <runId> <stepId> --tail 500.

Organization-wide routes

GET /workflows/runs

Lists runs across an organization; workflows:read is checked on the organization.
string
required
The organization. Missing, blank, repeated, or over-long is 400 VALIDATION_FAILED, never 403.
string
Narrow to one agent.
Every filter of GET /workflows/:agentId/runs applies. 403 when the caller holds no workflows:read on that organization; an unknown organization and another organization look the same. The CLI lists runs per agent only, so there is no CLI tab.

GET /workflows/runs/:runId

Returns a run by id alone, for deep links from inbox receipts and notifications; the scope is checked on the run’s agent. Same body as GET /workflows/:agentId/runs/:runId. 404 RUN_NOT_FOUND. Moving one user’s schedules, approvals, connections, and drafts to another user is POST /workflows/reassign on Workflow approvals.

Organization workflow policy

The organization’s workflow policy is one document read and written at /organizations/:orgId/workflow-policy; /admin/orgs/:orgId/workflow-policy is the same handler under the admin prefix. Reading needs org:read on the organization and writing needs org:manage. The policy carries blocks this page does not describe (the feature switches, caps, pacing, composeForm, retention, audit, approvalSurfaces, artefactAcl, encryption, residency, export); this section covers the models block, which is what lua workflows policy models reads and writes, and the autonomy block, which lua workflows policy autonomy reads and writes.

GET /organizations/:orgId/workflow-policy

boolean
true or 1 adds effective: the filled caps with their ceilings and source (org or default), pacing, composeForm, retentionDays, readAccessAudit, models { compose, maxClass, consentActions, pins, fallback, classMap }, and autonomy { enabled, maxCredits, maxSteps, maxDurationSeconds, maxActions, maxRunsPerHour, forms } as the platform resolves them for this organization: every dimension answered, defaults filled, ceilings applied, and any agent-level narrowing the organization’s agents carry not applied here.
Response 200 with the stored policy plus orgId; the models block, when set, is { compose?, maxClass?, consentActions?, allow?, classes?, classMap?, pins?, fallback? } and the autonomy block { enabled?, maxCredits?, maxSteps?, maxDurationSeconds?, maxActions?, maxRunsPerHour?, forms? }, exactly as stored. 403 when the credential holds no org:read on that organization; an unknown organization answers the same. 404 ORG_NOT_FOUND for an organization row that no longer exists. Equivalent: lua workflows policy models get.

PATCH /organizations/:orgId/workflow-policy

Updates the policy. Each block you send is judged on its own; a block you leave out is untouched. Needs org:manage. The approvalSurfaces block is the one exception to bearer authentication: it can be named only from a first-party signed-in session, and any other credential that names it is refused with 403 SESSION_REQUIRED before anything is read or written.
object or null
{ compose?, maxClass?, consentActions?, allow?, classes?, classMap?, pins?, fallback? }. Merges per key: a key you omit keeps its value, null on a key clears it, and null on the whole block clears the block. compose is on or off (may the agent assign model classes when it composes; absent means the platform’s own default for the organization). maxClass is fast, balanced, or strong (absent means strong). consentActions is an integer of at least 1, the estimated actions above which a composed run shows the consent card (absent means 60). allow is a list of model codes that narrows the organization’s set (absent or empty means every approved model minus the organization’s excluded ones). classes is { fast?, balanced?, strong?: string[] }, ordered overrides of what each class resolves to. classMap is { <taskClass>: <modelClass> }, overriding the platform map per task class. pins is class-only or allowed: may a chat-composed workflow pin a concrete code (absent means class-only; a pushed workflow may always pin). fallback is same-class or same-provider, where a failed model call may hop.
object or null
{ enabled?, maxCredits?, maxSteps?, maxDurationSeconds?, maxActions?, maxRunsPerHour?, forms? }, the pre-consent envelope. Merges per key like models: a key you omit keeps its value, null on a key clears it, and null on the block clears the block. enabled is a boolean; absent means false, and the envelope admits nothing until it is true. The five numbers are integers of at least 1: maxCredits (absent means 20; at most the organization’s per-run credit cap), maxSteps (absent means 15; at most 40), maxDurationSeconds (absent means 86,400; at most 604,800), maxActions (absent means the organization’s models.consentActions; no ceiling), and maxRunsPerHour (absent means 20; at most 200). forms is a non-empty list without duplicates from graph, script, and static; absent means graph and static.
Response 200 with the policy as stored. For autonomy, a shape problem is 400 VALIDATION_FAILED with issues[] at /autonomy/<key>: autonomy-key-unknown for a member the envelope does not have, autonomy-enabled-not-boolean, autonomy-<key>-invalid for a number below 1 or above its ceiling (the message names the range), autonomy-forms-not-array, autonomy-forms-empty, autonomy-form-unknown, and autonomy-form-duplicate; a stored value left above a ceiling this request lowered is 400 CAP_EXCEEDED with cap, value, and ceiling. For models, a shape problem is 400 VALIDATION_FAILED with issues[] whose path is a JSON pointer such as /models/maxClass and whose code names the rule (models-key-unknown, models-compose-unknown, models-max-class-unknown, models-consent-actions-invalid, models-class-unknown, models-task-class-unknown, and their -not-object kin). The codes are then judged against the organization’s effective model set: 400 MODEL_NOT_ELIGIBLE (field, codes) for a code the organization may not use in allow or classes; 400 MODEL_CLASS_UNREACHABLE (maxClass, and taskClass, modelClass for a class-map entry above the ceiling) when no eligible model remains at or below maxClass; 400 MODEL_CLASS_EMPTY (modelClass) when an override or class-map entry names a class with no eligible model; 400 MODEL_JOB_PROVIDER_UNSUPPORTED (modelClass, codes, providers) when a class override leaves no model a Job-tier step can run on; 503 MODEL_POLICY_UNVERIFIABLE when that set cannot be read, in which case nothing is stored. 403 without org:manage; 404 ORG_NOT_FOUND. A change to either block takes effect on the next workflow start: saving it tells the platform to drop the policy it had cached. Should that notice be lost, the cached policy expires within 30 seconds and the cached snapshot of it within 60 seconds, so the change applies by then at the latest. Equivalent: lua workflows policy models set --max-class balanced --class-map judge=strong, lua workflows policy autonomy set --enabled on --max-credits 25.

See also