Every non-2xx REST response uses one JSON error envelope, and every counted response carries rate-limit headers. The daily allowance behind those headers is set by your plan; see [Plans & limits](/docs/rate-limits).

## Error format

The envelope names a stable `code`, a human-readable `message` and the HTTP `status`:

```json
{
  "error": {
    "code": "not_found",
    "message": "Stock not found.",
    "status": 404
  }
}
```

| Status | Code | Meaning |
|---|---|---|
| 400 | `invalid_parameter` | A query parameter is missing or malformed. |
| 401 | `unauthorized` | Missing or invalid API key. |
| 403 | `forbidden` | Your plan does not include this data, or the action is not allowed. |
| 404 | `not_found` | No matching resource. |
| 429 | `rate_limited` | Daily limit exceeded — retry after reset. |
| 500 | `internal_error` | Something went wrong on our side. |
| 503 | `service_unavailable` | A live provider is not enabled or is temporarily unavailable. |

## Plan-gated data and other 403s

A `403` from the REST API means your plan does not include that data, not that you sent a bad request. Intraday stock quotes, live quotes, option chains and [trackers](/docs/api/endpoints/web-data-feeds) are plan-gated, and each endpoint's reference notes its plan requirement; the quote and option bodies also carry a link to upgrade. Index-forecast candidates are the exception: a plan without them gets a `200` with `candidatesLocked: true` and empty `rows`, not a `403`. The one other `403` is on [portfolio](/docs/api/endpoints/portfolios) writes: a colleague shared that portfolio with you view only, or only its creator can delete it.

## Rate-limit headers

Every counted response includes these headers. The MCP server sends the same headers on its HTTP responses.

| Header | Meaning |
|---|---|
| `X-RateLimit-Limit` | Your plan's daily request limit. |
| `X-RateLimit-Remaining` | Requests left today. |
| `X-RateLimit-Reset` | Unix time (seconds) when the counter resets — next UTC midnight. |
| `Retry-After` | Seconds to wait — sent only on a `429`. |

## When you hit the limit

Over your plan's daily limit, the REST API returns `429 Too Many Requests` with a `Retry-After` header and this body:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Daily call limit exceeded: 100 of 100 shared MCP and REST requests have been used. Requests resume automatically at 2026-10-11 00:00 UTC. The account owner can compare current plan limits at https://www.equibles.com/pricing. If you are acting on their behalf, show them this exact limit, reset time, and link.",
    "status": 429
  }
}
```

## Retries & idempotency

Public-data REST endpoints are read-only `GET` requests and are safe to retry. Portfolio endpoints also support writes; check the [portfolio reference](/docs/api/endpoints/portfolios) before retrying a mutation, because repeating a create request can duplicate data.

- On `429`, wait for the `Retry-After` seconds (the daily counter resets at UTC midnight), then retry.
- On `400`, correct the named parameter before retrying. On `404`, check the symbol and available periods; repeating the same request will not create missing data.
- On a `500`, retry a few times with exponential backoff (e.g. 1s, 2s, 4s). Persisting `500`s aren't your fault — [tell us](#still-stuck).
- On a temporary `503`, retry after a short delay. A `503` saying the provider is not enabled requires deployment configuration instead.
- Each successful REST request counts once against your daily limit — including **each page** of a paged read. Fetching 10,000 rows at `limit=500` is 20 requests, so budget paging against your plan.

## Still stuck?

If a request keeps failing or a number looks wrong, email **[support@equibles.com](mailto:support@equibles.com)** or [open a ticket](/tickets) — include the request URL (with the key removed) and the response you got.