---
title: "API Keys"
description: "Create, list, rotate, and revoke the sk_live_ secret keys your server-side integrations use to call the ThunderPhone REST API on behalf of your organization."
---

API keys (`sk_live_...`) are the primary credential for the
ThunderPhone REST API. They are **secret** — treat them like a
password. Each key is bound to exactly one organization; when an API
call authenticates with a key, we look up the bound org automatically,
which is why the rest of this reference never asks for an org id in
the URL path.

This page documents the endpoints for **creating, listing, and
revoking** keys — the same operations the dashboard exposes at
**Settings → Keys**. You can create your first key from the
dashboard without ever using this API.

## Endpoints

| Method | Path | Required role | Description |
|--------|------|---------------|-------------|
| `GET` | `/v1/whoami` | any authenticated role | Validate a connection and identify its organization |
| `GET` | `/v1/developer/api-keys` | `admin+` | List API keys |
| `POST` | `/v1/developer/api-keys` | `admin+` | Create a new API key |
| `DELETE` | `/v1/developer/api-keys/{key_id}` | `admin+` | Revoke an API key |

<Warning>
  The API-key list, create, and revoke endpoints require an `admin` or
  `owner` role and a signed-in session (`Authorization: Token
  <session-token>` plus `X-ThunderPhone-Org`). An `sk_live_` key gets `403`
  from all three, so a leaked key can't mint its own replacement or revoke
  yours. `GET /v1/whoami` is available to any authenticated role, API keys
  included. See
  [What API keys can't do](/api-reference/introduction#what-api-keys-cant-do).
</Warning>

## API key object

```json
{
  "id": "b1c2d3e4-...",
  "name": "production",
  "key_prefix": "sk_live_abcde12",
  "is_active": true,
  "created_at": "2026-04-20T18:24:10.113Z",
  "last_used_at": "2026-04-20T18:25:06.201Z",
  "revoked_at": null
}
```

| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Public id used by the revoke endpoint |
| `name` | string | Display label |
| `key_prefix` | string | First 15 chars of the raw key for UI display (always `sk_live_` + 7 hex chars). The full key is NOT returned after creation |
| `is_active` | boolean | `false` once the key is revoked |
| `created_at` | timestamp | |
| `last_used_at` | timestamp \| null | Updated best-effort on every successful request |
| `revoked_at` | timestamp \| null | If set, the key is revoked and will no longer authenticate |

---

## Validate a connection

`GET /v1/whoami` is a cheap credential check for integration setup. It
accepts an organization API key, a human session token with
`X-ThunderPhone-Org`, or an OAuth access token. OAuth tokens do not need an
additional product scope for this endpoint.

```bash
curl https://api.thunderphone.com/v1/whoami \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
```

```json Response
{
  "organization": {"id": 42, "name": "Example Co"},
  "credential_kind": "api_key",
  "key_label": "Zapier production",
  "key_prefix": "sk_live_abcde12",
  "oauth_client_id": null,
  "scopes": [],
  "role": "owner"
}
```

Only safe identifiers are returned. Raw keys and tokens are never included.
For OAuth, `oauth_client_id` and the access token's `scopes` are populated;
for human sessions, the key and OAuth fields are `null`.

---

## List API keys

<CodeGroup>
```bash cURL
curl https://api.thunderphone.com/v1/developer/api-keys \
  -H "Authorization: Token YOUR_SESSION_TOKEN" \
  -H "X-ThunderPhone-Org: 42"
```
</CodeGroup>

Returns an array of [API key objects](#api-key-object) — both active
and revoked keys, sorted by `created_at` descending.

---

## Create an API key

<CodeGroup>
```bash cURL
curl -X POST https://api.thunderphone.com/v1/developer/api-keys \
  -H "Authorization: Token YOUR_SESSION_TOKEN" \
  -H "X-ThunderPhone-Org: 42" \
  -H "Content-Type: application/json" \
  -d '{"name": "production"}'
```

```python Python
result = requests.post(
    "https://api.thunderphone.com/v1/developer/api-keys",
    headers={"Authorization": "Token YOUR_SESSION_TOKEN", "X-ThunderPhone-Org": "42"},
    json={"name": "production"},
).json()
new_key = result["key"]
```
</CodeGroup>

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | no | Display label, 1–120 chars. Defaults to `"Default key"` |

Returns `201 Created` with the [API key object](#api-key-object) plus
an extra top-level `key` field containing the raw `sk_live_` value:

```json
{
  "id": "b1c2d3e4-...",
  "name": "production",
  "key_prefix": "sk_live_abcde12",
  "is_active": true,
  "created_at": "2026-04-20T18:24:10.113Z",
  "last_used_at": null,
  "revoked_at": null,
  "key": "sk_live_abcdef1234567890abcdef1234567890abcdef123456"
}
```

<Warning>
  **`key` is returned only once.** Store it in your secret manager
  immediately — if you lose it, revoke it and create a new one. Every
  subsequent `GET` returns only the `key_prefix`.
</Warning>

---

## Revoke an API key

<CodeGroup>
```bash cURL
curl -X DELETE https://api.thunderphone.com/v1/developer/api-keys/b1c2d3e4-... \
  -H "Authorization: Token YOUR_SESSION_TOKEN" \
  -H "X-ThunderPhone-Org: 42"
```
</CodeGroup>

Returns `204 No Content`. Revoked keys are permanently invalidated —
all future requests using the key return `401 Unauthorized`. You
cannot un-revoke; create a new key instead.

<Note>
  Revoking a key takes effect immediately: the next request that uses it
  gets `401`. Move your services to the replacement key first.
</Note>

---

## Related

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="/api-reference/introduction">
    How `sk_live_` keys are attached to requests.
  </Card>
  <Card title="Publishable Keys" icon="window-maximize" href="/api-reference/publishable-keys">
    Public `pk_live_` keys for the widget — different lifecycle.
  </Card>
</CardGroup>
