API reference
Workflow runs
Start workflow runs, send them signals, read and follow a run's tree, and cancel a run with everything it started.
A workflow run is a job, so the Jobs endpoints work on runs too. The code of a workflow lives in your workers, written with an SDK: these endpoints start runs, signal them and watch them from anywhere.
Start a run
POST /v1/spaces/{space}/jobs · scope create
Create a job with the workflow's name, its input as the payload, and "workflow": true. A worker that serves the workflow picks it up.
curl -s "$EMIT_URL/v1/spaces/accounts/jobs" \
-H "Authorization: Bearer $EMIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "trial", "payload": {"userId": "usr_42", "email": "ada@example.com"}, "key": "trial-usr_42", "workflow": true}'
# {"created":[{"id":"01M3QSR386017JCY0DC11GTG7D","outcome":"NEW","attempt":0}]}Use key against double starts, or singleton for one open run at a time: see Starting, keys and cancelling. Wait for the result by following the run, or by getting it once it's done.
Send signals
POST /v1/spaces/{space}/jobs:signal · scope create
Send one signal, an array, or {"signals": [...]}. Name the run by exactly one of id, key and singleton.
namestringrequiredThe signal's name: what the run waits for. At most 256 bytes.
idstringThe run's id.
keystringThe key the run was started with.
singletonstringThe singleton the run was started with.
dataJSONWhat the run receives. At most 4 MiB.
curl -s "$EMIT_URL/v1/spaces/accounts/jobs:signal" \
-H "Authorization: Bearer $EMIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "trial-usr_42", "name": "upgraded", "data": {"plan": "pro"}}'const took = await trial.signal({ key: "trial-usr_42" }, "upgraded", { plan: "pro" });took, err := trial.Signal(ctx, emit.ByKey("trial-usr_42"), "upgraded", map[string]string{"plan": "pro"}){ "delivered": [true], "ids": ["01M3QSR386017JCY0DC11GTG7D"] }One entry per signal: delivered is true if a run took it, and ids names that run. false, with an empty id, means there's no unfinished run to take it. A signal is never refused for that.
A signal sent before the run waits for it is kept. A signal repeated after a lost answer is delivered again, so send data that can arrive twice safely.
Signal, or start
POST /v1/spaces/{space}/jobs:signalOrStart · scope create
Delivers a signal to the open run under a singleton, or, if there's none, starts a run with job instead. Two calls at once never start two runs.
singletonstringrequiredThe run to find, or to start under.
namestringThe signal to send. Leave it out to only start a run if none is open.
dataJSONThe signal's data.
jobobjectrequiredThe run to start if none is open, as in Create jobs: its name, payload and "workflow": true. No key. The run it starts doesn't get the signal: its input is how it begins.
curl -s "$EMIT_URL/v1/spaces/accounts/jobs:signalOrStart" \
-H "Authorization: Bearer $EMIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"singleton": "trial-usr_88",
"name": "upgraded",
"data": {"plan": "pro"},
"job": {"name": "trial", "payload": {"userId": "usr_88", "email": "di@example.com"}, "workflow": true}
}'
# {"id":"01M3QSRDB002NRZ5VBGNQXKT8Q","started":true}started is true if this call started the run, and false if a run was open and got the signal.
Read a run's tree
GET /v1/spaces/{space}/jobs/{id}/tree · scope read, or a follow token
The run and everything under it: its steps, sleeps and signals, in the order they were made. A step that's itself a workflow run brings its own children. This is what the dashboard's run timeline shows.
curl -s "$EMIT_URL/v1/spaces/accounts/jobs/01M3QSR386017JCY0DC11GTG7D/tree" -H "Authorization: Bearer $EMIT_TOKEN"{
"id": "01M3QSR386017JCY0DC11GTG7D",
"name": "trial",
"status": "COMPLETED",
"attempt": 3,
"children": [
{ "name": "send-email", "lane": 0, "status": "COMPLETED", "label": "", "children": [] },
{ "name": "_timeout", "lane": 2, "status": "CANCELLED", "untilMs": "0", "children": [] },
{ "name": "upgraded", "lane": 1, "status": "COMPLETED", "children": [] }
]
}Each node has the job's id, name, status, attempt, maxAttempts, createdAtMs, finishedAtMs, progress and children, plus:
laneintegerWhat the node is in its run: 0 a step, 1 a signal (its name is the signal's), 2 a sleep (_sleep) or a wait's timeout (_timeout).
untilMsstringFor a sleep or a timeout, when it ends. For a running step, when its lease ends.
errorstringFor a failed step, or one waiting to be retried, its last error.
notesobject[]The lines written for people to read (wf.note, job.note), in order.
labelstringThe few words the step was started with.
detachedbooleanThe step is waiting on a link.
flowobjectFor a run: parked is true while it waits, with no worker holding it, and awaiting names the signals it waits for.
nextNintegerSet when this node's children were cut to fit: ask for ?from_n= this number to read the rest.
moreintegerWith nextN: how many of its children were cut.
A tree holds at most 20,000 nodes, 14 MiB, and 16 levels of runs. From emit 0.6, ?limit= reads a page of at most that many nodes (up to 500): the run's nextN says where the next page starts, so ask again with ?from_n= that number until there's no nextN. A child run cut by the page has its own nextN: page it by its own id. If the run starts over (flow.gen changes) while you page, start again from the beginning.
To follow a run as it changes, add ?tree=true to Follow a job: each message is then the whole tree, sent again whenever any node in it changes. Someone following with a follow token sees names, statuses, progress and notes, but no inputs, results, errors or labels.
Cancel a run
POST /v1/spaces/{space}/jobs:cancel · scope admin
With "tree": true, cancels each run and everything it started, all the way down: steps, child workflows and their steps, and steps it enqueued.
curl -s "$EMIT_URL/v1/spaces/accounts/jobs:cancel" \
-H "Authorization: Bearer $EMIT_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ids": ["01M3QSR386017JCY0DC11GTG7D"], "tree": true}'Without tree, the run and the steps it started are cancelled, and steps it enqueued go on.