HeyGen's X-RateLimit-Scope header vs Sume's error.details.scope

HeyGen's October 2026 429s add a scope header. Sume names the bucket in error.details.scope and sends ratelimit-* headers; one parser can read both.

4 min readSume
All posts

HeyGen's developer changelog (read 2026-10-07) says a rate-limit 429 now carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and X-RateLimit-Scope alongside Retry-After. Sume answers the same question with ratelimit-limit, ratelimit-remaining, ratelimit-reset and retry-after headers, plus error.details.scope in the body, which is read or write.

If you front both vendors, write one function that returns "seconds to wait and which bucket" and keep the vendor spelling at the edge.

The two schemes side by side

Header names differ, the idea is the same. Compare only what each vendor's own page states.

Rate-limit signals on a 429, HeyGen changelog and Sume docs (read 2026-10-07)
SignalHeyGen changelogSume docs
LimitX-RateLimit-Limitratelimit-limit
RemainingX-RateLimit-Remainingratelimit-remaining
ResetX-RateLimit-Resetratelimit-reset (seconds until the window resets)
Which bucketX-RateLimit-Scopeerror.details.scope: read or write
Wait hintRetry-After, alongside the new headersretry-after seconds on 429

Why the bucket name matters on Sume

Sume keeps read and write budgets separate per API key. A read is any GET or HEAD, such as a poll of status_url; everything else, including run creation and cancellation, is a write. Reads get forty times the plan's write number, so a tight poll loop cannot starve your own submits.

A 429 therefore means different things. scope: "write" says slow your submits. scope: "read" says slow your polling. Separately, queue_full is a 429 that is not a request-rate limit at all: it means the workspace's accepted generation capacity is full and only finishing or canceling a job helps.

One parser for both

The function below takes a response status, a lowercase header dict and a parsed body, and returns how long to wait and which bucket tripped. It reads Sume's fields and HeyGen's side by side. Only the HeyGen header names above come from HeyGen's page; the scope value format is not specified there, so the code just passes it through.

def wait_for(status, headers, body):
    if status != 429:
        return None
    err = (body or {}).get("error", {})
    if err.get("code") == "queue_full":
        return {"wait": None, "bucket": "queue_full"}
    scope = (err.get("details") or {}).get("scope") or headers.get("x-ratelimit-scope")
    wait = headers.get("retry-after") or headers.get("ratelimit-reset") or headers.get("x-ratelimit-reset")
    return {"wait": float(wait) if wait else 5.0, "bucket": scope}


print(wait_for(429, {"retry-after": "7"}, {"error": {"code": "rate_limited", "details": {"scope": "read"}}}))
print(wait_for(429, {}, {"error": {"code": "queue_full"}}))

Limits you still have to read per plan

HeyGen's entry also raises enterprise limits; those are HeyGen's numbers and a different product tier. On Sume the per-minute budget is set by plan (the authentication docs list Free through Scale) and Enterprise is contract-based, so always trust ratelimit-limit on the response over any table, including the one in these docs.

A checklist for a multi-vendor client

Rate-limit handling goes wrong in the same few places. These rules hold on Sume, and they are a sensible default for any vendor whose page does not say otherwise.

Do not log API keys, signed URLs or raw media URLs next to the headers; log the request id from the error body instead, which Sume says is safe to share with support.

  • Branch on the HTTP status first, then on error.code: rate_limited and queue_full are both 429 on Sume and need different waits.
  • Use retry-after when it is present; fall back to exponential backoff when it is not.
  • Never retry a paid submit without an Idempotency-Key; with one, a retry returns the original job.
  • Count reads and writes separately in your own metrics, so a scope: read 429 is not blamed on your submit path.
  • Treat ratelimit-remaining as advice for the current response only; the budget refills when the window resets.

Sources

Related posts

More in Comparisons

All Comparisons posts

Written by Sume