ky does not retry POST by default: Sume submit with a key
ky retries GET but not POST, and it also retries 413. How to configure it so a Sume submit retries safely and a too-large body fails fast.

Short answer
ky retries at most 2 times by default and does not include POST in its default retry methods, per the ky README. To retry a Sume submit you must add post to methods and send an Idempotency-Key. You should also drop 413 from the retried status codes, because a body that was too large once is still too large.
ky defaults versus what Sume needs
ky's defaults are tuned for idempotent calls, which is why POST is left out. That is a sensible default and matches the Sume SDK, which retries POST only when an Idempotency-Key is present. The risk is in the status list: ky also retries 413.
Sume answers 413 with payload_too_large when the body exceeds the API limit. Resending the same body repeats the failure and delays the real error.
| Option | Default |
|---|---|
| limit | 2 |
| methods | get, put, head, delete, options, trace, query |
| statusCodes | 408, 413, 429, 500, 502, 503, 504 |
| afterStatusCodes (honor Retry-After) | 413, 429, 503 |
| Delay | 0.3 x 2^(attempt-1) x 1000 ms, backoffLimit Infinity |
Configuration
Create one client, list the methods you want, and leave 413 out. Generate the key once per logical submit, outside the retry. ky honors the Retry-After header on 429 and 503, which lines up with the retry-after header Sume sends on rate_limited and queue_full.
Note that backoffLimit is Infinity by default, so set a cap if you raise limit. The Sume SDK caps its own computed backoff at 8 seconds and, when a retry-after header is present, waits that long instead (up to 60 seconds).
import ky from "ky";
const sume = ky.create({
headers: { "x-api-key": process.env.SUME_API_KEY! },
retry: {
limit: 3,
methods: ["get", "head", "post"],
statusCodes: [408, 429, 500, 502, 503, 504],
backoffLimit: 8000,
},
});
const key = crypto.randomUUID();
const job = await sume
.post("https://api.sume.com/v1/images", {
json: { model: "sume/auto", prompt: "A matte black bottle on marble", mode: "async" },
headers: { "Idempotency-Key": key },
})
.json();
console.log(job);
What the retry will and will not fix
A retry with the same key and the same body returns the original job instead of billing twice. A different body under the same key on a Format run is 409 idempotency_conflict, which ky does not retry because 409 is not in the list. Concurrent duplicates give 409 idempotency_key_in_use, which is retryable; add 409 only if you inspect the error code first.
Do not use the retry to wait for a job. Retries cover the submit. After the 202, poll the status endpoint and honor next_poll_after_seconds. See the errors page for the status codes.
Polling with the same client
The same client can poll. GET is in the default method list, so a transient 503 or a dropped connection on a status read is retried without extra setup. Keep the loop itself in your code: read next_poll_after_seconds from each status reply and sleep for that long, and stop as soon as terminal is true. ky's delay applies between retries of one request, not between polls, so it is no substitute for the loop.
Statuses are queued, processing, completed, failed and canceled. Only completed has a result. Asking for /result on any other state gives 409 job_not_completed, so read the failure from the job record instead.
Timeouts and aborts
Retries multiply wall time. Three retries with a growing delay can take longer than a single attempt by tens of seconds, so set a total deadline in your own code if a user is waiting. If you abort, the job is not canceled; the submit may already have been accepted. Resubmitting with the same key returns the original job, which is why the key must live outside the retry and, ideally, in your own storage next to the intent it represents.
Sources
Related posts
More in Developers
- LangChain4j StreamableHttpMcpTransport: protocol detection for Sume
LangChain4j probes the MCP protocol on connect, which costs one round trip. Pin protocolVersion and set timeouts before pointing it at Sume's hosted MCP server.
- last_frame without first_frame returns 400: kling-3 and Seedance fix
A frame_images list with only a last_frame is rejected with 400 unsupported_capability. Send a first_frame too; Kling 3.0 uses start and end frames only.
- Latin American Spanish text to speech: es or es-MX on Sume?
Sume's TTS language is a free string and its voice library tags voices with plain es. What that means for Mexican, Argentine or Spain Spanish, and how to test.
- FLUX 3 style boxes for Sume: draw a layout reference sheet in Python
Sume has no bounding-box input. Draw numbered boxes on a blank sheet from a 0-1000 grid, pass it as a reference, and name each box in the prompt.
Written by Sume