Workflow
Open in
Work that runs for days and never loses its place: a Workflow is durable code, a state machine, or both. It is the body of a long Verb, and each moment your Customer sees is a state.
export default define({
…
Deal: {
…
kickoff: workflow(async ($, deal) => {
const { plan } = await $.generative('Draft the kickoff plan for {company.name}', deal,
{ returns: { plan: 'string' } })
await $.human('Approve the plan', { plan }, { role: 'manager' })
const at = await $.step('stamp it', () => new Date().toISOString())
return { plan, at }
}),
},
on: { 'Deal.won': 'Deal.kickoff' }, // Trigger: what reacts to a change
…
})Durable code
A won Deal starts the kickoff: a model drafts the plan, a manager approves it, and the run keeps the time it was approved. The on line is its Trigger.
A Verb the on line names starts when its Event happens, once per Event, with no call.
Each $ call is a step: it runs once, and its result is kept. A failed step retries until its limit runs out, and a run survives restarts and waits of weeks.
| Call | What it does |
|---|---|
$.step(name, | Runs fn once and keeps its result: a replay reads the result and never runs it again. |
$.code(fn, | Runs a code Function as a step. |
$.generative(prompt, | Asks a model, and answers the fields returns names. |
$.agentic(prompt, | Gives an agent the job and the Sources it may use; it answers when the job is done. |
$.human(task, | Waits for a member who holds the Role to complete the task at /actions/. |
$.wait('14d'), | Waits for a duration, or for the first Event that matches where. |
$.call('Company.get', | Calls a Verb of this API, or of a Source that is an API product, and answers its reply. |
return output | Ends the run: the value is the Action's output. |
Durations read '2d' or '30m', and a schedule reads 'every Mon,, run once per Customer in that Customer's timezone. While the manager has not approved, the run is Pending, and its reason says who holds the ball.
curl https: //pipeline.apis.dev/ actions/ act_8 \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_1154ca02bc01e341ac7b
{
"id": "act_8",
"verb": "Deal.kickoff",
"status": "pending",
"reason": "AwaitingWorker",
"poll": "/actions/ act_8"
}curl https: //pipeline.apis.dev/ actions/ act_8/ complete \
-H "Authorization: Bearer $MANAGER_KEY" \
--json '{"output": {"approved": true}}'HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_5881a5df799b8e82a74b
{
"id": "act_8",
"verb": "Deal.kickoff",
"status": "done",
"output": {
"plan": "Week 1: import. Week 2: first review.",
"at": "2026-10-26T15: 00: 00Z"
}
}| Reason | What causes it |
|---|---|
AwaitingWorker | A $.human step waits for a member who holds its Role. |
AwaitingInput | A machine waits in a state for an event it accepts. |
AwaitingTime | A $.wait duration or an after timer is running. |
AwaitingSource | A Source has not answered yet, or a $.wait waits for its Event. |
AwaitingAction | A $.call waits for another Action to finish. |
Retrying | A step failed and runs again after its backoff, until its limit runs out. |
Running | A step or a Function is working now. |
State machine
Same job, other style: write code when the work is a list of steps, and a machine when it waits on people and the clock. A machine names its states, the events each one takes and its timers.
export default define({
…
Deal: {
…
kickoff: machine({
initial: 'reviewing',
states: {
reviewing: { // the draft runs on the way in
entry: generative('Draft the kickoff plan for {company.name}',
{ returns: { plan: 'string' } }),
on: { approve: { target: 'approved', role: 'manager' } },
after: { '2d': 'escalated' },
},
escalated: { on: { approve: { target: 'approved', role: 'manager' } } },
approved: { type: 'final' },
},
}),
},
on: { 'Deal.won': 'Deal.kickoff' }, // Trigger: what reacts to a change
…
})A machine's events are Verbs on its Noun, each sent by the Role it names; it fires after on its timer, and resumes where it left off.
The machine's state is the Action's state, and accepts lists the events it takes. approve is a Verb on Deal: POST /deals/, approveDeal and pipeline deals approve.
curl https: //pipeline.apis.dev/ actions/ act_9 \
-H "Authorization: Bearer $PIPELINE_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_e51d811890c4fe07c5e4
{
"id": "act_9",
"verb": "Deal.kickoff",
"status": "pending",
"reason": "AwaitingInput",
"state": "reviewing",
"accepts": ["approve"],
"poll": "/actions/ act_9"
}curl -X POST https: //pipeline.apis.dev/ deals/ deal_1/ approve \
-H "Authorization: Bearer $MANAGER_KEY"HTTP/1.1 200 OK
X-Action-Id: act_9
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_f157fac4bfe56e473ecf
{
"id": "act_9",
"verb": "Deal.kickoff",
"status": "done",
"state": "approved",
"output": {
"plan": "Week 1: import. Week 2: first review.",
"at": "2026-10-26T15: 00: 00Z"
}
}An event the current state does not take answers 409, and a member without its Role answers 403.
When a Verb is internal
A Verb that an on line starts and that names no Role is internal: it serves no path, tool or command. While it runs, its Thing is in its activity state, such as drafting.
curl https: //filings.apis.dev/ filings/ filing_1 \
-H "Authorization: Bearer $FILINGS_KEY"HTTP/1.1 200 OK
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_f781a6ad9678c1e5146d
{
"id": "filing_1",
"company": "acme.com",
"year": 2026,
"status": "drafting",
"createdAt": "2026-10-24T09: 00: 00Z",
"updatedAt": "2026-10-24T09: 00: 01Z"
}curl -X POST https: //filings.apis.dev/ filings/ filing_1/ draft \
-H "Authorization: Bearer $FILINGS_KEY"HTTP/1.1 404 Not Found
Content-Type: application/ problem+json
X-Cost: price= 0, currency= "USD"
X-Request-Id: req_537212d0f3c7ec2b2bb7
{
"type": "https: //filings.apis.dev/ docs/ errors#no_route",
"title": "Not Found",
"status": 404,
"detail": "draft is internal; Filing.ordered starts it.",
"code": "no_route",
"instance": "req_537212d0f3c7ec2b2bb7"
}What you never write
A read, a create, an update, a delete and a lifecycle move answer the record, and a Function or a Workflow answers its Action. Durable runs survive restarts and long waits, and each run stays on the Version it started on.
| What you never write | What does it instead |
|---|---|
| Code that starts a run | The on line, or a call to the Verb |
| A route that takes an event | A Verb on the Noun, which the machine accepts |
| A check for a wrong move | The lifecycle, which answers 409 before anything runs |
| A status route | The Action at /actions/, with its reason while it waits |
| A history table | The change feed: every change as an Event, in order |
Seen is a state and unseen is a step: the drafted plan is a step, and reviewing, escalated and approved are states.