Sume 429 body: parse details.scope and window_seconds in TypeScript

A Sume rate_limited 429 names the read or write bucket and gives limit, window_seconds and retry_after_seconds. Parse them in TypeScript and wait.

4 min readSume
All posts

A 429 rate_limited from the Sume API carries more than a wait time. The error envelope has a details object with limit, remaining (always 0 here), scope, window_seconds and retry_after_seconds. Read them, and your log line can say which budget was spent and how long to wait.

The fields

Fields in error.details on a rate_limited 429 (read 2026-10-04)
FieldValueUse
limitRequests allowed in the window for that bucketCompare with your plan row
remaining0Already spent
scoperead or writeWhich budget refused you
window_seconds60Length of the fixed window
retry_after_seconds1 or more, rounded upHow long to wait

Why scope is the useful one

Reads and writes have separate buckets. A 429 on read means your pollers are too tight, and shortening the submit rate will not help. A 429 on write means submits, cancellations or uploads are over the plan number. On a rate_limited response the retry-after header holds the same value as details.retry_after_seconds, so either works.

Typed parser

The function parses an error response and returns a wait in milliseconds, or null when the error is not a rate limit. It makes no network call, so it is easy to unit test.

type RateLimited = { scope: "read" | "write"; limit: number; windowSeconds: number; waitMs: number };

export function parseRateLimited(status: number, body: unknown, header?: string | null): RateLimited | null {
  const err = (body as { error?: { code?: string; details?: Record<string, unknown> } })?.error;
  if (status !== 429 || err?.code !== "rate_limited") return null;
  const d = err.details ?? {};
  const seconds = Number(d.retry_after_seconds ?? header ?? 1);
  return {
    scope: d.scope === "read" ? "read" : "write",
    limit: Number(d.limit ?? 0),
    windowSeconds: Number(d.window_seconds ?? 60),
    waitMs: Math.max(1, seconds) * 1000,
  };
}

const sample = { error: { code: "rate_limited", details: { limit: 120, remaining: 0, scope: "write", window_seconds: 60, retry_after_seconds: 17 } } };
console.log(parseRateLimited(429, sample)); // waitMs: 17000

Do not confuse it

Other 429 codes use a different shape. queue_full means the workspace generation queue is full and rate_limit_unavailable means the limiter itself is degraded. The parser returns null for both, and the caller handles them separately. The code check is what keeps them apart.

Add some jitter

Many workers that sleep for the same retry_after_seconds wake together at the start of the next window. Add up to 20 percent random delay so they do not arrive as one burst.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume