# Errors & rate limits

## The error envelope

Every error uses the same shape and a correct HTTP status:

```json
{ "success": false, "error": { "code": "not_found", "message": "unknown series" } }
```

`message` is written to be shown to a person. `code` is stable and meant for your code to branch on.

## Status codes

| HTTP | `code` | When | What to do |
|---|---|---|---|
| 401 | `unauthorized` | Missing or invalid API key or session | Check the `X-API-Key` header; create a new key if it was revoked |
| 402 | `credits_exhausted` | The free plan has used this month's 500 calls | Wait for the reset on the 1st, or upgrade |
| 402 | `subscription_inactive` | Subscription cancelled, expired or `past_due` beyond the 3-day grace | Update billing in the dashboard |
| 403 | `tier_forbidden` | The feature needs a higher plan: `vintage=` needs Pro or Business, webhooks need a paid plan, or you're at the 3-active-key or per-plan webhook cap | Upgrade, or free up a slot |
| 404 | `not_found` | Unknown series id, fund, security, AMC or commodity | Check the id with `/v1/search` |
| 409 | - | Signup with an email that already has an account | Sign in instead |
| 422 | `invalid_params` | Bad parameter: malformed date, bad `dims` token, unknown `transform`, or a transform that doesn't fit the frequency | Fix the request; `message` says what's wrong |
| 429 | `rate_limited` | Per-minute or per-day limit hit | Wait the `Retry-After` seconds |

Examples:

```json
{ "success": false, "error": { "code": "invalid_params",
  "message": "unknown transform 'bogus'; choose one of 3ma, index, mom, pct_change, yoy" } }
```

```json
{ "success": false, "error": { "code": "unauthorized", "message": "missing API key" } }
```

## Rate limits

Limits apply **per account**, across all its keys. Every keyed response carries:

- `X-RateLimit-Limit`: your per-minute ceiling.
- `X-RateLimit-Remaining`: requests left right now (the lower of your minute and day budgets).

The per-minute limit is a true sliding 60-second window. The daily limit resets at midnight.

| Plan | Per minute | Per day |
|---|---|---|
| Free | 10 | 500 calls a month (resets on the 1st) |
| Developer | 60 | 10,000 |
| Pro | 300 | 100,000 |
| Business | 600 | 500,000 |

Check your live usage with [`GET /v1/usage`](https://pakdatahub.com/docs/api-usage.md).

## Handling 429

```python
import time, httpx

def get(url, key):
    while True:
        r = httpx.get(url, headers={"X-API-Key": key}, timeout=30)
        if r.status_code != 429:
            return r
        time.sleep(int(r.headers.get("Retry-After", "5")))
```

## Tips to use fewer requests

- Use `/v1/series/{id}/latest` for tickers instead of pulling full history.
- Pull a long history in one call (`limit` up to 10,000) rather than many small windows.
- Use [`/v1/funds/screener`](https://pakdatahub.com/docs/api-funds.md) for every fund in **one** request instead of one call per fund.
- Subscribe to [webhooks](https://pakdatahub.com/docs/api-webhooks.md) instead of polling for updates (paid plans).
- Cache responses: most series update daily, weekly or monthly, never intraday.

---
Source: https://pakdatahub.com/docs/errors-rate-limits - PakDataHub docs index: https://pakdatahub.com/docs/llms.txt
