Sume job next_action: poll_status, fetch_result, inspect_events
The Sume status payload tells your client what to do next. Three next_action values map to five job states; here is the full table and a TypeScript switch.

Every Sume job read carries a next_action field with exactly three possible values: poll_status for a job that has not finished, fetch_result for a completed job, and inspect_events for a failed or canceled one. A client that switches on this field does not need its own table of statuses.
The field sits next to terminal, result_ready, cancelable, and next_poll_after_seconds on submit responses and on GET /v1/jobs/:id/status. The Sume OpenAPI schema describes next_action as an enum of those three values, and the docs describe the statuses behind them.
The mapping
The table combines the status list from the Jobs and results page with the envelope fields in the OpenAPI schema.
| Status | next_action | terminal | result_ready | Cancelable |
|---|---|---|---|---|
queued | poll_status | false | false | Yes, before generation work starts |
processing | poll_status | false | false | Only if generation has not started |
completed | fetch_result | true | true | No |
failed | inspect_events | true | false | No |
canceled | inspect_events | true | false | No |
A switch that follows the field
The status response also keeps a queue-shaped status (IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED, CANCELED) and sume_status. The docs say the two always agree and tell you not to mix them. Branch on next_action and you avoid the question.
The snippet below reads one status and returns what the caller should do. It uses the data envelope that status reads return.
type Next = "wait" | "fetch" | "debug";
export async function nextStep(jobId: string): Promise<{ step: Next; waitSeconds?: number }> {
const res = await fetch(`https://api.sume.com/v1/jobs/${jobId}/status`, {
headers: { Authorization: `Bearer ${process.env.SUME_API_KEY}` },
});
if (!res.ok) throw new Error(`status read failed: ${res.status}`);
const { data } = await res.json();
switch (data.next_action) {
case "poll_status":
return { step: "wait", waitSeconds: data.next_poll_after_seconds ?? 2 };
case "fetch_result":
return { step: "fetch" }; // GET /v1/jobs/{id}/result
case "inspect_events":
return { step: "debug" }; // GET /v1/jobs/{id}/events
default:
throw new Error(`unknown next_action: ${data.next_action}`);
}
}What this saves you
A hand-written client usually grows a status table, then a second table for the mode that returned the job (async, sync, subscribe, webhook), then special cases for the 30 second wait budget. Every submit response uses the same envelope, so the next_action switch works for all four modes. After a sync submit that timed out, next_action is still poll_status, which is the signal to keep polling the same job id and not to submit again.
The field does not replace the booleans. Use terminal as the loop exit and result_ready before the result read, as the Jobs and results page describes. Use next_action where you need to decide which endpoint to call.
Why the failed branch points at events
A failed or canceled job has no result. GET /v1/jobs/:id/result answers 409 job_not_completed for it, so a client that goes straight to the result URL gets an error that hides the real cause. The job record carries the public error, and GET /v1/jobs/:id/events gives the public timeline: job.created, job.queued, job.started, generation.submitted, job.completed, job.failed, job.canceled, and webhook.delivery.
Two more fields help with pacing. recommended_poll_interval_seconds is the default cadence for non-terminal jobs, which is 2 seconds in the API code, and retry_after_seconds is set only when Sume has delayed the next worker attempt. For a terminal job all three pacing fields are null. That is why the snippet falls back to 2 only for a non-terminal job.
Keep the default branch strict, as above. If Sume adds a value later, a thrown error is easier to notice than a loop that quietly stops. For the wait itself, obey next_poll_after_seconds when it is a number, and fall back to exponential backoff when it is not, as the Jobs and results page recommends.
Sources
Related posts
More in Developers
- Sume MCP OAuth in six steps: from the first challenge to a call
Hosted Sume MCP OAuth takes six steps: challenge, authorize redirect, consent page, Write toggle, PKCE code exchange, then a bearer call to the MCP endpoint.
- pricing_skus has three key shapes: one Python function for the total
GET /v1/videos/models returns per-video-second-<res>, per-video-second and per-1000-video-tokens. A Decimal function totals a 30 s clip and flags token pricing.
- Sume SDK errors by status: which class for 401, 402, 403, 404, 409
The @sume-com/sdk error classes by HTTP status, which fields they carry, and how run helpers differ from generated operations that resolve instead of throwing.
- Sume TTS language field: set ja or ko, or the voice reads in English
Omit language on Sume TTS and the provider defaults to English; Sume infers ko or ja only from a Hangul- or kana-only script. Set it on non-English scripts.
Written by Sume