API keys authenticate requests to the /api/v1/* endpoints. Keys are
scoped to a tenant and can be configured with rate limits, allowed CORS
origins, and (in v1.1) per-scope restrictions.
These endpoints use dashboard (JWT) authentication, not API keys.
Your Supabase session must have the api_keys:manage permission.
Issue a key
curl -X POST https://api.yotel.in/api/v1/keys \
-H "Authorization: Bearer SUPABASE_JWT" \
-H "Content-Type: application/json" \
-d '{
"label": "Production backend",
"environment": "live",
"rate_limit_per_min": 600
}'
Auth: Supabase JWT + api_keys:manage | Status: 201 Created
Request body
| Field | Type | Default | Description |
|---|
label | string | — | Display label for the key (1–200 chars, required) |
environment | string | "live" | "live" (production, real PSTN) or "test" (sandbox, no PSTN) |
scopes | array | [] | Reserved for v1.1 per-scope enforcement |
rate_limit_per_min | integer | 600 | Requests per minute (1–100,000) |
allowed_origins | array | [] | CORS origin allowlist (empty = no CORS restriction) |
Response
{
"id": "key_abc123",
"tenant_id": "tenant_xyz",
"label": "Production backend",
"environment": "live",
"key": "yt_live_a1b2c3d4e5f6...",
"key_prefix": "yt_live_",
"last_four": "f6g7",
"rate_limit_per_min": 600,
"created_at": "2026-05-18T10:00:00Z"
}
The key field is shown only once. We store only a hash — if you
lose the key, you’ll need to issue a new one.
List keys
Returns all keys for the tenant. The full key secret is never included.
curl https://api.yotel.in/api/v1/keys \
-H "Authorization: Bearer SUPABASE_JWT"
Auth: Supabase JWT + api_keys:manage | Status: 200 OK
Query parameter: include_revoked (boolean, default false) — set to
true to include revoked keys.
Response fields
| Field | Type | Description |
|---|
id | string | Key UUID |
label | string | Display label |
environment | string | "live" or "test" |
key_prefix | string | Key format prefix (e.g., "yt_live_") |
last_four | string | Last four characters of the key |
scopes | array | Configured scopes |
rate_limit_per_min | integer | Requests per minute |
allowed_origins | array | CORS origins |
is_active | boolean | Whether the key is active (false if revoked) |
last_used_at | string | null | Last time the key was used |
created_at | string | ISO 8601 |
revoked_at | string | null | When revoked (null if active) |
Rotate a key
Atomically issues a new key and revokes the old one. The response
contains the new key secret (shown once).
curl -X POST https://api.yotel.in/api/v1/keys/{key_id}/rotate \
-H "Authorization: Bearer SUPABASE_JWT" \
-H "Content-Type: application/json" \
-d '{
"label": "Production backend (rotated)",
"rate_limit_per_min": 600
}'
Auth: Supabase JWT + api_keys:manage | Status: 201 Created
The old key is immediately revoked — there is no grace period. Deploy the
new key before rotating.
Revoke a key
Soft-revokes a key. Takes effect immediately.
curl -X DELETE https://api.yotel.in/api/v1/keys/{key_id} \
-H "Authorization: Bearer SUPABASE_JWT" \
-H "Content-Type: application/json" \
-d '{"reason": "Compromised — rotated to new key"}'
Auth: Supabase JWT + api_keys:manage | Status: 204 No Content
The optional reason field (max 500 chars) is stored for audit. If
omitted, defaults to "revoked via dashboard".
| Environment | Prefix | Example |
|---|
| Production | yt_live_ | yt_live_a1b2c3d4e5f6... |
| Sandbox | yt_test_ | yt_test_x9y8z7w6v5u4... |
Test keys create real database rows but do not place PSTN calls. See
Environments for the full behavior difference.
Errors
| Status | Condition |
|---|
403 | Missing api_keys:manage permission |
404 | Key not found or belongs to another tenant |