API Keys
Create API key
Section titled “Create API key”POST https://api.bve.me/admin/api-keysAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYContent-Type: application/jsonRequest body
Section titled “Request body”{ "name": "my-client", "rpm_limit": 60, "rpd_limit": 10000, "monthly_limit": null, "monthly_token_limit": null, "allowed_models": null}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | — | Human-readable label (1–255 chars) |
rpm_limit | integer | No | 60 | Max requests per minute |
rpd_limit | integer | No | 10000 | Max requests per day |
monthly_limit | integer | null | No | null | Max requests per month (null = unlimited) |
monthly_token_limit | integer | null | No | null | Max tokens per month (null = unlimited) |
allowed_models | string[] | null | No | null | Restrict to specific exact live Fuelix model IDs. New admin writes reject wildcard prefixes. null or [] removes the per-key restriction, so the key inherits the gateway’s global policy and live model availability. Older keys may still return legacy wildcard prefixes until normalized. |
expires_at | ISO 8601 datetime | null | No | null | Key expiry date/time. After this timestamp, all requests with the key return 401 api_key_expired. Set to null for no expiry. |
Response (201)
Section titled “Response (201)”{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "my-client", "key": "sk-bve-a1b2c3d4...", "status": "active", "rpm_limit": 60, "rpd_limit": 10000, "monthly_limit": null, "monthly_token_limit": null, "allowed_models": null, "expires_at": null, "is_expired": false, "created_at": "2026-05-21T12:00:00.000Z", "updated_at": "2026-05-21T12:00:00.000Z", "revoked_at": null, "last_used_at": null}cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "my-client", "rpm_limit": 60, "rpd_limit": 10000 }'Create a rate-limited key
Section titled “Create a rate-limited key”curl -X POST https://api.bve.me/admin/api-keys \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "trial-key", "rpm_limit": 10, "rpd_limit": 100, "monthly_limit": 500 }'Create a model-restricted key
Section titled “Create a model-restricted key”curl -X POST https://api.bve.me/admin/api-keys \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "embeddings-only", "allowed_models": ["text-embedding-3-small", "text-embedding-3-large"] }'Create a key with an expiry date
Section titled “Create a key with an expiry date”curl -X POST https://api.bve.me/admin/api-keys \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "trial-30-days", "expires_at": "2026-06-25T00:00:00.000Z" }'After the expires_at timestamp, all requests using this key return 401 api_key_expired. To remove the expiry, PATCH the key with "expires_at": null.
Get API key
Section titled “Get API key”GET https://api.bve.me/admin/api-keys/:idAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYReturns a single API key by its UUID.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the API key |
Response (200)
Section titled “Response (200)”{ "id": "550e8400-e29b-41d4-a716-446655440000", "name": "my-client", "status": "active", "rpm_limit": 60, "rpd_limit": 10000, "monthly_limit": null, "monthly_token_limit": null, "allowed_models": null, "expires_at": null, "is_expired": false, "created_at": "2026-05-21T12:00:00.000Z", "updated_at": "2026-05-21T12:00:00.000Z", "revoked_at": null, "last_used_at": "2026-05-21T14:37:00.000Z"}| Field | Type | Description |
|---|---|---|
id | string | UUID of the API key |
name | string | Human-readable label |
status | string | active, suspended, or revoked |
rpm_limit | integer | Max requests per minute |
rpd_limit | integer | Max requests per day |
monthly_limit | integer | null | Monthly request cap; null = unlimited |
monthly_token_limit | integer | null | Monthly token cap; null = unlimited |
allowed_models | string[] | null | Stored model allowlist; null or [] = no per-key restriction (global policy and live availability still apply). Older keys may still return legacy wildcard prefixes until normalized, but new admin writes reject them and require exact live Fuelix IDs. |
expires_at | string | null | ISO 8601 expiry timestamp; null = no expiry |
is_expired | boolean | true when expires_at is set and in the past; false otherwise. Lets clients detect expired-but-status=active keys without comparing date strings. |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 timestamp of last update |
revoked_at | string | null | ISO 8601 revocation timestamp; null if not revoked |
last_used_at | string | null | ISO 8601 timestamp of the last accepted request; null if never used |
Response (404)
Section titled “Response (404)”{ "error": { "message": "API key not found", "type": "invalid_request_error", "code": "not_found" }}cURL example
Section titled “cURL example”curl https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Update API key
Section titled “Update API key”PATCH https://api.bve.me/admin/api-keys/:idAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYContent-Type: application/jsonPartially update key metadata or limits. All fields are optional — only provided fields are changed.
Request body
Section titled “Request body”{ "name": "new-name", "rpm_limit": 120, "rpd_limit": 20000, "monthly_limit": 5000, "monthly_token_limit": null, "allowed_models": ["gpt-4o", "gpt-4o-mini"]}| Field | Type | Description |
|---|---|---|
name | string | New display name (1–255 chars) |
rpm_limit | integer | New requests-per-minute limit |
rpd_limit | integer | New requests-per-day limit |
monthly_limit | integer | null | Monthly request cap (null = unlimited) |
monthly_token_limit | integer | null | Monthly token cap (null = unlimited) |
allowed_models | string[] | null | Stored model allowlist (null or [] = no per-key restriction; global policy and live availability still apply). Older keys may still return legacy wildcard prefixes until normalized, but new admin writes reject them and require exact live Fuelix IDs. |
expires_at | ISO 8601 datetime | null | Expiry date/time; null removes the expiry |
Response (200)
Section titled “Response (200)”Returns the full updated key object (same shape as Get API key, without the raw key value).
Response (404)
Section titled “Response (404)”{ "error": { "message": "API key not found", "type": "invalid_request_error", "code": "not_found" }}cURL examples
Section titled “cURL examples”Rename and raise rate limit:
curl -X PATCH https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "production-client", "rpm_limit": 120 }'Restrict to specific models:
curl -X PATCH https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "allowed_models": ["gpt-4o", "gpt-4o-mini"] }'Remove the monthly limit:
curl -X PATCH https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000 \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "monthly_limit": null }'List API keys
Section titled “List API keys”GET https://api.bve.me/admin/api-keysAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYReturns all API keys by default. Use ?status= to filter by lifecycle state, ?name= to search by display name or UUID, ?since= / ?until= to filter by creation date, ?sort_by= / ?sort_dir= to control order, and ?limit= / ?offset= for pagination.
Query parameters
Section titled “Query parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
status | string | No | — | Filter by key status: active, suspended, or revoked. Omit to return all statuses. Mutually exclusive with ?expired=true. |
name | string | No | — | Case-insensitive substring match on the key’s display name or UUID. Operators can paste a partial UUID from a request log to find the associated key. Combinable with all other parameters. |
expired | boolean | No | — | true to return only keys whose expires_at date has passed. Mutually exclusive with ?status=. |
since | string | No | — | ISO 8601 timestamp. Return only keys created at or after this time. Invalid timestamps return 400 validation_error. |
until | string | No | — | ISO 8601 timestamp. Return only keys created at or before this time. since must not be after until. |
sort_by | string | No | created_at | Column to sort by. One of: name, created_at, updated_at, last_used_at. Any other value returns 400 validation_error. |
sort_dir | string | No | desc | Sort direction: asc or desc. Any other value returns 400 validation_error. |
limit | integer | No | 100 | Maximum number of keys to return (1–500). |
offset | integer | No | 0 | Number of keys to skip. Use with ?limit= for cursor-free pagination. |
All parameters may be combined: ?status=active&name=prod&since=2026-05-01T00:00:00Z&sort_by=last_used_at&limit=10 returns up to 10 active keys whose name contains "prod", created on or after 2026-05-01, sorted by most recently used.
Response (200)
Section titled “Response (200)”{ "total": 1, "has_more": false, "keys": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "my-client", "status": "active", "rpm_limit": 60, "rpd_limit": 10000, "monthly_limit": null, "monthly_token_limit": null, "allowed_models": null, "expires_at": null, "is_expired": false, "created_at": "2026-05-21T12:00:00.000Z", "updated_at": "2026-05-21T12:00:00.000Z", "revoked_at": null, "last_used_at": "2026-05-21T14:37:00.000Z" } ]}| Field | Type | Description |
|---|---|---|
total | integer | Total number of keys matching the current filters (ignores limit/offset). Use for pagination UI. |
has_more | boolean | true when additional pages exist (offset + len(keys) < total). Shorthand for detecting additional pages without computing pagination math. |
keys | array | Array of key objects. Each key has the same fields as Get API key, without the raw key value. |
The raw key (sk-bve-...) is not included in list responses.
cURL examples
Section titled “cURL examples”List all keys:
curl https://api.bve.me/admin/api-keys \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"List only active keys:
curl "https://api.bve.me/admin/api-keys?status=active" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"List only suspended keys:
curl "https://api.bve.me/admin/api-keys?status=suspended" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Search by name (substring match):
curl "https://api.bve.me/admin/api-keys?name=production" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Find a key by UUID prefix (paste from a request log):
curl "https://api.bve.me/admin/api-keys?name=550e8400" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Combine name/UUID search with status filter:
curl "https://api.bve.me/admin/api-keys?status=active&name=prod" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Paginate through all keys (100 at a time):
# Page 1curl "https://api.bve.me/admin/api-keys?limit=100&offset=0" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"
# Page 2curl "https://api.bve.me/admin/api-keys?limit=100&offset=100" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Filter by creation date range:
# Keys created in May 2026curl "https://api.bve.me/admin/api-keys?since=2026-05-01T00:00:00Z&until=2026-05-31T23:59:59Z" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"List only expired keys:
# Keys whose expires_at has passed (cannot be combined with ?status=)curl "https://api.bve.me/admin/api-keys?expired=true" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Sort by most recently used (stale keys first with asc):
curl "https://api.bve.me/admin/api-keys?sort_by=last_used_at&sort_dir=asc" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Most recently updated keys:
curl "https://api.bve.me/admin/api-keys?sort_by=updated_at&sort_dir=desc" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Active keys sorted alphabetically by name:
curl "https://api.bve.me/admin/api-keys?status=active&sort_by=name&sort_dir=asc" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Suspend API key
Section titled “Suspend API key”POST https://api.bve.me/admin/api-keys/:id/suspendAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYSuspends an active key. Suspended keys are immediately rejected with 403 api_key_suspended on any request. Unlike revocation, suspension is reversible.
Only active keys can be suspended. Calling this on an already-suspended or revoked key returns 404.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the active API key to suspend |
Response (200)
Section titled “Response (200)”{ "id": "550e8400-e29b-41d4-a716-446655440000", "status": "suspended", "updated_at": "2026-05-21T12:30:00.000Z"}cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/suspend \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Unsuspend API key
Section titled “Unsuspend API key”POST https://api.bve.me/admin/api-keys/:id/unsuspendAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYRestores a suspended key to active status. The key can make requests again immediately.
Only suspended keys can be unsuspended. Calling this on an active or revoked key returns 404.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the suspended API key to reactivate |
Response (200)
Section titled “Response (200)”{ "id": "550e8400-e29b-41d4-a716-446655440000", "status": "active", "updated_at": "2026-05-21T12:45:00.000Z"}cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/unsuspend \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Rotate API key
Section titled “Rotate API key”POST https://api.bve.me/admin/api-keys/:id/rotateAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYGenerates a new raw key value and immediately invalidates the old one. The key’s ID, status, rate limits, and model allowlist are preserved. Use this when a key has been exposed or as part of a routine rotation policy.
Only active and suspended keys can be rotated. Attempting to rotate a revoked key returns 404.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the API key to rotate |
Response (200)
Section titled “Response (200)”{ "key": "sk-bve-a1b2c3d4...", "id": "550e8400-e29b-41d4-a716-446655440000", "status": "active", "updated_at": "2026-05-22T16:00:00.000Z"}Response (404)
Section titled “Response (404)”{ "error": { "message": "API key not found or revoked", "type": "invalid_request_error", "param": null, "code": "not_found" }}cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/rotate \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Revoke API key
Section titled “Revoke API key”POST https://api.bve.me/admin/api-keys/:id/revokeAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYRevocation is permanent and immediate. The key cannot be restored.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the API key to revoke |
Response (200)
Section titled “Response (200)”{ "id": "550e8400-e29b-41d4-a716-446655440000", "status": "revoked", "revoked_at": "2026-05-21T13:00:00.000Z"}Response (404)
Section titled “Response (404)”Returned when the key does not exist or is already revoked. Calling this endpoint twice on the same key returns 404 on the second call to prevent revoked_at from being overwritten.
{ "error": { "message": "API key not found or already revoked", "type": "invalid_request_error", "param": null, "code": "not_found" }}cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/revoke \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Bulk revoke API keys
Section titled “Bulk revoke API keys”POST https://api.bve.me/admin/api-keys/bulk-revokeAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYContent-Type: application/jsonRevokes multiple API keys in a single request. Useful for emergency lockdowns, offboarding, or cleaning up a set of compromised keys. Revocation is immediate — affected keys are evicted from the auth cache so subsequent requests fail instantly.
Idempotent: already-revoked keys in the ids list are silently skipped. revoked_count reflects only keys that were actually transitioned to revoked.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
ids | string[] | Yes | Array of API key UUIDs to revoke. Minimum 1, maximum 100 per request. |
{ "ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ]}Response (200)
Section titled “Response (200)”{ "revoked_count": 2, "revoked_ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ], "total": 2}| Field | Type | Description |
|---|---|---|
revoked_count | integer | Number of keys actually transitioned to revoked (skips already-revoked keys) |
revoked_ids | string[] | UUIDs of the keys that were newly revoked |
total | integer | Total number of IDs submitted (including any that were already revoked) |
Response (400) — validation error
Section titled “Response (400) — validation error”Returned when the request body is missing, malformed, or violates the 1–100 ID constraint.
{ "error": { "message": "ids must contain at most 100 items", "type": "invalid_request_error", "param": "ids", "code": "validation_error" }}Response (401) — unauthorized
Section titled “Response (401) — unauthorized”Returned when the Authorization header is missing or the token is invalid.
cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys/bulk-revoke \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ] }'Revoke all active keys for an emergency lockdown (using key:list script):
# Get all active key IDs as JSON arraybun run key:list -- --status active --json | \ jq '{ ids: [.[].id] }' | \ curl -X POST https://api.bve.me/admin/api-keys/bulk-revoke \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d @-Bulk suspend API keys
Section titled “Bulk suspend API keys”POST https://api.bve.me/admin/api-keys/bulk-suspendAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYContent-Type: application/jsonTemporarily disables multiple active API keys in a single request. Useful for maintenance windows, security holds, or pausing a team’s credentials without permanent revocation. Suspension is immediate — affected keys are evicted from the auth cache so subsequent requests fail with 403 api_key_suspended instantly.
Unlike bulk revoke, suspended keys can be reactivated at any time via POST /admin/api-keys/bulk-unsuspend (or the single-key unsuspend endpoint).
Idempotent: already-suspended or revoked keys in the ids list are silently skipped. suspended_count reflects only keys that actually transitioned from active → suspended.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
ids | string[] | Yes | Array of API key UUIDs to suspend. Minimum 1, maximum 100 per request. |
{ "ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ]}Response (200)
Section titled “Response (200)”{ "suspended_count": 2, "suspended_ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ], "total": 2}| Field | Type | Description |
|---|---|---|
suspended_count | integer | Number of keys actually transitioned to suspended (skips already-suspended or revoked keys) |
suspended_ids | string[] | UUIDs of the keys that were newly suspended |
total | integer | Total number of IDs submitted |
cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys/bulk-suspend \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ] }'Bulk unsuspend API keys
Section titled “Bulk unsuspend API keys”POST https://api.bve.me/admin/api-keys/bulk-unsuspendAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYContent-Type: application/jsonReactivates multiple suspended API keys in a single request. Suspended keys are immediately restored to active status — affected keys are evicted from the auth cache so the updated status is read from D1 on the next request.
Idempotent: already-active or revoked keys in the ids list are silently skipped. unsuspended_count reflects only keys that actually transitioned from suspended → active.
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
ids | string[] | Yes | Array of API key UUIDs to unsuspend. Minimum 1, maximum 100 per request. |
{ "ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ]}Response (200)
Section titled “Response (200)”{ "unsuspended_count": 2, "unsuspended_ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ], "total": 2}| Field | Type | Description |
|---|---|---|
unsuspended_count | integer | Number of keys actually transitioned to active (skips already-active or revoked keys) |
unsuspended_ids | string[] | UUIDs of the keys that were reactivated |
total | integer | Total number of IDs submitted |
cURL example
Section titled “cURL example”curl -X POST https://api.bve.me/admin/api-keys/bulk-unsuspend \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "ids": [ "550e8400-e29b-41d4-a716-446655440000", "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ] }'Get quota status
Section titled “Get quota status”GET https://api.bve.me/admin/api-keys/:id/quotaAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYReturns the real-time quota consumption and configured limits for an API key. Minute and day counts come from the Durable Object (in-memory, reset at the window boundary). Monthly request and token totals come from D1. Use this to monitor remaining capacity without waiting for a 429.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the API key |
Response (200)
Section titled “Response (200)”{ "key_id": "550e8400-e29b-41d4-a716-446655440000", "status": "active", "limits": { "rpm": 60, "rpd": 10000, "monthly_requests": null, "monthly_tokens": 500000 }, "current": { "minute": { "count": 3, "remaining": 57, "reset_at": "2026-05-22T12:01:00.000Z", "reset_in_seconds": 42 }, "day": { "count": 142, "remaining": 9858, "reset_at": "2026-05-23T00:00:00.000Z", "reset_in_seconds": 42847 }, "month": { "request_count": 2871, "request_remaining": null, "token_count": 187432, "token_remaining": 312568, "reset_at": "2026-06-01T00:00:00.000Z" } }, "checked_at": "2026-05-22T12:00:18.000Z"}| Field | Description |
|---|---|
limits.monthly_requests | null if no monthly request cap is set |
limits.monthly_tokens | null if no monthly token cap is set |
current.minute.remaining | Requests available in the current minute window |
current.day.remaining | Requests available in the current day window |
current.month.request_remaining | null if no monthly request cap |
current.month.token_remaining | null if no monthly token cap |
checked_at | ISO 8601 timestamp when this response was generated |
Response (404)
Section titled “Response (404)”{ "error": { "message": "API key not found", "type": "invalid_request_error", "param": null, "code": "not_found" }}cURL example
Section titled “cURL example”curl https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/quota \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Get key performance stats
Section titled “Get key performance stats”GET https://api.bve.me/admin/api-keys/:id/statsAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYReturns aggregate performance statistics for a single API key from the 1%-sampled request log: total request count, latency percentiles (avg/p50/p95/max), total tokens, error counts split by 4xx vs 5xx, and error rate.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the API key |
Query parameters
Section titled “Query parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
since | string | No | Include only log entries at or after this ISO 8601 timestamp. Invalid timestamps return 400 validation_error. |
until | string | No | Include only log entries at or before this ISO 8601 timestamp. |
Both parameters are optional. Omit them to get all-time stats for this key.
Response (200)
Section titled “Response (200)”{ "key_id": "550e8400-e29b-41d4-a716-446655440000", "total_requests": 142, "stats": { "avg_latency_ms": 1240, "max_latency_ms": 8340, "p50_latency_ms": 980, "p95_latency_ms": 6200, "total_tokens": 246860, "error_count": 3, "client_error_count": 2, "server_error_count": 1, "error_rate": 2.11 }}| Field | Type | Description |
|---|---|---|
key_id | string | UUID of the API key |
total_requests | integer | Number of sampled log entries for this key (~1% of actual requests) |
stats.avg_latency_ms | number | null | Average end-to-end latency in milliseconds. null if no entries. |
stats.max_latency_ms | number | null | Maximum end-to-end latency seen in the sample window |
stats.p50_latency_ms | number | null | Median (50th percentile) latency — more representative than average for skewed distributions |
stats.p95_latency_ms | number | null | 95th percentile latency — useful for tail-latency SLO alerting |
stats.total_tokens | integer | Sum of prompt + completion tokens across all sampled entries |
stats.error_count | integer | Sampled requests with HTTP status ≥ 400 |
stats.client_error_count | integer | Sampled requests with HTTP 4xx status (bad request, quota exceeded, auth failure) |
stats.server_error_count | integer | Sampled requests with HTTP 5xx status (gateway or upstream failures) |
stats.error_rate | number | (error_count / total_requests) × 100, rounded to 2 decimal places. 0 when total_requests is 0. |
Response (404)
Section titled “Response (404)”Returned when the key UUID does not exist.
{ "error": { "message": "API key not found", "type": "invalid_request_error", "param": null, "code": "not_found" }}cURL examples
Section titled “cURL examples”All-time stats for a key:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/stats" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Stats for the last 7 days:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/stats?since=2026-05-18T00:00:00Z" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Alert on high error rate:
const res = await fetch( `https://api.bve.me/admin/api-keys/${keyId}/stats`, { headers: { Authorization: `Bearer ${adminKey}` } },);const { stats } = await res.json();if (stats.error_rate > 5) { console.warn(`Key ${keyId} error rate: ${stats.error_rate.toFixed(2)}%`);}Get key audit log
Section titled “Get key audit log”GET https://api.bve.me/admin/api-keys/:id/auditAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYReturns the lifecycle history for a single API key: every create, update, suspend, unsuspend, revoke, rotate, and quota reset event that targeted this key. Entries are returned in descending chronological order (most recent first).
This is a scoped view of the global Audit Logs filtered to one key. The key difference: this endpoint returns 404 when the key does not exist, whereas GET /admin/audit-logs?target_id= silently returns an empty list.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the API key |
Query parameters
Section titled “Query parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
action | string | No | — | Exact action string to filter by (e.g. api_key.rotated, api_key.created). Returns only entries with this exact action. |
action_category | string | No | — | Filter by action category: keys, auth, models, or admins. keys matches all api_key.* events. Invalid values return 400 validation_error. |
search | string | No | — | Substring match across action, target_id, and actor_type. Case-insensitive LIKE filter. Truncated to 200 characters. |
since | string | No | — | ISO 8601 timestamp. Return only entries at or after this time. Invalid values return 400 validation_error. |
until | string | No | — | ISO 8601 timestamp. Return only entries at or before this time. |
limit | integer | No | 100 | Max entries per page (1–500). |
offset | integer | No | 0 | Number of entries to skip. Use with limit for cursor-free pagination. |
Response (200)
Section titled “Response (200)”{ "total": 4, "has_more": false, "logs": [ { "id": "880e8400-e29b-41d4-a716-446655440003", "action": "api_key.rotated", "actor_type": "admin", "target_type": "api_key", "target_id": "550e8400-e29b-41d4-a716-446655440000", "metadata": { "source": "legacy_admin_api" }, "created_at": "2026-05-22T09:00:00.000Z" }, { "id": "770e8400-e29b-41d4-a716-446655440002", "action": "api_key.updated", "actor_type": "admin", "target_type": "api_key", "target_id": "550e8400-e29b-41d4-a716-446655440000", "metadata": { "changes": ["rpm_limit"], "before": { "rpm_limit": 60 }, "after": { "rpm_limit": 120 }, "source": "legacy_admin_api" }, "created_at": "2026-05-21T14:00:00.000Z" }, { "id": "660e8400-e29b-41d4-a716-446655440001", "action": "api_key.created", "actor_type": "admin", "target_type": "api_key", "target_id": "550e8400-e29b-41d4-a716-446655440000", "metadata": { "name": "my-client", "source": "legacy_admin_api" }, "created_at": "2026-05-21T12:00:00.000Z" } ]}Response fields
Section titled “Response fields”| Field | Type | Description |
|---|---|---|
total | integer | Total number of audit entries for this key matching the current filters (before limit/offset). Use for pagination. |
has_more | boolean | true when offset + len(logs) < total. Shorthand for detecting additional pages without computing pagination math. |
logs | array | Array of audit log entries (see below). |
Log entry fields
Section titled “Log entry fields”| Field | Type | Description |
|---|---|---|
id | string | UUID of the audit log entry |
action | string | Action type (see Audit Logs — Action types) |
actor_type | string | "admin" for Admin API operations; "admin_user" for dashboard actions |
target_type | string | Always "api_key" for this endpoint |
target_id | string | UUID of the API key (same as the :id path parameter) |
metadata | object | null | Action-specific JSON details. For api_key.updated: includes changes (array of changed field names), before, and after objects. For api_key.quota_reset: includes window. Most entries include source: "legacy_admin_api" when triggered via the Admin API. |
created_at | string | ISO 8601 timestamp of the event |
Response (404)
Section titled “Response (404)”{ "error": { "message": "API key not found", "type": "invalid_request_error", "param": null, "code": "not_found" }}cURL examples
Section titled “cURL examples”Full audit history for a key:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/audit" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Audit events since a date:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/audit?since=2026-05-01T00:00:00Z" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Audit events in a date range:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/audit?since=2026-05-01T00:00:00Z&until=2026-05-31T23:59:59Z" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Only rotation events:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/audit?action=api_key.rotated" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"All key lifecycle events (create/update/rotate/suspend/unsuspend/revoke):
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/audit?action_category=keys" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Paginate (page 2, 50 entries per page):
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/audit?limit=50&offset=50" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Substring search across action/target (e.g. all “rotated” events):
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/audit?search=rotated" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Most recent 5 events (using bun run key:audit):
bun run key:audit -- --id 550e8400-e29b-41d4-a716-446655440000 --limit 5Search by keyword (using bun run key:audit):
bun run key:audit -- --id 550e8400-e29b-41d4-a716-446655440000 --search rotatedCommon errors
Section titled “Common errors”| Status | Code | Cause |
|---|---|---|
| 400 | validation_error | since or until is not a parseable ISO 8601 timestamp, or action_category is not one of auth, keys, models, admins |
| 401 | missing_api_key | No Authorization header |
| 401 | invalid_api_key | Admin key does not match the configured ADMIN_API_KEY secret |
| 404 | not_found | No API key with the given UUID exists |
Get key usage history
Section titled “Get key usage history”GET https://api.bve.me/admin/api-keys/:id/usageAuthorization: Bearer admin_bve_YOUR_ADMIN_KEYReturns daily and monthly usage rows scoped to a single API key. This is a convenience wrapper around GET /admin/usage?key_id= with one key difference: it returns 404 when the key UUID does not exist, whereas the generic usage endpoint silently returns empty arrays.
Path parameters
Section titled “Path parameters”| Parameter | Description |
|---|---|
id | UUID of the API key |
Query parameters
Section titled “Query parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
from | string | No | — | Earliest date to include, in YYYY-MM-DD format. Filters daily rows by date and monthly rows by year-month prefix. |
to | string | No | — | Latest date to include, in YYYY-MM-DD format. |
daily_limit | integer | No | 30 | Max daily rows to return (1–500). |
monthly_limit | integer | No | 12 | Max monthly rows to return (1–500). |
from and to can be combined to select a specific window. Invalid date strings (not-a-date, 2026-13-01) return 400 validation_error. from must not be after to.
Response (200)
Section titled “Response (200)”{ "key_id": "550e8400-e29b-41d4-a716-446655440000", "daily": [ { "id": "aa0e8400-e29b-41d4-a716-446655440001", "key_id": "550e8400-e29b-41d4-a716-446655440000", "date": "2026-05-30", "request_count": 47, "prompt_tokens": 12840, "completion_tokens": 3210, "total_tokens": 16050, "updated_at": "2026-05-30T23:00:00.000Z" }, { "id": "bb0e8400-e29b-41d4-a716-446655440002", "key_id": "550e8400-e29b-41d4-a716-446655440000", "date": "2026-05-29", "request_count": 63, "prompt_tokens": 18200, "completion_tokens": 4900, "total_tokens": 23100, "updated_at": "2026-05-29T23:00:00.000Z" } ], "monthly": [ { "id": "cc0e8400-e29b-41d4-a716-446655440003", "key_id": "550e8400-e29b-41d4-a716-446655440000", "year_month": "2026-05", "request_count": 1420, "prompt_tokens": 412800, "completion_tokens": 108600, "total_tokens": 521400, "updated_at": "2026-05-30T23:00:00.000Z" } ]}daily[] row:
| Field | Type | Description |
|---|---|---|
id | string | Row UUID (internal) |
key_id | string | UUID of the API key this row belongs to |
date | string | Calendar date in YYYY-MM-DD format |
request_count | integer | Requests recorded on this date |
prompt_tokens | integer | Prompt token total for this date |
completion_tokens | integer | Completion token total for this date |
total_tokens | integer | Sum of prompt + completion tokens |
updated_at | string | ISO 8601 timestamp of last write |
monthly[] row:
| Field | Type | Description |
|---|---|---|
id | string | Row UUID (internal) |
key_id | string | UUID of the API key this row belongs to |
year_month | string | Month in YYYY-MM format |
request_count | integer | Requests recorded this month |
prompt_tokens | integer | Prompt token total for this month |
completion_tokens | integer | Completion token total for this month |
total_tokens | integer | Sum of prompt + completion tokens |
updated_at | string | ISO 8601 timestamp of last write |
Rows are ordered newest-first. The key_id field at the top level confirms which key was queried.
Response (404)
Section titled “Response (404)”{ "error": { "message": "API key not found", "type": "invalid_request_error", "param": null, "code": "not_found" }}cURL examples
Section titled “cURL examples”Full usage history (last 30 daily + 12 monthly rows):
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/usage" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"Last 7 days only:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/usage?from=2026-05-23&to=2026-05-30" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"90 days of daily data for a billing report:
curl "https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/usage?daily_limit=90" \ -H "Authorization: Bearer admin_bve_YOUR_ADMIN_KEY"TypeScript — compute total tokens for May 2026:
const res = await fetch( 'https://api.bve.me/admin/api-keys/550e8400-e29b-41d4-a716-446655440000/usage?from=2026-05-01&to=2026-05-31&daily_limit=31', { headers: { Authorization: 'Bearer admin_bve_YOUR_ADMIN_KEY' } },);const { daily } = await res.json() as { daily: Array<{ total_tokens: number }> };const monthTotal = daily.reduce((sum, d) => sum + d.total_tokens, 0);console.log('May 2026 tokens:', monthTotal);Common errors
Section titled “Common errors”| Status | Code | Cause |
|---|---|---|
| 400 | validation_error | from or to is not YYYY-MM-DD; from is after to |
| 401 | missing_api_key | No Authorization header |
| 401 | invalid_api_key | Admin key does not match the configured ADMIN_API_KEY secret |
| 404 | not_found | No API key with the given UUID exists |