Format run status_url or result_url: which one do I poll?
Poll status_url for a small payload, then read result_url once the run is terminal. result_url answers 409 run_not_completed while the run is in flight.

Poll status_url while the run is in flight, because it returns a small payload, and read result_url once the status is terminal. result_url before that is 409 run_not_completed. You can also poll GET /v1/format-runs/{run_id}, which returns the full receipt at any status. All three work; the difference is payload size and what you get back.
Every create receipt carries four URLs, status_url, result_url, events_url and cancel_url. The Runs and results docs say to follow them rather than build paths by hand.
What does each endpoint return?
The three read endpoints overlap, so pick by what you need next.
| Endpoint | In flight | Once terminal |
|---|---|---|
GET /v1/format-runs/{run_id} | Full receipt, output is null | Full receipt with output, artifacts[], primary_output_url |
GET .../status (status_url) | status, next_action, cancelable, expires_at, queue, timestamps, URLs | Adds usage and, on failure, error; never output, artifacts or primary_output_url |
GET .../result (result_url) | 409 run_not_completed, with details.status | The full receipt |
GET .../events (events_url) | Phase timeline | Phase timeline |
Why does result_url return 409?
run_not_completed is not a failure. It means the run is still queued or processing. The error carries details.status with the current status and a retry_after_seconds hint for the next poll. The documented handling is to poll status_url, then read result_url.
Do not treat a 429 or a 503 in this loop as a failed run either. Abandoning the loop does not stop the run or its spend. The read budget is separate from, and forty times larger than, the write budget, so a poll loop cannot starve your own creates.
How do I write the loop?
Back off by doubling the gap up to a minute. Long-form host video is typically 15 to 30 minutes of work, so polling every second buys nothing. Stop on any status that is not queued or processing, then read the result:
RUN_ID="arun_..."
SLEEP=5
while :; do
S=$(curl -sS "https://api.sume.com/v1/format-runs/$RUN_ID/status" \
-H "Authorization: Bearer $SUME_API_KEY")
STATUS=$(echo "$S" | jq -r '.data.status')
case "$STATUS" in
queued|processing) sleep "$SLEEP"; SLEEP=$(( SLEEP < 60 ? SLEEP * 2 : 60 )) ;;
*) break ;;
esac
done
curl -sS "https://api.sume.com/v1/format-runs/$RUN_ID/result" \
-H "Authorization: Bearer $SUME_API_KEY" \
| jq '.data | {status, primary_output_url, error, output_error}'What should I check on the finished receipt?
Check error and output_error before reading output. A failed run still lists every file it made in artifacts[], but primary_output_url is null on any non-completed run, so if (run.primary_output_url) is a safe test for whether the deliverable exists.
Use expires_at from the status payload as your own ceiling. A non-terminal receipt carries the deadline past which the run is force-finalized as failed: 90 minutes from created_at, or sooner when the run is older than 25 minutes and has been silent for 10. It is null once the run is terminal.
Can I skip polling altogether?
Yes, with a webhook, and the best practice is both: the webhook as the fast path and a read of result_url as the backup for the day your endpoint is down. A canceled or skipped run never delivers a webhook, so a webhook-only integration must read the cancel response for those. In TypeScript, subscribeFormatRun creates the run and runs the loop, and waitForRun does the loop for an id you already hold; it requires family: "format" because a run id does not say which surface it belongs to.
Sume does not offer a push stream of progress. There is no SSE or WebSocket channel, and events_url is a polled phase timeline of preparing, running and finalizing, not agent output.
Which should I choose?
Use the full receipt endpoint when your handler is the same for polling and webhooks: the webhook payload is byte-identical to data from GET /v1/format-runs/{run_id}, so one handler serves both transports. Use status_url when you poll many runs and only need to know when each ends, since it never carries output or artifacts. Use result_url when you want the API itself to refuse you until the run is terminal.
Sources
Related posts
More in Formats
- Format run stuck in queued: read queue.state and retry_after_seconds
A Sume Format run that stays queued carries a queue block. waiting is normal; runtime_unavailable means nothing claimed it. What to read, and when to ticket.
- Format run failed with unattended_blocked: why it never half-finishes
Over the API, a Sume Format run is told approvals are granted. If it still cannot finish it fails with unattended_blocked, never a half-done completed.
- OpenAI response_format json_schema to a Sume output_schema
Moving a json_schema from OpenAI structured outputs to a Sume Format run: the field name, what transfers, no JSON mode, and why output comes once, post-run.
- Sume agent_reported_failure: the three reasons and what to do
agent_reported_failure on a Sume run means the run's own receipt said it did not deliver. Its details.reason tells you which of three cases it was.
Written by Sume