# Limits, quotas, and retention

## Requests per minute

Rate limits use a sliding 60-second window and are counted in three independent scopes:

| Scope | Production default | Sandbox |
|---|---|---|
| Origin (IP address), before authentication | 600 per minute | 600 per minute |
| Account | 1,000 per minute | 500 per minute |
| Credential | 300 per minute | 150 per minute |

Successful responses do not include rate limit headers. When a request exceeds a limit, the API returns `429` with a `Retry-After` header, and the response body reports the state of each scope:

```json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests.",
    "retry_after_seconds": 12,
    "blocked_by": ["key"],
    "limits": [
      { "scope": "instance", "limit": 1000, "window_seconds": 60, "remaining": 640, "retry_after_seconds": 0 },
      { "scope": "key", "limit": 300, "window_seconds": 60, "remaining": 0, "retry_after_seconds": 12 }
    ]
  }
}
```

In `limits`, the `instance` scope is the account limit and the `key` scope is the credential limit. `blocked_by` lists the scopes that rejected the request.

A `429` response executes nothing and does not count against the window. Wait `retry_after_seconds` before you retry, and spread your load over time instead of retrying in bursts. If the rate limiter is unavailable, the API returns `503 rate_limiter_unavailable`: it fails closed and never admits a request without counting it.

Higher quotas are arranged with Cademí and apply to the account. A credential can have its own limit, which is always less than or equal to the account limit. `GET /account/usage` reports consumption per window (`1h`, `24h`, `7d`, `30d`), per outcome, and per credential, along with the limits in effect.

## Request size

| Limit | Value |
|---|---|
| JSON request body | 1 MiB (`413 payload_too_large`) |
| JSON nesting depth | 16 levels (`400 json_too_deep`) |
| Items in an array | 1,000 (`422 too_many_items`) |
| Plain text | 64 KiB per field |
| Rich text | 512 KiB per field |
| Operation batch | Depends on the operation type, up to 1,000 items (`422 too_many_items`) |
| Open operations per account | 100 in `queued` or `running`, counted per environment (`operation_backlog_exceeded`) |
| File | 5 GiB, always through an upload session |
| IDs in a policy selector | 1,000 |
| Event streams | 5 per credential, 20 per account (`429 too_many_streams`) |

## Retention

What the API stores, and for how long:

| Data | Retention |
|---|---|
| Events (`GET /events`) | 30 days |
| Webhook deliveries and attempts | 30 days |
| Operations and items | 30 days from creation (`expires_at`) |
| Audit trail (`/requests`, `/audit-entries`) | 30 days |
| Idempotency receipt | 48 hours |
| Previous policy revisions | 365 days |
| Declarative configuration plans | Until the plan expires |

After the retention period, the record no longer exists: a `GET` for a pruned event returns `404`, and a cursor that points to it returns `410 cursor_expired`. If your integration needs a longer history, consume events through webhooks or `GET /events` and store them on your side.
