HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 36
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1748535696
X-Request-Id: req_01HV9XK...

{
  "type": "https://docs.digitalseaservice.com/errors/rate_limit_exceeded",
  "title": "Rate limit exceeded",
  "status": 429,
  "detail": "Rate limit of 600 requests exceeded. Retry after 36s.",
  "instance": "req_01HV9XK..."
}

What it means

Your client_id has burned through its per-minute or per-day budget. We return 429 until the sliding window opens back up.

Budgets

WindowDefault budget per client_id
Per minute600
Per day10,000
Both windows apply; the tighter remaining budget wins. Premium-tier partners can negotiate higher limits — talk to your DSS account contact.

Headers on every authenticated response

Every 200, 304, and 429 carries:
HeaderMeaning
X-RateLimit-LimitThe window’s budget (e.g. 600 for per-minute).
X-RateLimit-RemainingHow many requests you have left in this window.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.
429 also includes Retry-After: <seconds>.

What to do

  1. Respect Retry-After. Wait at least that long before retrying.
  2. Back off, don’t retry tight. A retry loop that ignores Retry-After will keep hitting 429 for the rest of the window.
  3. Use conditional GETs. Pair every read with the prior ETag or Last-Modified — a 304 Not Modified counts as 1 request but returns no body. See Rate limits for the full polling story.
  4. Subscribe to webhooks. They cost zero rate-limit budget. Polling every user every minute will burn your budget fast; webhooks + a conditional GET on receipt is essentially free.
import time

def with_rate_limit_backoff(call):
    while True:
        r = call()
        if r.status_code != 429:
            return r
        delay = int(r.headers.get("Retry-After", "1"))
        time.sleep(max(1, delay))