ky retry on POST for Sume: Idempotency-Key, 40 s timeout, v2 baseUrl

ky does not retry POST by default and times out at 10 s, but Sume sync can hold 30 s. A tested ky v2 config with a stable Idempotency-Key and no 429 retries.

4 min readSume
All posts

Configure ky with methods: ["post"] in retry, raise timeout above its 10-second default, and send an Idempotency-Key that you made before the first attempt. The ky README (read 2026-10-10) says retries default to a limit of 2, exclude POST, cover status codes 408, 413, 429, 500, 502, 503 and 504, and honor Retry-After.

Those defaults are fine for a GET. For a paid Sume submit, two defaults need changing, and ky v2 renamed an option that older Sume-and-ky snippets still use.

The tested config

I ran this against a local server that answered 503 and watched ky retry three times with the same key. Running ky 2.2.0, the old prefixUrl option threw The prefixUrl option has been renamed prefix in v2; the sample uses baseUrl, with a path that has no leading slash.

import ky from "ky";

const api = ky.create({
  baseUrl: "https://api.sume.com",
  headers: { "x-api-key": process.env.SUME_API_KEY! },
  timeout: 40_000, // ky defaults to 10 s per attempt; sync mode can hold 30 s
  retry: {
    limit: 3,
    methods: ["post"], // POST is not retried by default
    statusCodes: [408, 500, 502, 503, 504], // not 413, not 429 queue_full
  },
});

// One key per intent, created before the first attempt and reused by retries.
const idempotencyKey = `promo-8823-v1`;
const res = await api
  .post("v1/image-1.0/generate", {
    headers: { "Idempotency-Key": idempotencyKey },
    json: { prompt: "Matte black bottle on marble", mode: "async" },
  })
  .json<{ data: { request_id: string } }>();

console.log(res.data.request_id);

The three changes and why

ky defaults against Sume (ky README and Sume docs, read 2026-10-10)
ky defaultProblem with SumeChange
POST not retriedA dropped response after the job was created leaves you guessingAllow post, but only with a stable Idempotency-Key
timeout 10,000 msSync and subscribe waits can hold up to 30 sUse 40,000 ms, or use async mode and poll
Retries 413 and 429413 is payload_too_large and a queue_full 429 is not fixed by waitingDrop 413 and 429, or inspect error.code first

Why 429 needs a look before you retry

Sume has two kinds of 429. rate_limited is a request-volume limit with retry-after and ratelimit-reset headers, and retrying after the wait is correct. queue_full means the workspace already holds its accepted job capacity, so another attempt a second later fails the same way. The errors page separates them by error.code.

A ky afterResponse hook can read the body, branch on error.code, and throw for queue_full so the retry never happens. That is better than listing 429 and hoping.

Key hygiene

ky builds the retry from the same options object, so a header you set once is sent on every attempt. That is exactly what you want from the idempotency key, and exactly what you do not want if you generate the value inside a beforeRequest hook, which runs again for each retry. Build the key outside the request and pass it in.

Log the request_id from the error envelope on the final failure so support can trace the attempt on the API side.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume