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

# Rate limits

> Per-client, per-scope, fixed-window limits, the x-ratelimit-* headers, the 429 body, defaults, and retry guidance.

Financial Data API enforces rate limits per client in a fixed time window, against a ceiling set by your plan or, for enterprise and internal keys, by the scope a route requires. Every API response tells you where you stand through four response headers, and exceeding a limit returns a `429 rate_limited` error with the relevant details.

This page covers the model, which ceiling applies to your key, monthly caps, the headers, the 429 body, and how to back off and retry.

## The model

Limits are counted along three dimensions at once:

<ResponseField name="client" type="dimension">
  Your API key. Each key has its own counter.
</ResponseField>

<ResponseField name="scope" type="dimension">
  The scope the route requires (`data:read`, `ops:read`, or `admin`). Each scope has its own limit, so heavy product traffic on `data:read` does not consume your `ops:read` budget.
</ResponseField>

<ResponseField name="window" type="dimension">
  A fixed time window. The current window length is 60 seconds. Your counter resets at the start of each window.
</ResponseField>

In other words: each key gets a separate quota for each scope, refreshed every window.

## Rate limit headers

Every API response (success or error) carries four headers describing your current budget. Read them on every response and let them drive your pacing.

<ResponseField name="x-ratelimit-policy" type="string">
  A description of the policy applied to this request (the limit and window in effect for the matched scope).
</ResponseField>

<ResponseField name="x-ratelimit-limit" type="integer">
  The maximum number of requests allowed in the current window for this client and scope.
</ResponseField>

<ResponseField name="x-ratelimit-remaining" type="integer">
  The number of requests you have left in the current window. When this reaches 0, further requests return `429` until the window resets.
</ResponseField>

<ResponseField name="x-ratelimit-reset" type="string (ISO 8601)">
  The timestamp when the current window resets and `remaining` refills. Use this to schedule retries.
</ResponseField>

```http Example response headers theme={"theme":"css-variables"}
x-ratelimit-policy: 1000 per 60s (scope data:read)
x-ratelimit-limit: 1000
x-ratelimit-remaining: 987
x-ratelimit-reset: 2026-06-24T18:42:00Z
```

## The 429 response

When you exceed your limit, the request fails with HTTP `429` and the `rate_limited` code. The `details` object tells you which scope was limited, the ceiling, and the window length.

```json 429 rate_limited theme={"theme":"css-variables"}
{
  "error": {
    "code": "rate_limited",
    "message": "The Financial Data API rate limit has been exceeded.",
    "request_id": "8f1c2e90-7a4b-4c3d-9e21-2b6f0a1c4d55",
    "requestId": "8f1c2e90-7a4b-4c3d-9e21-2b6f0a1c4d55",
    "details": {
      "required_scope": "data:read",
      "limit": 1000,
      "window_seconds": 60
    }
  },
  "requestId": "8f1c2e90-7a4b-4c3d-9e21-2b6f0a1c4d55"
}
```

<ResponseField name="details.required_scope" type="string">
  The scope whose limit you exceeded.
</ResponseField>

<ResponseField name="details.limit" type="integer">
  The request ceiling for that scope in one window.
</ResponseField>

<ResponseField name="details.window_seconds" type="integer">
  The window length in seconds.
</ResponseField>

## Which limit applies to your key

There are two ceilings, and which one governs depends on the kind of key you hold. This is the most common source of confusion, so it is worth being explicit.

**Self-serve plan keys** (Free, Advance, Scale) are limited by plan. This is what applies if you signed up at app.financialdatapi.com:

| Plan    | Per-minute limit | Monthly call cap |
| ------- | ---------------- | ---------------- |
| Free    | 60 requests      | 2,500            |
| Advance | 600 requests     | 2,000,000        |
| Scale   | 2,400 requests   | 10,000,000       |

**Enterprise, internal and system keys** have no self-serve plan attached, so they fall back to the per-scope baseline instead:

| Scope       | Per-minute limit | Applies to                                                             |
| ----------- | ---------------- | ---------------------------------------------------------------------- |
| `data:read` | 1,000 requests   | Product and data routes, most of the API.                              |
| `ops:read`  | 500 requests     | Operational routes: liveness, source health, manifest.                 |
| `admin`     | 250 requests     | Sensitive admin routes. The `admin` scope also satisfies lower scopes. |

Enterprise keys have no monthly cap.

<Note>
  Both tables are correct; they describe different kinds of key. If you are on a Free, Advance or Scale plan, read the first and ignore the second. Whichever applies, the live `x-ratelimit-*` headers and the `details` block in a `429` are authoritative, so trust those over any number hard-coded in a client.
