> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yotel.in/llms.txt
> Use this file to discover all available pages before exploring further.

# API keys

> Issue, list, rotate, and revoke API keys programmatically.

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.

<Note>
  These endpoints use **dashboard (JWT) authentication**, not API keys.
  Your Supabase session must have the `api_keys:manage` permission.
</Note>

## Issue a key

<CodeGroup>
  ```bash curl theme={null}
  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
    }'
  ```
</CodeGroup>

**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

```json theme={null}
{
  "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"
}
```

<Warning>
  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.
</Warning>

***

## List keys

Returns all keys for the tenant. The full key secret is never included.

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.yotel.in/api/v1/keys \
    -H "Authorization: Bearer SUPABASE_JWT"
  ```
</CodeGroup>

**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).

<CodeGroup>
  ```bash curl theme={null}
  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
    }'
  ```
</CodeGroup>

**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.

<CodeGroup>
  ```bash curl theme={null}
  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"}'
  ```
</CodeGroup>

**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"`.

***

## Key formats

| 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](/concepts/environments) for the full behavior difference.

***

## Errors

| Status | Condition                                  |
| ------ | ------------------------------------------ |
| `403`  | Missing `api_keys:manage` permission       |
| `404`  | Key not found or belongs to another tenant |
