# 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.

> The rate limit is separate from your credits. It caps how fast you may spend them, not how many you have.

## 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](https://screenshotbuddy.io/documentation/caching.md) 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](https://screenshotbuddy.io/documentation/signed-urls.md) 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.

| Header | Type | Description |
| --- | --- | --- |
| `X-RateLimit-Limit` | integer | The 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-Cache` header on the same answer says which of the two it was. |
| `X-RateLimit-Remaining` | integer | What 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. |
| `Retry-After` | integer | Sent with a `429`, and with the other statuses worth retrying. It is the number of seconds to wait before sending the request again. |
| `X-Credits-Limit` | integer | The credits your current period was granted. Sent with every answer to a request that carried a working token, a rendered file included. |
| `X-Credits-Remaining` | integer | The 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. |
| `X-Credits-Reset` | integer | When your credits are granted again, as a Unix timestamp in seconds. |
| `X-Request-Id` | string | Identifies 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. |
| `X-Target-Status` | integer | The status the page you captured answered, which is not the status of this response: a rendered `404` page comes back as a `200` carrying `X-Target-Status: 404`. On screenshot answers only, and absent when we were not told one. Absent means not known rather than `200`. See [what the target answered](https://screenshotbuddy.io/documentation/taking-screenshots.md). |

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 below.

Both sets are on answers to a request that carried your bearer token, and on none of the answers to a [signed URL](https://screenshotbuddy.io/documentation/signed-urls.md). 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 below.

## Checking your usage

A `GET` to `https://api.screenshotbuddy.io/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.

```bash
curl "https://api.screenshotbuddy.io/v1/usage" \
  -H "Authorization: Bearer {token}"
```

```json
{
    "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](https://api.screenshotbuddy.io/v1/openapi.json) 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](https://screenshotbuddy.io/documentation/errors.md) 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](https://screenshotbuddy.io/documentation/batch-renders.md) 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.
