**Source:** https://heykiku.com/help/api-rate-limits

# Rate limits and error codes

Every API response carries rate-limit headers. Every error response uses a consistent envelope. This article documents both so your client can be a good citizen and degrade gracefully.

## Rate limit buckets

heykiku groups endpoints into buckets, each with its own per-tenant and per-account limit:

- `polling` — high-frequency reads (`GET /api/documents`), the most generous bucket
- `general` — other list/read endpoints (`GET /api/folders`, `/conversations`, `/access-tags`)
- `upload` — `POST /api/documents`, limited per minute
- `billing` — API key creation, update, and deletion, the strictest bucket

The tenant limit caps total workspace traffic; the account limit caps any one user/key within that workspace. We don't publish the exact ceilings — every response includes the current ceiling for the bucket it hit, so read it from the headers rather than hard-coding numbers and your client adapts if we adjust limits.

## Headers on every response

Values below are illustrative — always use what your own responses report:

```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1716123600
```

`X-RateLimit-Reset` is a Unix timestamp (UTC seconds). When you hit the ceiling, the response is a 429 with a `Retry-After` header naming the wait in seconds.

```
HTTP/1.1 429 Too Many Requests
Retry-After: 47
```

## Error envelope

Every error response uses the same base shape:

```json
{
  "error": {
    "code": "API_KEY_SCOPE_INSUFFICIENT",
    "message": "Your API key or role permissions don't grant the 'write' scope needed for this action.",
    "doc_url": "https://heykiku.com/help/errors/API_KEY_SCOPE_INSUFFICIENT"
  }
}
```

- `code` is stable. Switch on it.
- `message` is human-readable. Show it; don't parse it.
- `doc_url` is optional — authentication errors include it; most other errors don't.
- A 429 carries a `retryAfter` field (seconds) instead of `doc_url`; the same number is also in the `Retry-After` header.

## Frequently seen error codes

| Code | Status | When |
|---|---|---|
| `API_KEY_INVALID` | 401 | A Bearer token was sent but couldn't be used — malformed, revoked, expired, or outside the key's CIDR allowlist (all collapse to one code so a probe can't confirm a real key) |
| `AUTHENTICATION_REQUIRED` | 401 | No credential at all — the Authorization header is missing and there's no session |
| `API_KEY_SCOPE_INSUFFICIENT` | 403 | Key doesn't have the scope this endpoint needs (e.g. a `read` key calling a `write` route) |
| `PLAN_TIER_INSUFFICIENT` | 403 | Workspace plan doesn't include API access (Bloom required) |
| `SUBSCRIPTION_REQUIRED` | 402 | Workspace subscription is inactive or the workspace is locked |
| `MFA_REQUIRED` | 403 | Two-factor must be enabled before creating a `write`-scope key |
| `VALIDATION_ERROR` | 400 | Request body or query params failed validation |
| `RATE_LIMIT_EXCEEDED` | 429 | Bucket limit hit — see `Retry-After` and the `retryAfter` field |

The set is open-ended. Handle unknown codes by displaying `message` and logging `code` for triage.

## What 429 means in practice

When you hit a 429, wait at least `Retry-After` seconds before the next request to that bucket. The `X-RateLimit-Reset` header tells you exactly when the window rolls over — `general`, `polling`, and `billing` buckets use an hourly window; `upload` uses a per-minute window.

Production clients should:

1. Inspect `X-RateLimit-Remaining` proactively and slow down before hitting zero
2. On 429, honor `Retry-After` exactly (jittered backoff is usually overkill at this scale)
3. Separate read traffic from upload traffic — they share buckets only at the account ceiling

See [API versioning policy](https://heykiku.com/help/api-versioning) for what error `code` stability guarantees over time.