Authentication
Authenticate requests with API keys — securely and at scale.
On this page
Caedral authenticates API requests with Bearer API keys. Keys are issued per account from the dashboard and should be treated as secrets.
Header format
Authorization: Bearer cd_live_...How keys are stored
Caedral never stores your plaintext key. At creation time the gateway computes an HMAC-SHA256 lookup hash over the raw key using a server-side pepper (API_KEY_LOOKUP_PEPPER) and stores that hash — prefixed v2: — as the only credential material. Authentication hashes the presented key the same way and looks the result up in one indexed query, so there is no plaintext to leak from the database and no slow password-style comparison in the request path.
- The pepper is required in production: without it the service fails fast instead of deriving one from another secret
- Only the first 16 characters of the key are kept separately as a display prefix for the dashboard list
- Because the plaintext is never persisted, a lost key cannot be recovered — generate a new one
Key lifecycle
- Create keys at /dashboard/api-keys — full secret shown once
- Revoke keys immediately if leaked; requests then return 401
- Use separate keys for staging and production n8n instances
- Prefer environment variables or n8n Credentials over hardcoded values
Public vs authenticated routes
| Route | Auth |
|---|---|
| GET /health | Public |
| GET /v1/models and GET /v1/models/:id | Public (Bearer optional) |
| GET /v1/status and GET /v1/status/models | Public |
| POST /v1/chat/completions | Bearer required |
| POST /v1/embeddings | Bearer required |
| POST /v1/rerank | Bearer required |
| GET /v1/usage | Bearer required |
Rate limits and concurrency
| Limit | Value | Scope |
|---|---|---|
| Free-tier catalog models | 60 requests/minute | Per API key |
| Paid models | 100 requests/minute | Per API key |
| Concurrent streams | 10 simultaneous SSE streams | Per API key (configurable by Caedral) |
| Unauthenticated catalog/status GETs | 120 requests/minute | Per client IP |
Requests billed against a team seat also count toward one shared per-team bucket, so multiple seats cannot multiply the team's effective limit. Exceeding any limit returns HTTP 429 rate_limit_exceeded; exceeding the concurrent-stream cap returns 429 with a message naming the cap.
Failure modes
| HTTP | type | When |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, or revoked key |
| 402 | insufficient_balance | Included pools cannot cover the request |
| 429 | rate_limit_exceeded | Per-key RPM exceeded or too many concurrent streams |