> ## Documentation Index
> Fetch the complete documentation index at: https://docs.teasy.link/api-v1/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> Two windows, four headers, and how to back off when you hit one.

Two windows apply at once: a per-minute limit and a daily quota. Both are counted per account, and
both come from your plan.

## Limits by plan

| Plan   | Per minute | Per day |
| ------ | ---------- | ------- |
| Agency | 120        | 10 000  |

The API is part of Agency. On any other plan every request returns
`403 api_access_not_available_on_current_plan`, including requests that would otherwise be valid.
We check the plan on each request, so a downgrade takes effect immediately and an upgrade restores
access without reissuing keys.

## Headers

Whenever we can tell which account a request belongs to, the response carries your current
standing on both windows:

| Header                       | Meaning                     |
| ---------------------------- | --------------------------- |
| `RateLimit-Limit-Minute`     | requests allowed per minute |
| `RateLimit-Remaining-Minute` | left in the current minute  |
| `RateLimit-Limit-Day`        | requests allowed per day    |
| `RateLimit-Remaining-Day`    | left today                  |

Read your limits from these headers rather than hard-coding the numbers above — when your plan
changes, so do they.

<Warning>
  **These four headers are not on every response.** A `401`, and a `429` triggered by repeated
  rejected requests from your address, both arrive before we know whose request it is — with no
  account, there is no plan and no remaining balance to report. Read them defensively, or your
  client will find `undefined` where it expected a number. `Retry-After` is still present.
</Warning>

On `429` the response also carries `Retry-After` — **seconds until the window that fired resets**:
the end of the current minute for `rate_limit_exceeded`, midnight UTC for `daily_quota_exceeded`.

## Handling a 429

The two codes need different treatment. `rate_limit_exceeded` clears within a minute, so waiting
out `Retry-After` and repeating the call works. `daily_quota_exceeded` will not clear until
midnight UTC, so stop and surface it rather than retrying.

<CodeGroup>
  ```ts TypeScript theme={null}
  async function call(path: string, init?: RequestInit) {
    for (let attempt = 0; attempt < 5; attempt++) {
      const res = await fetch(`https://api.teasy.link/v1${path}`, {
        ...init,
        headers: { ...init?.headers, Authorization: `Bearer ${process.env.TEASY_KEY}` },
      });

      if (res.status !== 429) return res;

      const { error } = await res.json();
      if (error.code === "daily_quota_exceeded") {
        throw new Error(`Daily quota exhausted, request ${error.request_id}`);
      }

      const wait = Number(res.headers.get("Retry-After") ?? 60);
      await new Promise((resolve) => setTimeout(resolve, wait * 1000));
    }
    throw new Error("Still rate limited after 5 attempts");
  }
  ```

  ```python Python theme={null}
  import os, time, requests

  def call(path, **kwargs):
      headers = {"Authorization": f"Bearer {os.environ['TEASY_KEY']}"}
      for _ in range(5):
          res = requests.request(
              kwargs.pop("method", "GET"),
              f"https://api.teasy.link/v1{path}",
              headers={**headers, **kwargs.pop("headers", {})},
              **kwargs,
          )
          if res.status_code != 429:
              return res

          error = res.json()["error"]
          if error["code"] == "daily_quota_exceeded":
              raise RuntimeError(f"Daily quota exhausted, request {error['request_id']}")

          time.sleep(int(res.headers.get("Retry-After", 60)))
      raise RuntimeError("Still rate limited after 5 attempts")
  ```
</CodeGroup>

## What counts

* Everything that reached the quota check, **including requests that then fail validation**.
* A bulk request counts as **one**, however many items it contains.

Rejected requests — a bad key, a plan without the API — do not touch your quota. They are counted
separately, per address, which is the `429` that arrives without rate limit headers.

## One budget, several keys

The quota belongs to the account, not to the key. Three keys calling the API draw down the same
120 per minute and the same 10 000 per day, and one busy integration will slow the others.

Issuing an extra key buys you rotation without downtime, not extra capacity. If several of your
services call the API, plan their combined traffic against a single budget.

<Tip>
  Creating a hundred links through
  [`POST /v1/redirects/bulk`](/api-v1/api-v1/api-reference/redirects/create-redirects-in-bulk) costs one request;
  a hundred individual calls cost a hundred. See [Bulk operations](/api-v1/api-v1/bulk-operations).
</Tip>
