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.

5 min readSume
All posts

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.

Job states and the action fields, as of 2026-10-09 (Jobs and results docs; OpenAPI schema).
Statusnext_actionterminalresult_readyCancelable
queuedpoll_statusfalsefalseYes, before generation work starts
processingpoll_statusfalsefalseOnly if generation has not started
completedfetch_resulttruetrueNo
failedinspect_eventstruefalseNo
canceledinspect_eventstruefalseNo

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

All Developers posts

Written by Sume