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

# Authentication

> How to authenticate with the ThunderPhone API, set your target organization, and handle errors.

The ThunderPhone API is a REST + JSON API. Every public endpoint accepts an
[Organization API key](#api-keys) via the standard OAuth 2.0 Bearer scheme
and returns JSON with UTF-8 encoding.

## Base URL

```
https://api.thunderphone.com/v1
```

All examples in this reference are rooted at that base URL. The API is
versioned via the `/v1` path prefix — we add new fields additively and
avoid breaking changes within `v1`.

## API keys

Create a key from the dashboard:

<Steps>
  <Step title="Sign in">
    Log into the [ThunderPhone Dashboard](https://app.thunderphone.com).
  </Step>

  <Step title="Open Keys">
    Navigate to **Settings → Keys**.
  </Step>

  <Step title="Create key">
    Click **Create API Key**, give it a name (e.g. `production`, `ci`), and copy the `sk_live_...` value.
    The raw key is shown **only once** — if you lose it, revoke and create a new one.
  </Step>
</Steps>

<Warning>
  **Keep API keys secret.** They carry full access to your organization's
  resources. Never ship them in client-side code or commit them to version
  control. Publishable keys (`pk_live_...`) are **not** API keys — they are
  a separate, origin-restricted credential for the embeddable web widget
  and are not accepted by this API.
</Warning>

<Note>
  **API keys are exempt from the "Require passkeys" org policy.** Passkeys
  are a human 2FA check performed at sign-in and on session use; an
  `sk_live_...` key is a machine credential with no login session to
  attach a passkey check to, so it authenticates independently of that
  policy even when it's enabled for your organization. This is intentional
  — treat key issuance and rotation as your access control for
  server-to-server integrations, and audit or revoke keys from
  **Organization → Keys** the same way you would rotate any other
  standing credential. See [Create a server API key](/guides/api-keys)
  and [Enterprise SSO](/guides/sso#security-policy) for how the policy
  applies to human members.
</Note>

## Making a request

Send the key as a bearer token in the `Authorization` header. The API
infers your organization from the key, so **you don't need an org id in
the URL path**.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.thunderphone.com/v1/agents \
    -H "Authorization: Bearer sk_live_YOUR_API_KEY"
  ```

  ```python Python theme={null}
  import requests

  resp = requests.get(
      "https://api.thunderphone.com/v1/agents",
      headers={"Authorization": "Bearer sk_live_YOUR_API_KEY"},
      timeout=10,
  )
  resp.raise_for_status()
  agents = resp.json()
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.thunderphone.com/v1/agents",
    { headers: { Authorization: `Bearer ${process.env.THUNDERPHONE_API_KEY}` } },
  );
  if (!response.ok) throw new Error(await response.text());
  const agents = await response.json();
  ```
</CodeGroup>

<Info>
  Successful JSON responses use `200 OK` for reads and `201 Created` for
  writes. Deletes return `204 No Content`. Errors use the standard HTTP
  status codes listed below.
</Info>

## Health check

Verify API connectivity without authentication:

```bash theme={null}
curl https://api.thunderphone.com/v1/healthz
```

```json Response theme={null}
{ "ok": true }
```

## Dashboard access (X-ThunderPhone-Org)

The dashboard at [app.thunderphone.com](https://app.thunderphone.com)
authenticates users with a personal session/token (one user can belong to
many organizations). Those requests identify the target organization via
the `X-ThunderPhone-Org` header:

```http theme={null}
Authorization: Token <personal-token>
X-ThunderPhone-Org: 42
```

You will normally **not** need this header — it exists for the dashboard
code path. API integrations built against an `sk_live_` key should omit
it; the key already carries an org binding and the header, if present, is
ignored for API-key requests.

## ID format

Most core resources use 64-bit integer ids (agents, phone numbers,
calls, members, organizations, publishable keys, VoIP connections).
Newer resources use **UUID** ids — integrations, developer API keys,
webhook endpoints, campaigns, knowledge bases and documents, MCP
servers, provider connections, investigations, and test suites — and
issue reports are addressed by a UUID `trace_id`. Each page states the
id type in its object table. Tokens (invites, API keys) are opaque
strings. Every successful write returns the persisted object including
its assigned id:

```json theme={null}
{
  "id": 12,
  "name": "Customer Support Agent",
  "created_at": "2026-04-20T18:24:10.113Z",
  "updated_at": "2026-04-20T18:24:10.113Z"
}
```

Timestamps are ISO 8601 with millisecond precision in UTC.

## Pagination

List endpoints return a plain JSON array by default. Endpoints that can
produce very large result sets (currently **calls** and
**issue reports**) accept `limit` and `offset` query parameters and
return a wrapped envelope:

```json theme={null}
{
  "results": [ /* ... */ ],
  "total":   1234,
  "limit":   50,
  "offset":  0
}
```

To page forward, add `limit` to the current `offset` on each request
and stop when `offset + limit >= total`. See each endpoint's page for
the default and maximum `limit` it accepts.

<Note>
  [Billing transactions](/api-reference/billing#list-transactions) also
  accept `limit`/`offset` but return a plain array without the envelope
  — page forward until you receive fewer rows than `limit`.
</Note>

## Errors

The API uses conventional HTTP status codes:

| Status | Meaning                                                                                                          |
| ------ | ---------------------------------------------------------------------------------------------------------------- |
| `200`  | OK                                                                                                               |
| `201`  | Created                                                                                                          |
| `204`  | No Content — operation succeeded with no body                                                                    |
| `400`  | Bad Request — invalid parameters. Body contains `detail` or field-specific messages                              |
| `401`  | Unauthorized — missing or invalid API key                                                                        |
| `402`  | Payment Required — insufficient balance                                                                          |
| `403`  | Forbidden — valid credentials but the resource is out of scope or policy denies the action                       |
| `404`  | Not Found — resource doesn't exist (or isn't visible to this key)                                                |
| `409`  | Conflict — optimistic-concurrency failures, or an action that requires a live/other state                        |
| `410`  | Gone — deprecated URL shape (see `new_path` in body), or an expired/used invite token                            |
| `422`  | Unprocessable Entity — the request is well-formed but can't be processed (e.g. billing prerequisites not met)    |
| `429`  | Too Many Requests — a scoped limit on a specific endpoint (noted on that endpoint's page)                        |
| `500`  | Internal Server Error                                                                                            |
| `502`  | Bad Gateway — an upstream provider (LiveKit, VoIP, model backend) failed                                         |
| `503`  | Service Unavailable — an upstream provider (e.g. the billing provider) is temporarily unreachable; retry shortly |

Error responses include a JSON body with a human-readable `detail`
message:

```json Error body theme={null}
{
  "detail": "Call not found."
}
```

Field-specific validation errors (`400`) instead map each offending
field to a list of messages, using DRF's default format:

```json Validation error theme={null}
{
  "name": ["This field is required."],
  "voice": ["Unsupported voice option."]
}
```

<Note>
  Most endpoints return **only** `detail` (or the field-error map) —
  there is no machine-readable `code` field on generic errors. The
  exception is [Billing](/api-reference/billing#errors), whose payment
  errors add `code`, `retryable`, and `next_action` fields so clients
  can drive recovery flows (3-D Secure, card update, retry).
</Note>

## Rate limiting

There is **no global rate limit** on the API today, and general
endpoints never return `429`. A small number of specific endpoints
carry scoped limits — for example, [invite access
requests](/api-reference/invites#request-access-to-an-invite) are
limited to 3 per hour per user, and simulations enforce a cap on
concurrently running calls. These are documented on the relevant
endpoint pages and return `429 Too Many Requests` when exceeded (no
`Retry-After` header is sent). Treat write operations as
non-idempotent unless the endpoint documents otherwise.

## Migration from `/v1/orgs/<id>/...` paths

The API previously nested every resource under your organization id in
the URL path — e.g. `/v1/orgs/42/agents`. Those URLs now return `410
Gone` with a body pointing to the new flat shape:

```json theme={null}
{
  "detail": "This endpoint moved. Drop the /orgs/<id>/ prefix...",
  "legacy_path": "/v1/orgs/42/agents",
  "new_path": "/v1/agents"
}
```

Existing integrations only need a one-line change: remove `/orgs/<id>`
from every URL. Your `sk_live_` key is unchanged and continues to
identify your organization automatically.

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Create your first agent, provision a number, and take a test call.
  </Card>

  <Card title="Agents" icon="robot" href="/api-reference/agents">
    Configure voices, prompts, and tool integrations.
  </Card>

  <Card title="Calls" icon="phone" href="/api-reference/calls">
    List, inspect, and export call history.
  </Card>

  <Card title="Webhooks" icon="bolt" href="/webhooks/overview">
    Subscribe to real-time events.
  </Card>
</CardGroup>