</Note>

### The /v1/ask endpoint has its own budget

`/v1/ask` runs a language model on every call, which costs far more than a data read, so it is metered separately and does not consume your data-read allowance:

| Plan    | `/v1/ask` per-minute limit |
| ------- | -------------------------- |
| Free    | 5 requests                 |
| Advance | 60 requests                |
| Scale   | 240 requests               |

### Monthly caps

Monthly caps reset at the start of each calendar month, UTC. Exceeding one also returns `429`, but with a distinct message, `Monthly request cap for your plan has been reached.`, and a `details` block carrying your `plan` and `monthly_cap`. That lets you tell a monthly exhaustion apart from a per-minute burst without guessing.

## Backoff and retry

Retry on `429` and on `5xx` (`internal_error`). Do not retry on `4xx` other than `429`: a `bad_request`, `unauthorized`, `forbidden`, or `not_found` will fail identically on retry, so fix the request instead.

For a `429`, the cleanest strategy is to wait until the window resets:

<Steps>
  <Step title="Detect">
    The response is `429`, or `x-ratelimit-remaining` has hit 0.
  </Step>

  <Step title="Read the reset time">
    Take `x-ratelimit-reset` from the response headers (or compute from `details.window_seconds`).
  </Step>

  <Step title="Wait until reset">
    Sleep until that timestamp before retrying. This avoids hammering a closed window.
  </Step>

  <Step title="Retry, then escalate">
    For `5xx`, use exponential backoff with jitter. Cap the number of attempts so a persistent failure surfaces instead of looping forever.
  </Step>
</Steps>

<CodeGroup>
  ```python Python (requests) theme={"theme":"css-variables"}
  import os
  import time
  from datetime import datetime, timezone
  import requests

  URL = "https://api.financialdatapi.com/observations"
  HEADERS = {"x-api-key": os.environ["FINANCIALDATA_API_KEY"]}
  PARAMS = {"country": "USA", "indicator": "cpi_inflation_yoy"}

  def get_with_retry(max_attempts: int = 5):
      backoff = 1.0
      for attempt in range(max_attempts):
          resp = requests.get(URL, headers=HEADERS, params=PARAMS)

          if resp.status_code == 429:
              reset = resp.headers.get("x-ratelimit-reset")
              if reset:
                  reset_at = datetime.fromisoformat(reset.replace("Z", "+00:00"))
                  wait = max(0.0, (reset_at - datetime.now(timezone.utc)).total_seconds())
              else:
                  wait = resp.json()["error"]["details"].get("window_seconds", 60)
              time.sleep(wait)
              continue

          if resp.status_code >= 500:
              time.sleep(backoff)
              backoff *= 2  # exponential backoff for 5xx
              continue

          resp.raise_for_status()
          return resp.json()

      raise RuntimeError("exhausted retries")
  ```

  ```typescript TypeScript (fetch) theme={"theme":"css-variables"}
  const URL =
    "https://api.financialdatapi.com/observations?country=USA&indicator=cpi_inflation_yoy";
  const headers = { "x-api-key": process.env.FINANCIALDATA_API_KEY! };

  async function getWithRetry(maxAttempts = 5) {
    let backoff = 1000;
    for (let attempt = 0; attempt < maxAttempts; attempt++) {
      const res = await fetch(URL, { headers });

      if (res.status === 429) {
        const reset = res.headers.get("x-ratelimit-reset");
        const waitMs = reset
          ? Math.max(0, new Date(reset).getTime() - Date.now())
          : 60_000;
        await new Promise((r) => setTimeout(r, waitMs));
        continue;
      }

      if (res.status >= 500) {
        await new Promise((r) => setTimeout(r, backoff));
        backoff *= 2; // exponential backoff for 5xx
        continue;
      }

      if (!res.ok) throw new Error(`request failed: ${res.status}`);
      return res.json();
    }
    throw new Error("exhausted retries");
  }
  ```
</CodeGroup>

<Tip>
  Stay ahead of the limit instead of reacting to it. Watch `x-ratelimit-remaining` and slow down before it hits 0. A short delay that keeps you under the ceiling beats a stall waiting out a `429`.
</Tip>

<Columns cols={2}>
  <Card title="Errors" icon="circle-exclamation" href="/concepts/errors">
    The full error envelope and code table.
  </Card>

  <Card title="Response envelope" icon="box" href="/concepts/response-envelope">
    The shared shape of every response.
  </Card>
</Columns>
