Tasks

Transition a task

Fires a named transition on a task. The transition must exist in the workflow and be valid from the task's current state; its guard must pass. This is the single path every state change routes through. A transition declaring requires_approval does not move the task — it parks a pending ApprovalItem and returns the task with pending_transition set; the move applies only when the approval is approved.

post/v1/projects/{project_id}/tasks/{task_id}/transitions

Request

  • Base URL: https://api.naturali.ai
  • URL: https://api.naturali.ai/v1/projects/{project_id}/tasks/{task_id}/transitions
  • Auth: one of:
    • HTTP bearer
    • OAuth 2 (authorization code, authorization URL https://api.naturali.ai/authorize, token URL https://api.naturali.ai/token, scopes: mcp:access)

Path parameters

task_idstring required

Headers

Idempotency-Keystring

The request's deduplication key. Equivalent to idempotency_key in the request body; sending both with different values is 400 VALIDATION_FAILED. A non-empty string of at most 255 characters.

Request body

transitionstring required
notestring nullable
payloadobject

Keys shallow-merged over the task's payload under the transition's lock, before the guard reads it, exactly as PATCH merges them; the result must satisfy the payload schema of the version the task is pinned to (TASK_PAYLOAD_INVALID). A refused move writes none of it. On a requires_approval transition the payload is held with the gate and merged when the approval fires the move, so a rejected or expired gate writes nothing.

idempotency_keystring

The move's deduplication key, unique within the task and claimed for as long as the task exists. The same key with the same body replays the task as it is now instead of moving it again, whether the move applied or parked an approval; with a different body it is 409 IDEMPOTENCY_KEY_REUSED. Equivalent to the Idempotency-Key header.

tool_contextobject

Caller context for the automation dispatches the task makes from here on, forwarded as X-Naturali-Context-<key> headers on their tool calls. Supplying it replaces the task's stored bag wholesale; omitting it keeps the current one, so the context follows whoever last moved the task and survives every move that does not speak about it — including an approval gate, a retry, and an automation hop. Send an empty object to clear it without closing the task. The reserved identity keys (session_id, actor_id, actor_external_id) are stripped in any casing and re-derived server-side. A key outside the HTTP header-name grammar is rejected with INVALID_TOOL_CONTEXT_KEY (400). Write-only: never returned by a task read, and cleared when the transition closes the task.

Response

The task after the transition

idstring
project_idstring
workflow_idstring
workflow_versioninteger nullable

The workflow version this task runs on, fixed when the task was created. Transitions, approval gates and payload validation all resolve through it, so editing the workflow never re-shapes a task already in flight. null for tasks created before pinning existed, which run on the live definition.

titlestring
statestring
status'open' | 'closed'
payloadobject

Caller-owned task data; input to guards (as task.payload) and dispatch mappings. The engine never writes into it except the workflow's declared payload_writes.

metadataMetadataBag

Caller-owned annotations on a resource, stored as the object they were written as: the types a value was written with are the types a read returns, so a filter can ask an ordering question about a number. Unlike other body fields, keys are stored and returned verbatim in the casing supplied — they are not converted between snake_case and camelCase.

No key is reserved, and that is the point: every piece of state the platform owns lives in its own typed column, so nothing written here reaches platform state. The platform never reads the bag — it is not an IAM context, not a policy input and not part of a prompt — which is what separates it from a tag bag.

last_resultunknown
assigneestring nullable
active_dispatchobject nullable

{ kind, id, status } of the current state's dispatch, if any. kind is generation, orchestration_run or tool_call; a tool_call always carries a null id, since a direct tool call leaves no addressable record. Carries an additional attempt (1-based) while the state's on_enter.retry policy is in effect.

automation_status'running' | 'completed' | 'failed' | 'unrouted' | 'paused' | 'null' nullable

Status of the current state's dispatch. null until a state with an automation is entered. paused means an operator pause suppressed this state's on_enter before it ran, so resume-task will start it.

pause_requested_atstring date-time nullable

When an operator paused this task's automation, or null when no pause is in force.

pause_reasonstring nullable

The reason supplied with the pause, when one was.

automation_chain_depthinteger

Server-owned. How many machine-driven transitions have run back-to-back with no outside intervention — a dispatch outcome routed through on_complete/on_failure, or a transition-task call made by a dispatched run or agent with its run-as token. Any move by a person, a plain API key, or an approval resolution resets it to 0. Once it would exceed the server's limit (TASK_AUTOMATION_CHAIN_LIMIT, default 50) the next such transition is refused with TASK_AUTOMATION_CHAIN_LIMIT, bounding a cycle composed across workflows and orchestrations.

pending_transitionstring nullable

The name of a requires_approval transition parked awaiting a human decision. Non-null while an ApprovalItem gates the move; the task stays in its current state and no other transition may fire until the approval resolves.

entered_state_atstring date-time
created_atstring date-time
updated_atstring date-time

Example response

{
  "workflow_version": 1,
  "metadata": {
    "author": "John",
    "revision": 2
  }
}

Changes

No recorded changes to this operation across all 1 revision of this API.