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

# Rate limits and quotas

> Burst limits, cycle quotas, and error handling

Two separate mechanisms limit API usage:

1. **Rate limits** — per-minute burst protection, keyed by **API key**. Hitting one means *slow down and retry*.
2. **Cycle quotas** — workspace-level `used`/`limit` meters (unlocks, searches, messages) that reset each cycle. Hitting one means *you're done until reset or upgrade*.

## Request rate limits (stacking)

Every authenticated request consumes the **global** limit. Some routes also consume a **route-class** limit.

| Class             | Limit (per key / 60s) | Block duration | Routes                           |
| ----------------- | --------------------- | -------------- | -------------------------------- |
| **global**        | 120                   | 60s            | All `/api/v1/*`                  |
| **search**        | 10                    | 120s           | `GET /search/candidates`         |
| **sensitiveRead** | 30                    | 60s            | `GET /candidates/{talentCardId}` |
| **createJob**     | 5                     | 300s           | `POST /jobs`                     |
| **inviteToApply** | 10                    | 120s           | `POST /invite-to-apply`          |

### On rate limit exceeded

HTTP **429** with:

```json theme={null}
{
  "error_code": "rate_limit_exceeded",
  "error_description": "Rate limit exceeded for this search endpoint. Retry after 42 seconds."
}
```

The response includes a **`Retry-After`** header (seconds). Back off and retry — do not retry in a tight loop.

## Cycle quotas

Workspace-level running totals, reset each usage cycle. Defaults (production workspaces get custom grants):

| Meter        | Default limit / cycle | Consumed by                                                    | Exhausted → `error_code`    |
| ------------ | --------------------- | -------------------------------------------------------------- | --------------------------- |
| `apiUnlocks` | 10                    | `GET /candidates/{id}` on a **locked** candidate (auto-unlock) | `api_unlock_limit_reached`  |
| `searches`   | 100                   | `GET /search/candidates` (each page request)                   | `api_search_limit_reached`  |
| `messages`   | 20                    | `POST /invite-to-apply` only (not `POST /messages` follow-ups) | `api_message_limit_reached` |

Quota errors are also HTTP **429**, but retrying does not help — the meter resets at `cycleEndsOn`, or contact Cutshort for a higher grant.

## Checking usage

`GET /usage` returns every meter as a `used`/`limit` pair:

```json theme={null}
{
  "workspaceId": "…",
  "apiUnlocks": { "used": 3, "limit": 10 },
  "searches": { "used": 42, "limit": 100 },
  "messages": { "used": 5, "limit": 20 },
  "cycleEndsOn": "2026-08-15T00:00:00.000Z",
  "planExpiresOn": null
}
```

| Field           | Meaning                                                      |
| --------------- | ------------------------------------------------------------ |
| `cycleEndsOn`   | When usage meters reset                                      |
| `planExpiresOn` | When the workspace subscription ends (`null` for free plans) |

## Other 429 responses

| `error_code`                | Meaning                                                              |
| --------------------------- | -------------------------------------------------------------------- |
| `rate_limit_exceeded`       | Burst limit — retry after `Retry-After` seconds                      |
| `api_unlock_limit_reached`  | Unlock quota exhausted for the cycle                                 |
| `api_search_limit_reached`  | Search quota exhausted for the cycle                                 |
| `api_message_limit_reached` | Message quota exhausted for the cycle                                |
| `search_throttled`          | Plan-level search throttle from upstream (not the Redis burst limit) |

## Best practices

* Run searches sequentially, not in parallel
* Refine filters instead of paginating deep — every page request costs one search
* Check `apiUnlocks` before batch candidate fetches
* Cache results client-side when iterating on the same role
* Check `Retry-After` on 429 and exponential-backoff retry (burst limits only)
