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.
Developers building Studio clients, Slack/email notifiers, or backend workers that continue parked runs.
runId from execute / webhook.GET /api/runs/:runId or listen on the run SSE stream until status === "suspended".state.resume (or the SSE input_request event):Studio History shows the same fields and an API curl snippet. See also How do I stream playbook run events?.
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"}}'
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.
Treat request_mismatch / wake_conflict as already-answered for idempotent UX.
Notifiers are delivery UIs for the same park:
input_request SSE, webhook, or poll until suspended).runId, requestId, prompt, details, schema.options.POST /api/runs/:runId/reply with { requestId, reply }.Do not invent a custom pause API inside step code, and do not busy-wait on Slack from the sandbox.
Same idea: stable run identity + server-issued wait token + payload.
Ballet's requestId is the UiPath taskId / Temporal signal-correlation equivalent.