Rate limits
The ScreenshotBuddy API limits how fast an account may ask for captures. There are two limits rather than one, because the two kinds of answer cost us completely different things: a render is a browser and a page load, while a repeat we can serve out of your cache is a file read. This page explains what each of them is, how to read them off a response, and what happens when you go over one.
Your limits
An account may ask for up to 20 renders per minute by default. Larger plans carry a higher limit, and an account can be given a limit of its own, so treat 20 as the floor rather than the number your integration should hard-code.
Answers served out of your cache are counted apart from that, against a flat 300 per minute. That number is the same for every account whatever plan it is on, because a hit costs a file read rather than a render and there is nothing in it for a plan to scale. It is sized for the case it exists for: a page of signed URL thumbnails fetches twenty images the moment it loads, every one of them a hit. Charged to the render limit, that single page load would spend a whole minute of a plan's renders.
What spends which
A request spends a render when it asks us to render: a capture we have not made yet, or one sent with
cache=0. A repeat we answer out of the cache spends a cached answer instead. A conditional request
we can answer 304 out of your cache spends neither, so asking whether a capture you already hold
has changed stays free in every sense.
A 304 we had to render to establish is the other case. The page was loaded before we could say it
looks the same, so it spends a render exactly like the 200 it replaced, and it carries the render
pair to say so. What the answer saved you is the download.
A request we refuse before we get as far as the cache costs you nothing at all. Failing validation, asking for a mode your token was narrowed away from, or sending from an account whose email is not verified leaves both budgets exactly where they were.
Being refused for having no plan, or for having spent every credit, does spend a render. That request did ask us for render capacity, and a refusal that costs nothing to ask for is one that can be asked for in a loop.
Reading your limit off a response
Rather than keeping count yourself, read the headers the API sends back.
-
Name
X-RateLimit-LimitTypeintegerDescriptionThe size of the budget this answer was counted against: your own render limit when the page was rendered for you, the flat 300 when the answer came out of the cache. The
X-Cacheheader on the same answer says which of the two it was. -
Name
X-RateLimit-RemainingTypeintegerDescriptionWhat is left of that same budget after the request you are reading it on. When it reaches
0, hold off until the minute is over. -
Name
Retry-AfterTypeintegerDescriptionSent with a
429, and with the other statuses worth retrying. It is the number of seconds to wait before sending the request again. -
Name
X-Credits-LimitTypeintegerDescriptionThe credits your current period was granted. Sent with every answer to a request that carried a working token, a rendered file included.
-
Name
X-Credits-RemainingTypeintegerDescriptionThe credits you have left, counted after the request you are reading it on. A render that worked is already subtracted, and one that failed is already refunded, so you never have to guess which of the two happened.
-
Name
X-Credits-ResetTypeintegerDescriptionWhen your credits are granted again, as a Unix timestamp in seconds.
-
Name
X-Request-IdTypestringDescriptionIdentifies that one answer. It is on every response we send, so you can log it alongside a rendered file as well as alongside an error.
-
Name
X-Target-StatusTypeintegerDescriptionThe status the page you captured answered, which is not the status of this response: a rendered
404page comes back as a200carryingX-Target-Status: 404. On screenshot answers only, and absent when we were not told one. Absent means not known rather than200. See what the target answered.
The two sets answer two different questions. Credits are how many you have; the rate limit is how fast you may spend them. Running out of credits is not fixed by waiting a minute, and hitting the rate limit costs you nothing.
The rate limit pair is on a rendered answer and on a 429, and nowhere else. A 304
we answered out of your cache carries neither, because it was counted against neither budget, and a refusal we
answer before anything is counted has no budget to report. A 304 we had to render for does carry
the pair, because it was counted. On a 429 the pair describes the limit that actually refused the
request, next to the Retry-After that says how long it holds, so you can tell which of the two you
ran into.
The headers only arrive with an answer, so they tell you where you stand after you have spent a request. To find that out before you send one, ask the usage endpoint.
Both sets are on answers to a request that carried your bearer token, and on none of the answers to a
signed URL.
What a signed request escapes is the reporting, not the limit.
Every fetch of a signed URL is counted in full, against both budgets and against the credits of the account
whose token signed it, exactly as the same request sent with that token as a bearer would be, and it is
refused with a 429 when it goes over.
What a signed answer never carries is the numbers.
A signed URL is built to be handed out, so its answer is read by whoever you gave it to and cached by whatever
sits in front of them: your plan size and how much of it you have spent are not something an embedded image
should be telling its readers. X-Request-Id and Retry-After are on both, because
neither says anything about your account, so a signed 429 still tells whoever fetched it how long
to wait. To see where a signed URL left your account, read the headers off a bearer request or ask
the usage endpoint.
Checking your usage
A GET to /v1/usage reports where your account stands: the credits you have
left, used and were granted this period, the plan you are on, the date the period resets, and both of the
per-minute limits your account is actually held to, as requests_per_minute and
cached_requests_per_minute.
It describes your account rather than refusing on it. An account with no plan, or one that has spent every
credit, is answered 200 with the numbers that say so, where a render would come back as
402.
It carries a limit of its own of 60 requests per minute, separate from both limits above. Checking your usage therefore never spends anything you were saving for a render, so you are free to ask before every batch.
curl "https://api.screenshotbuddy.io/v1/usage" \
-H "Authorization: Bearer {token}"
{
"credits": {
"remaining": 8432,
"used": 1568,
"total": 10000
},
"plan": {
"name": "Business",
"slug": "business"
},
"period": {
"started_at": "2026-08-01T00:00:00+00:00",
"resets_at": "2026-09-01T00:00:00+00:00"
},
"rate_limit": {
"requests_per_minute": 40,
"cached_requests_per_minute": 300
}
}
plan and period are null for an account that has neither yet, so read
them before you use them. The OpenAPI document describes the response
in full.
Going over a limit
A request that goes over either limit is answered with HTTP status 429 and a
Retry-After header. Nothing is rendered and no credit is spent, so waiting the stated number of
seconds and sending the request again is all it takes. The errors page lists the other statuses worth retrying.
If you are working through a queue of pages, spread the requests out instead of firing them all at once. A short pause between calls keeps you inside the limit and finishes the batch sooner than a burst that spends most of its time being refused. Pace that against the render number: a batch made mostly of captures you already hold is answered from the cache and may run far faster than your render limit alone suggests.
Or hand the pacing to us. Every item of an asynchronous batch is counted against the same render budget, so a batch buys no extra capacity, but an item that meets the limit waits and asks again instead of being refused. Submitting one spends neither budget and runs under a small throttle of its own of 10 submissions per minute.
The playground
The playground has a limit of its own: one capture per minute, whatever your account may do through the API. It is there to try options out by hand, so the pace is set for a person rather than for a script.
Try an option before you spend a minute of your limit
Get the parameters right in the browser, then copy the finished request into your project and run it at your own pace.