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

Steps

  1. 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: 1789156090

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

  2. 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:10Z

    x-ratelimit-reset carries 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.

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

Sign: The client sleeps for the value of x-ratelimit-reset and never wakes up.Cause: The field is a Unix timestamp, not a duration. A sleep of 1789156090 seconds is 56 years. The wait is reset minus now, computed against the clock of the machine sending the request.
Sign: x-ratelimit-remaining drops between two of your requests by more than the number you sent.Cause: The quota is attached to the credential or the source address, not to your process. On a shared egress address a CI job or a colleague spends the same bucket, which is why a test that asserts an exact decrement is flaky by construction.
Sign: The API documents a ratelimit header and the response carries x-ratelimit-limit instead.Cause: These are two different fields. The x-ratelimit family is a vendor convention with no specification, while ratelimit and ratelimit-policy come from the IETF draft and carry named parameters rather than bare numbers. A client written for one does not read the other.
Sign: Headers report plenty of quota left, yet calls keep failing.Cause: x-ratelimit-resource names the bucket the numbers belong to. Search, GraphQL and core are counted apart, so a healthy core figure says nothing about the search endpoint that is refusing you.

Thresholds

60 requests per hour

is the unauthenticated ceiling GitHub reported in x-ratelimit-limit, and it is shared by every client behind the same address.

Source: measured on api.github.com from this machine, 2026-09-11

What to check next

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.

basic3 minpublished updated Maks Verny