Node fetch to Sume with Ideogram 4.5: branch on status 200 or 202

POST /v1/images returns 200 with data[].url or 202 with a job envelope. A Node 22 fetch sample that checks res.status, with a 40 second abort signal.

4 min readSume
All posts

POST /v1/images blocks for up to 30 seconds. If the image is ready it answers 200 with data[].url; if not, it answers 202 with the standard job envelope (data.job.id, status_url, result_url). The image docs are blunt about it: examine the status code, not the body shape. Slow settings such as 4K, high quality and large n are the likeliest to degrade to 202.

Ideogram 4.5 at quality: "high" and resolution: "2K" is a good test, because it is the kind of request that may land on either side.

Node 22 sample

The abort signal is 40 seconds, longer than the 30 second server wait, so you see the 202 instead of cutting the call yourself. The last two lines run the function against fake responses so you can try both branches with no key.

export async function submit(fetchFn = fetch) {
  const res = await fetchFn("https://api.sume.com/v1/images", {
    method: "POST",
    headers: {
      "x-api-key": process.env.SUME_API_KEY ?? "",
      "content-type": "application/json",
      "idempotency-key": "banner-autumn-v1",
    },
    body: JSON.stringify({
      model: "ideogram/ideogram-v4.5",
      prompt: "Autumn sale banner, bold serif headline",
      quality: "high",
      resolution: "2K",
    }),
    signal: AbortSignal.timeout(40_000), // the route itself waits up to 30 s
  });
  const body = await res.json();
  if (res.status === 200) return { kind: "ready", urls: body.data.map((i) => i.url) };
  if (res.status === 202) return { kind: "job", jobId: body.data.job.id, statusUrl: body.data.status_url };
  throw new Error(`HTTP ${res.status}: ${body?.error?.code ?? "unknown"}`);
}

const fake = (status, body) => async () => new Response(JSON.stringify(body), { status });
console.log(await submit(fake(200, { data: [{ url: "https://media.sume.com/a.png" }] })));
console.log(await submit(fake(202, { data: { job: { id: "job_1" }, status_url: "https://api.sume.com/v1/jobs/job_1/status" } })));

After a 202

Hand the job id to a poller. In TypeScript, waitForJob in @sume-com/sdk reads /v1/jobs/{id}/status with a 2 second floor and returns the terminal job. Without the SDK, poll status_url, honor next_poll_after_seconds, and fetch result_url only when the job is completed; fetching it earlier gives 409 job_not_completed.

Details worth keeping

  • Keep the Idempotency-Key stable across your own retries, so a retried POST does not create a second image job.
  • Send x-api-key only, never alongside Authorization; both together get 401 unauthorized.
  • Do not set stream: true, seed or output_compression for these models: they return 400 rather than being ignored.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume