Authentication
BVE Gateway uses two separate authentication schemes — one for public API access and one for admin operations.
Public API keys
Section titled “Public API keys”Public API keys authenticate requests to /v1/* endpoints.
Format: sk-bve- followed by 64 lowercase hex characters.
Example: sk-bve-a1b2c3d4e5f6... (71 characters total)
Keys are provisioned by an admin via POST /admin/api-keys and are only shown once at creation time. They are never stored in plain text — only sha256(key + pepper) is stored in D1.
How to pass your key
Section titled “How to pass your key”Use the Authorization header with the Bearer scheme:
Authorization: Bearer sk-bve-YOUR_KEYAuth errors
Section titled “Auth errors”| Code | Status | Meaning |
|---|---|---|
missing_api_key | 401 | No Authorization header |
invalid_api_key | 401 | Header is not Bearer, key lacks sk-bve- prefix, or key not found in DB |
api_key_expired | 401 | Key exists but its expires_at date is in the past |
api_key_suspended | 403 | Key exists but is suspended |
api_key_revoked | 403 | Key has been revoked |
All error responses use the OpenAI error format.
Key statuses
Section titled “Key statuses”| Status | Can make requests |
|---|---|
active | Yes (unless expires_at is in the past — see below) |
suspended | No (403 api_key_suspended) |
revoked | No (403 api_key_revoked) |
expired | No (401 api_key_expired) — status stays active; expiry is determined by expires_at |
Keys with expires_at in the past return 401 api_key_expired even though their status field is still active. Reactivate by PATCHing expires_at to a future date or null via PATCH /admin/api-keys/:id.
Admin key
Section titled “Admin key”Admin routes (/admin/*) require the ADMIN_API_KEY secret set in the Worker environment.
Format: Any string — set by the operator.
Convention: Use a long random string (not the sk-bve- format).
Example placeholder: admin_bve_YOUR_ADMIN_KEY
Pass it the same way as a public key:
Authorization: Bearer admin_bve_YOUR_ADMIN_KEYThe admin auth middleware also accepts the token without the Bearer prefix, but using the full Bearer scheme is recommended.
BVE Gateway allows cross-origin requests from any origin (*).
Allowed request headers:
| Header | Purpose |
|---|---|
Authorization | API key (Bearer sk-bve-…) |
Content-Type | Request body type |
X-Request-Id | Client-provided request ID |
OpenAI-Beta | OpenAI beta feature flags (e.g. assistants=v2) |
Anthropic-Version | Anthropic API version (e.g. 2023-06-01) |
Anthropic-Beta | Anthropic beta feature flags (e.g. interleaved-thinking-2025-05-14) |
x-api-key | Alternative API key header accepted by the Anthropic SDK in browser contexts — the gateway maps this to Authorization: Bearer and strips it before forwarding to Fuelix |
Exposed response headers (readable by browser JS):
| Header | Description |
|---|---|
X-Request-Id | Gateway request UUID |
X-BVE-Latency | Total gateway latency in ms |
X-BVE-Model | Model ID resolved for the request (from response JSON when buffered, from request body for streaming) |
X-BVE-Key-Name | Operator-assigned display name of the API key that authenticated the request (e.g. "ci-bot", "production-app"). Absent on 401/403 error responses — only emitted after successful authentication |
X-BVE-Cache | Model list cache status: HIT, STALE, or MISS (only on GET /v1/models) — see Models — Model list caching |
X-Quota-Allowed, X-Quota-Available, X-Quota-Reset | Fuelix quota headers (forwarded when present) |
X-RateLimit-Limit-Requests, X-RateLimit-Remaining-Requests, X-RateLimit-Reset-Requests | Per-key RPM limit, remaining capacity, and reset time |
X-RateLimit-Limit-Day, X-RateLimit-Remaining-Day, X-RateLimit-Reset-Day | Per-key RPD limit, remaining capacity, and reset time |
X-RateLimit-Limit-Month, X-RateLimit-Remaining-Month, X-RateLimit-Reset-Month | Monthly request limit, remaining requests, and reset time (only when monthly_limit is set) |
X-RateLimit-Limit-Tokens, X-RateLimit-Remaining-Tokens, X-RateLimit-Reset-Tokens | Monthly token limit, remaining tokens, and reset time (only when monthly_token_limit is set) |
Retry-After | Seconds until the rate-limit window resets (present on 429/503) |
Anthropic-RateLimit-Requests-Limit, Anthropic-RateLimit-Requests-Remaining, Anthropic-RateLimit-Requests-Reset | Anthropic Messages API request-count rate limits |
Anthropic-RateLimit-Tokens-Limit, Anthropic-RateLimit-Tokens-Remaining, Anthropic-RateLimit-Tokens-Reset | Anthropic Messages API combined token rate limits |
Anthropic-RateLimit-Input-Tokens-Limit, Anthropic-RateLimit-Input-Tokens-Remaining, Anthropic-RateLimit-Input-Tokens-Reset | Anthropic Messages API input-token rate limits (allows distinguishing prompt vs. output budget exhaustion) |
Anthropic-RateLimit-Output-Tokens-Limit, Anthropic-RateLimit-Output-Tokens-Remaining, Anthropic-RateLimit-Output-Tokens-Reset | Anthropic Messages API output-token rate limits |
X-Groq-Request-Id | Groq request ID for log correlation |
OpenAI-Processing-Ms | OpenAI server-side processing time |
X-OpenRouter-Model | Actual model selected by OpenRouter after provider routing (e.g. openai/gpt-4o) |
X-Or-Cache-Status | OpenRouter semantic cache result: HIT or MISS |
X-Or-Remaining-Tokens | Token budget remaining in the OpenRouter rate-limit window |
Preflight responses are cached for 24 hours (Access-Control-Max-Age: 86400).