GET format-runs result 409 run_not_completed: poll status first
Reading result_url while a Format run is in flight returns 409 run_not_completed with details.status. Poll status_url, then read the receipt once terminal.

GET /v1/format-runs/{run_id}/result answers 409 run_not_completed while the run is still in flight. It is not a failure of the run. details.status tells you the current status, and retry_after_seconds suggests when to look again. Poll status_url until the run is terminal, then read result_url once.
Which URL do I poll?
A Format run receipt names several URLs, and they answer differently while the run is going. Most integrations poll the receipt itself. The status_url is the small poll payload. The result_url is for after the end.
| Field on the receipt | Endpoint | While running |
|---|---|---|
| (the receipt) | GET /v1/format-runs/{run_id} | Full receipt at any status |
| status_url | GET /v1/format-runs/{run_id}/status | status, next_action, cancelable, expires_at, queue, timestamps |
| result_url | GET /v1/format-runs/{run_id}/result | 409 run_not_completed with details.status |
| events_url | GET /v1/format-runs/{run_id}/events | The phase timeline |
What does the status payload leave out?
Once terminal, status_url carries usage and, on a failure, error, but never output, artifacts or primary_output_url. So a terminal status is your signal to make one more read: the full receipt from result_url or the main receipt URL. Also note queue: if queued lasts, queue.state is waiting while pickup is inside the normal window and runtime_unavailable once the run has waited past it with nothing claiming it. Then retry_after_seconds says how long to back off.
The error table for this call also lists 429 rate_limited with details.scope: read. Reads have their own budget, far larger than the write one, so a polling loop does not starve your creates.
const base = process.env.SUME_API_BASE_URL ?? "https://api.sume.com/v1";
const headers = { "x-api-key": process.env.SUME_API_KEY! };
const TERMINAL = new Set(["completed", "failed", "canceled", "skipped"]);
export async function resultWhenDone(runId: string) {
for (;;) {
const s = await fetch(`${base}/format-runs/${runId}/status`, { headers });
if (!s.ok) throw new Error(`status read failed: ${s.status}`);
const { data } = await s.json();
if (TERMINAL.has(data.status)) break;
await new Promise((r) => setTimeout(r, 2000));
}
const r = await fetch(`${base}/format-runs/${runId}/result`, { headers });
return r.json();
}Why does the SDK not retry this 409 for me?
On this API a 409 means a state conflict, and repeating the call returns the same answer until the state changes, so the SDK's retry rule leaves 409 out on purpose. That is why subscribeFormatRun and waitForRun poll status rather than hammering the result URL. In TypeScript, call one of them and you get the terminal receipt without writing this loop.
Prefer a webhook if you can skip the wait: the run webhook delivers the identical receipt, and polling stays the fallback.
How long can a run take, and what if I stop watching?
Video Formats routinely run ten to twenty minutes, which is why subscribeFormatRun defaults to a 20-minute timeout against waitForRun's 10. Giving up on a poll does not stop the run or its spend. If a read inside your loop returns 429 or 503, that is the read failing and not the run, so back off and poll again rather than abandoning the handle.
The receipt also carries expires_at on the status payload and a cancel_url. If you no longer want the output, cancel explicitly: generation that finished before a cancel is still billed, but a run you stop early does not go on spending. A skipped run, one skipped by on_active_run: "skip", costs nothing.
Does the same 409 exist for generation jobs?
Generation jobs have a sibling, 409 job_not_completed, on /v1/jobs/{id}/result, and the two surfaces are not interchangeable: a run id and a job id belong to different endpoints and different SDK helpers. For Format, Action and Agent runs use waitForRun, and for generation jobs use waitForJob. Both resolve for any terminal status, so check status on what they return rather than assuming success.
Sources
Related posts
More in Developers
- Format run webhook_delivery status: what each value means
A Format run receipt carries webhook_delivery with six statuses: not_armed, pending, retrying, delivered, failed, exhausted. Read them to decide on a redeliver.
- frame_images needs frame_type: first_frame or last_frame on Sume
Each frame_images entry on POST /v1/videos needs a frame_type. What the two values mean, how supported_frame_images limits them, and a pre-check.
- Gemini CLI settings.json httpUrl, timeout and includeTools for Sume
Gemini CLI's MCP entry takes httpUrl, headers, a 600000 ms timeout and includeTools. Set up Sume's hosted server with a read-only tool list and one credential.
- Gemini Live Translate transcripts as subtitles: you supply timing
Google's Live Translate can return input and output transcripts, but the page lists no word times. To burn subtitles, time each line, then send cues to Sume.
Written by Sume