GET result_url returns 409 run_not_completed: poll status_url first

A 409 run_not_completed from result_url means the Format run is not terminal. Read details.status, poll status_url with backoff, then fetch the result.

5 min readSume
All posts

409 run_not_completed means you called GET /v1/format-runs/{run_id}/result while the run was still queued or processing. Nothing is wrong with the run or your key. The error carries details.status with the current status. Poll status_url until the run is terminal, then call result_url again.

Which URL returns what

A receipt holds its own URLs, and the docs ask you to use them rather than build the paths. They differ in what they return and when they answer.

URL on the receiptReturnsWhile the run is in progress
The receipt itself, GET /v1/format-runs/{run_id}Full receiptAnswers at every status
status_urlSmall poll payload: status, next_action, cancelable, expires_at, queue, timestamps, URLsAnswers
result_urlFull receipt409 run_not_completed with details.status
events_urlPhase timeline (Format runs)Answers

Mind one trap in the status payload. When the run is terminal it adds usage, and error on a failure, but it never holds output, artifacts or primary_output_url. A client that stops on status: "completed" from status_url and reads primary_output_url from it gets undefined. Fetch result_url or the full receipt for the media.

A loop that waits correctly

The sample polls status_url with a doubling gap capped at 60 seconds, which matches the docs' guidance for long-form video, then reads result_url once. It treats queued and processing as the only non-terminal states. It accepts the body either as {data: ...} or unwrapped. Set SUME_API_KEY first; SUME_API_BASE_URL is optional and includes /v1.

const key = process.env.SUME_API_KEY;
const base = process.env.SUME_API_BASE_URL ?? "https://api.sume.com/v1";
if (!key) throw new Error("set SUME_API_KEY");
const get = (path) => fetch(`${base}${path}`, { headers: { "x-api-key": key } });

export async function finish(runId) {
  for (let wait = 5; ; wait = Math.min(wait * 2, 60)) {
    const s = await (await get(`/format-runs/${runId}/status`)).json();
    const status = s.data?.status ?? s.status;
    if (status !== "queued" && status !== "processing") break;
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
  // status never carries output, artifacts or primary_output_url
  const res = await get(`/format-runs/${runId}/result`);
  if (res.status === 409) throw new Error("run_not_completed: poll status_url first");
  return res.json();
}

console.log(await finish(process.argv[2] ?? "arun_demo"));

Why the 409 is useful

The endpoint refuses to hand you a half-finished receipt, so a handler that reads result_url can trust the receipt it gets: the run is terminal, and output, artifacts[] and primary_output_url are final. The refusal costs you one read and nothing else, since a read never starts a run. The same split exists on the run webhook, whose payload is byte-identical to data from the receipt route, so one handler can serve both transports.

Rules that keep a poll loop safe

  • Back off. A poll every second buys nothing for a 15 to 30 minute run and spends read budget. Double the gap up to one minute.
  • A 429 or 503 in the loop is temporary. The run keeps executing and spending, so wait for retry-after and poll again. Do not mark the run failed.
  • Use expires_at from a non-terminal receipt as your ceiling. Sume force-finalizes a run as failed at that deadline, so do not invent a different timeout.
  • A 404 format_run_not_found means an unknown id or a run owned by someone else. The API gives the same answer for both.
  • If you would rather not poll, send communication.webhook_url at create and wait for the single terminal POST. Keep polling as a backup.

In TypeScript

subscribeFormatRun creates the run and runs this loop for you, and waitForRun does it for a run id you already have. Both resolve on each terminal status, and both ride out transient read failures. See the SDK runs page for the options, and the Runs and results page for the three receipt URLs.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume