Sume job status headers: cache-control no-store and x-sume-poll-after

GET /v1/jobs/:id/status is never cacheable and sends x-sume-poll-after: 2. What each header means for CDNs, browsers and a TypeScript poll loop.

4 min readSume
All posts

The job status route of the Sume API sets two response headers that are easy to miss: cache-control: no-store and x-sume-poll-after: 2. The first means no cache between you and the API may keep the response. The second is a hint about how often to ask again, in seconds. Together they decide how a tight poll loop should behave.

no-store

A status read must always reflect the current job state. With no-store, a CDN, a corporate proxy or a browser cache has no permission to replay an old processing body after the job completed. If you see a stale status through your own infrastructure, a rule that overrides cache headers is the likely cause, since the API has asked for no caching.

x-sume-poll-after

The hint is the fallback for a client that does not read the body. The body of a non-terminal status carries next_poll_after_seconds, which wins when both are present, and 2 seconds is a sensible floor for the poll interval. The CORS configuration exposes x-sume-poll-after and the ratelimit-* headers, so a browser client can read them too.

Headers on the job status route (read 2026-10-04)
HeaderValueMeaning
cache-controlno-storeNever cache a status read
x-sume-poll-after2Ask again in 2 seconds, not sooner
ratelimit-remainingvariesReads left in the window

Poll loop

The loop reads the body hint first, then the header, then falls back to 2 seconds. It stops on any terminal status, and it has a wall-clock deadline so that it cannot run forever.

const TERMINAL = new Set(["completed", "failed", "canceled"]);

export async function waitForJob(base: string, key: string, statusUrl: string, deadlineMs = 20 * 60_000) {
  const stop = Date.now() + deadlineMs;
  while (Date.now() < stop) {
    const res = await fetch(new URL(statusUrl, base), { headers: { "x-api-key": key }, cache: "no-store" });
    if (!res.ok) throw new Error(`status ${res.status}`);
    const { data } = (await res.json()) as { data: { status: string; next_poll_after_seconds?: number | null } };
    if (TERMINAL.has(data.status)) return data;
    const hint = data.next_poll_after_seconds ?? Number(res.headers.get("x-sume-poll-after") ?? 2);
    await new Promise((r) => setTimeout(r, Math.max(1, hint) * 1000));
  }
  throw new Error("deadline exceeded");
}

Why not poll faster

Polling faster does not make a job finish faster, and it spends from the read bucket. The read bucket is large, 4800 per minute even on Free, but a worker that holds 50 jobs and polls each one every 200 ms would spend 15000 reads a minute. At the 2-second hint the same 50 jobs cost 1500 reads a minute.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume