How to check rate limit headers
Read the quota off one ordinary response: curl -s https://api.github.com/repos/curl/curl -D - -o /dev/null | grep -i '^x-ratelimit'. The reply names the ceiling, what is left of it and the epoch second the counter resets. Nothing has to fail first, so the whole policy costs a single GET.
Why check this
Quota headers are the only part of a rate limit policy an API states at runtime. Read them before a client ships against a third-party API, and again after a gateway or CDN change, because the layer that enforces the quota is rarely the service that documents it. Skip the check and a nightly sync written for 900 calls meets a 60 call ceiling at 03:00, fails half way through, and leaves a partial import behind.
Prerequisites
- curl 7.0 or later. Any build works, HTTP/2 is not needed. See the curl manual.
- One endpoint of the API under test, plus the credentials the client normally sends. Quota is counted per credential, so an anonymous read and a token show different ceilings on the same URL.
- The RateLimit header fields draft for the standard field names, and the GitHub REST rate limit reference for the
x-ratelimit-*family used below.
Steps
- Step 1.
Read the quota fields off one plain GET.
curl -s 'https://api.github.com/repos/curl/curl' -D - -o /dev/null | grep -i '^x-ratelimit'x-ratelimit-limit: 60 x-ratelimit-remaining: 22 x-ratelimit-used: 38 x-ratelimit-resource: core x-ratelimit-reset: 1789156090Five fields, one policy. The ceiling is 60 calls an hour, 38 are spent, 22 are left, the numbers describe the bucket named
core, and the counter returns to 60 at epoch second 1789156090. - Step 2.
Convert the reset value into a time you can line up against a log.
date -u -d @1789156090 '+%Y-%m-%d %H:%M:%SZ'2026-09-11 19:48:10Zx-ratelimit-resetcarries an absolute Unix second on the server clock. The seconds a client has to wait are that number minus the current time, and the difference is the only figure a backoff should use. - Step 3.
Ask for the full picture when the API publishes a quota endpoint of its own.
curl -s 'https://api.github.com/rate_limit' | head -14{ "resources": { "code_search": { "limit": 60, "remaining": 21, "reset": 1789156090, "used": 39 }, "core": { "limit": 60, "remaining": 21, "reset": 1789156090, "used": 39 },Headers on a response describe the one bucket that request belonged to. A quota endpoint lists every bucket at once, which is how you learn that search and core are counted apart before a client mixes them.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| x-ratelimit-limit: 60 | The ceiling for the current window | Compare it with the call volume your client plans, not with the volume it has today. |
| x-ratelimit-remaining: 22 | Calls left before the next request is refused | Alert on this falling below one window of normal traffic. |
| x-ratelimit-reset: 1789156090 | An absolute epoch second, not a countdown | Subtract the current time. Sleeping for the raw number parks the client for 56 years. |
| x-ratelimit-resource: core | Which bucket the four numbers describe | Read the quota again on the endpoint that actually failed. |
| ratelimit: "default";r=50;t=30 | The IETF draft field, not the vendor family | Parse r for what is left and t for the seconds the window still has to run. |
| No quota headers at all | The limiter is silent, absent, or behind a proxy that strips it | Drive the endpoint to 429 and read the refusal instead. |
Common mistakes
Thresholds
is the unauthenticated ceiling GitHub reported in x-ratelimit-limit, and it is shared by every client behind the same address.
What to check next
- How to test API rate limiting: the headers state a policy, this check proves the server enforces it.
- How to test retry-after header: the field the API sends once the quota is gone.
- How to check HTTP response headers with curl: the general technique these three commands are one case of.
- How to check HTTP status code: confirm the refusal arrives as 429 and not as 403 or 500.
- How to test CORS with curl: a browser client cannot read quota headers unless the API exposes them.
FAQ
How to check the rate limit of an API that documents none?
Send one request and print every response header, then look for any field holding a bare number beside a word like limit, remaining, quota or reset. If nothing turns up, the limit still exists. You find it by driving the endpoint until it refuses.
What does x-ratelimit-remaining mean?
Calls left in the current window for the bucket named in x-ratelimit-resource, counted for your credential or address. It is the value after the request that carried it, so a remaining of 0 means the next call is refused, not this one.
Is x-ratelimit-* a standard?
No. It is a convention copied between vendors, and the spellings differ. The IETF draft defines ratelimit and ratelimit-policy with named parameters instead. Test whichever field your API sends, and read both when it sends both.
Should a test assert an exact value of x-ratelimit-remaining?
Only against an API you control. On a shared address the counter moves for traffic you did not send, so assert that the field exists, parses as an integer, and decreases across your own calls.
Verified
Verified by Maks Vernycurl 8.21.0
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
basic3 minpublished updated Maks Verny