Savee Developers
API

Rate limits

The two quotas every request is checked against, the headers that report them, and how to back off correctly after a 429.

Requests are metered per token against two quotas that run in parallel. The burst quota gives you headroom for short spikes; the sustained quota is your hourly allowance. Exceed either and the API returns 429 TOO_MANY_REQUESTS until that window replenishes.

Quotas

PolicyLimitWindowKeyed on
burst60 requests1 minuteToken
sustained5,000 requests1 hourToken

Both are evaluated on every request, and both must have room for the request to proceed. A steady 60 requests per minute would exhaust the sustained quota in under an hour and a half — pace against the sustained figure, not the burst one.

Search is metered on top of those, with its own far tighter pair — it's the most expensive call we serve, and the only one that reads Savee's public library rather than your own data.

PolicyLimitWindow
search-burst20 searches5 minutes
search-weekly200 searches7 days

A search costs one general request and one search request, so all four policies appear in the RateLimit header. Pace against the weekly figure: 200 searches a week is a deliberate ceiling, not an oversight, and it's what keeps the library from being enumerated a query at a time. A search refused by the 5-minute window doesn't spend weekly quota.

If your integration needs more, talk to us rather than working around it — email hey@savee.com.

Requests that arrive without a usable token — missing, malformed, or unknown — are metered by IP instead, at 30 requests per minute. That bucket exists to blunt token guessing; it isn't a quota you should ever be designing against.

RateLimit headers

Every response carries two headers describing your quota state — including error responses, and including requests that never got as far as authenticating. Read them and pace your traffic accordingly — don't guess when a window replenishes, and don't retry in a tight loop after a 429.

RateLimit-Policy: "burst";q=60;w=60, "sustained";q=5000;w=3600
RateLimit:        "burst";r=57;t=42, "sustained";r=4991;t=3401

Each header is a list with one entry per active policy. The quoted string is the policy name; the remaining tokens are parameters. Both burst and sustained always appear on token-authenticated requests, so you never have to infer one window's state from the other.

RateLimit-Policy

Declares the quota and window of each policy. These values are fixed and only change if your limits are raised.

VariableTypeDescription
namestringPolicy identifier — burst, sustained, or unauthenticated.
qintegerTotal requests granted for the window.
wintegerWindow length, in seconds.

RateLimit

Reports each policy's state after the current request has been charged.

VariableTypeDescription
namestringPolicy identifier matching an entry in RateLimit-Policy.
rintegerRequests remaining in the current window. 0 means the policy is exhausted.
tintegerSeconds until the window resets. After a 429, the exhausted policy's t serves the same purpose as Retry-After.

Legacy X-RateLimit-* headers

The older X-RateLimit-Bucket, -Limit, -Remaining and -Reset headers are still sent and aren't going anywhere. They can only describe one quota, so they describe whichever is closest to exhaustion — the one you'd need to pace against. Prefer the RateLimit headers: they show you every policy at once.

Backing off after a 429

A rate-limited request returns 429 with Retry-After, in seconds:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
RateLimit-Policy: "burst";q=60;w=60, "sustained";q=5000;w=3600
RateLimit:        "burst";r=0;t=60, "sustained";r=4940;t=3421

Sleep for Retry-After seconds, then retry. If you'd rather compute it yourself, parse the RateLimit header, find every policy with r=0, and sleep until the slowest of those windows resets — the largest t among the exhausted policies. Waiting against a policy that still has requests left will retry too soon and trip the limit again.

A rejected request is only charged to the policy that rejected it. In the example above the burst window is spent while sustained still reports 4,940 — being rate-limited doesn't burn your hourly budget.

If you're retrying repeatedly, add jitter. A fleet of clients that all wake at the same instant re-exhausts the window together.

Staying under the limit

  • Ask for bigger pages instead of more requests. limit=100 costs one request; four pages of 25 cost four. The sustained quota counts requests, not saves.
  • Cache on your side. Responses carry Cache-Control: private, no-cache so shared caches don't hold your data, but nothing stops you keeping your own.
  • Poll on a sensible interval. Saves don't arrive every second, and a once-a-minute poll costs 60 requests an hour against a 5,000 budget — a once-a-second poll cannot fit at all.
  • Back off on failure, not just on 429. Retrying a 500 in a tight loop spends the same quota as retrying a 429.

MCP

The MCP server is metered the same way — the same two quotas and the same RateLimit headers, keyed on the OAuth access token instead of an API token. Exceeding either returns 429 with Retry-After and a JSON-RPC error carrying retryAfterSeconds. Most MCP clients handle that for you.

On this page