How do I wake a suspended run with reply or resume?
TL;DR: When a run is suspended, continue the same runId with POST /api/runs/:id/reply (HITL / waitForInput) or POST /api/runs/:id/resume (operator pause). Copy requestId from state.resume or the SSE input_request event — never invent it. Slack and email are notifiers that call the same /reply contract.
Who this is for
Developers building Studio clients, Slack/email notifiers, or backend workers that continue parked runs.
How do I discover a parked run?
- Keep the
runIdfrom execute / webhook. - Poll
GET /api/runs/:runIdor listen on the run SSE stream untilstatus === "suspended". - Read
state.resume(or the SSEinput_requestevent):
Studio History shows the same fields and an API curl snippet. See also How do I stream playbook run events?.
How do I wake HITL (waitForInput)?
Prefer reply when reason is input_request:
POST /api/runs/:runId/reply
Authorization: Bearer <token>
Content-Type: application/json
{
"requestId": "<from state.resume.requestId>",
"reply": { "id": "approve", "label": "Approve" }
}
Effect: wakes the same run and injects reply so waitForInput returns that value in the re-entered step. Later steps do not see the reply.
Operator pause must not use /reply — you get 409 reply_not_accepted. Use /resume instead.
# Discover
curl -s -H "Authorization: Bearer $BALLET_API_TOKEN" \
"$API/runs/$RUN_ID" | jq '.status, .state.resume'
# Continue (HITL)
curl -s --request POST \
--url "$API/runs/$RUN_ID/reply" \
--header "Authorization: Bearer $BALLET_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"requestId":"<state.resume.requestId>","reply":{"id":"approve","label":"Approve"}}'
How do I wake an operator pause?
POST /api/runs/:runId/resume
Authorization: Bearer <token>
Content-Type: application/json
{}
Optional body: { "fromStepId": "...", "input": {} }.
For operator pause, no reply is injected (so a later waitForInput is not auto-answered). /resume also continues failed/cancelled runs from a step.
Common error codes
Treat request_mismatch / wake_conflict as already-answered for idempotent UX.
How do Slack / email notifiers work?
Notifiers are delivery UIs for the same park:
- Detect park (
input_requestSSE, webhook, or poll untilsuspended). - Read
runId,requestId,prompt,details,schema.options. - Post Approve/Reject (embed server-issued ids in the button or link).
- On click →
POST /api/runs/:runId/replywith{ requestId, reply }.
Do not invent a custom pause API inside step code, and do not busy-wait on Slack from the sandbox.
How does this compare to Temporal and UiPath?
Same idea: stable run identity + server-issued wait token + payload.
Ballet's requestId is the UiPath taskId / Temporal signal-correlation equivalent.
