Authentication
The gateway uses a two-key authentication model. Every proxy request requires both a Curate-Me API key (for gateway governance) and a provider API key (for the upstream LLM provider).
Curate-Me API key
Your Curate-Me API key identifies your organization and determines which governance policies apply. API keys start with the cm_sk_ prefix (other accepted prefixes: cm_gw_, cm-gw-, cmgw_, cm_, sk_live_).
How to include your API key
The gateway checks for your API key in the following order:
| Priority | Method | Example |
|---|---|---|
| 1 | X-CM-API-Key header (recommended) | X-CM-API-Key: cm_sk_abc123def456 |
| 2 | Authorization: Bearer header | Authorization: Bearer cm_sk_abc123def456 |
| 3 | api_key query parameter | ?api_key=cm_sk_abc123def456 |
The X-CM-API-Key header is the recommended method. The Authorization: Bearer method works when the token starts with a recognized gateway key prefix. The query parameter is a fallback for EventSource/SSE connections that cannot send custom headers.
# Recommended: X-CM-API-Key header
curl https://api.curate-me.ai/v1/chat/completions \
-H "X-CM-API-Key: cm_sk_abc123def456" \
-H "X-Provider-Key: sk-openai-xxx" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'Key format
cm_sk_{random_alphanumeric_string}API keys are created in the dashboard under Settings > API Keys or via the B2B API (POST /api/v1/b2b/api-keys).
Key scopes
API keys can be scoped to limit what they can do:
| Scope | Allows |
|---|---|
gateway:write | Send proxy requests (chat completions, messages) |
gateway:read | List models, check health |
admin:write | Modify governance policies, secrets, model aliases |
admin:read | View governance policies, usage data, billing |
billing:read | View billing and cost data |
Proxy requests require the gateway:write scope. The GET /v1/models endpoint requires gateway:read.
Key rotation
When an API key is rotated, the old key enters a grace period during which it continues to work but the gateway returns deprecation headers:
| Header | Value | Description |
|---|---|---|
X-CM-Key-Deprecated | "true" | The key has been replaced |
X-CM-Grace-Remaining-Hours | Number | Hours until the old key stops working |
After the grace period expires, the old key returns a 401 error with code: "key_rotated" and the ID of the replacement key.
Provider API key
The provider API key is your API key for the upstream LLM provider (OpenAI, Anthropic, Google, Groq, Mistral, xAI, and more). The gateway needs this to forward requests to the provider.
How to include your provider key
| Priority | Method | Example |
|---|---|---|
| 1 | X-Provider-Key header (explicit) | X-Provider-Key: sk-openai-xxx |
| 2 | Authorization: Bearer header | Authorization: Bearer sk-openai-xxx |
| 3 | Stored secrets (dashboard) | Configure once, no header needed |
| 4 | Environment variable (self-hosted) | OPENAI_API_KEY, DEEPSEEK_API_KEY, etc. |
When both the gateway key and provider key are sent via Authorization: Bearer, the gateway distinguishes them by prefix — tokens starting with a gateway prefix (cm_sk_, etc.) are treated as gateway keys, and all others are treated as provider keys.
# Both keys via headers
curl https://api.curate-me.ai/v1/chat/completions \
-H "X-CM-API-Key: cm_sk_abc123" \
-H "X-Provider-Key: sk-openai-xxx" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'
# Gateway key via X-CM-API-Key, provider key via Authorization
curl https://api.curate-me.ai/v1/chat/completions \
-H "X-CM-API-Key: cm_sk_abc123" \
-H "Authorization: Bearer sk-openai-xxx" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'Stored secrets (recommended for production)
For production deployments, store provider API keys as org-scoped secrets in the dashboard under Settings > Provider Secrets. When a request arrives without an explicit provider key, the gateway retrieves the stored key from encrypted secret custody storage.
This approach keeps provider keys out of application code and client-side configurations.
# Store a provider secret via the admin API
curl -X POST https://api.curate-me.ai/gateway/admin/secrets \
-H "Authorization: Bearer $DASHBOARD_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "openai",
"secret": "sk-openai-xxx"
}'
# Now proxy requests work without explicit provider keys
curl https://api.curate-me.ai/v1/chat/completions \
-H "X-CM-API-Key: cm_sk_abc123" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "Hello"}]}'Provider-specific auth translation
Each provider uses a different authentication mechanism. The gateway handles this translation automatically:
| Provider | Upstream auth method | You always use |
|---|---|---|
| OpenAI | Authorization: Bearer {key} | X-Provider-Key or stored secret |
| Anthropic | x-api-key: {key} + anthropic-version: 2023-06-01 | X-Provider-Key or stored secret |
| Google Gemini | ?key={key} query parameter | X-Provider-Key or stored secret |
| DeepSeek | Authorization: Bearer {key} (OpenAI-compatible) | X-Provider-Key or stored secret |
Dashboard JWT fallback
The gateway also accepts dashboard B2B JWT tokens for admin endpoints. When no gateway API key is present, the gateway checks for a valid Authorization: Bearer <jwt> token. If the JWT is a valid dashboard access token, the gateway derives gateway scopes from the user’s organization role.
| Org role | Derived gateway scopes |
|---|---|
| Owner / Admin | gateway_admin, gateway:write, gateway:read, admin:write, admin:read, billing:read |
| Manager / Operator | gateway_operator, gateway:write, gateway:read, admin:read, billing:read |
| Member (default) | gateway_viewer, gateway:read, admin:read, billing:read |
This fallback allows the dashboard UI to access gateway admin endpoints directly without creating a separate API key.
Organization context
Every API key is bound to an organization. The org_id is extracted from the API key record and determines:
- Which governance policy applies (rate limits, budgets, model allowlists)
- Which daily/monthly cost counters are incremented
- Which usage records and audit logs are created
- Which provider secrets are retrieved
The org context is included in the X-Gateway-Org response header on every request.
Error responses
Missing gateway key
HTTP/1.1 401 Unauthorized{
"error": {
"message": "Missing or invalid credentials. Provide a valid gateway key via X-CM-API-Key / Authorization: Bearer <gateway-key>, or a valid dashboard access token via Authorization: Bearer <jwt>.",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key"
}
}Invalid or expired key
HTTP/1.1 401 Unauthorized{
"error": {
"message": "Invalid API key, expired, or daily rate limit exceeded",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key"
}
}Rotated key (grace period expired)
HTTP/1.1 401 Unauthorized{
"error": {
"message": "API key has been rotated. Use the new key. Replacement key ID: key_xyz789",
"type": "authentication_error",
"param": null,
"code": "key_rotated"
}
}Missing provider key
HTTP/1.1 401 Unauthorized{
"error": {
"message": "Missing provider API key. Provide your provider's API key via X-Provider-Key header, Authorization: Bearer <provider-key>, or store it via POST /gateway/admin/secrets.",
"type": "authentication_error",
"param": null,
"code": "missing_provider_key"
}
}