Skip to main content
The ThunderPhone API is a REST + JSON API. Every public endpoint accepts an Organization API key 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:
1

Sign in

2

Open Keys

Navigate to Settings → Keys.
3

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

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.
curl https://api.thunderphone.com/v1/agents \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
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.

Health check

Verify API connectivity without authentication:
curl https://api.thunderphone.com/v1/healthz
Response
{ "ok": true }

Dashboard access (X-ThunderPhone-Org)

The dashboard at 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:
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

Resource ids are 64-bit integers (agent id, phone number id, call id, etc.). Tokens (invites, API keys) are strings. Every successful write returns the persisted object including its assigned id:
{
  "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 transactions) accept limit and offset query parameters and return a wrapped envelope:
{
  "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 maximum limit it accepts.

Errors

The API uses conventional HTTP status codes:
StatusMeaning
200OK
201Created
204No Content — operation succeeded with no body
400Bad Request — invalid parameters. Body contains detail or field-specific messages
401Unauthorized — missing or invalid API key
402Payment Required — insufficient balance
403Forbidden — valid credentials but the resource is out of scope or policy denies the action
404Not Found — resource doesn’t exist (or isn’t visible to this key)
409Conflict — optimistic-concurrency / unique-constraint failures
410Gone — deprecated URL shape, see new_path in body
429Too Many Requests — rate limit (response includes Retry-After)
500Internal Server Error
502Bad Gateway — upstream provider (Stripe, LiveKit, VoIP) failed
Errors always include a JSON body:
Error body
{
  "detail": "Agent not found.",
  "code": "agent_not_found"
}
Field-specific validation errors use DRF’s default format:
Validation error
{
  "name": ["This field is required."],
  "voice": ["\"foo\" is not a supported voice id."]
}

Rate limiting

The API currently imposes a soft rate limit of 100 requests per second per API key. When exceeded, the response is 429 Too Many Requests with a Retry-After header in seconds. Bursty workloads should back off and retry after the advertised delay; idempotent operations (GET, PUT, and explicitly-keyed POSTs noted on each endpoint) can be safely retried.

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:
{
  "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

Quickstart

Create your first agent, provision a number, and take a test call.

Agents

Configure voices, prompts, and tool integrations.

Calls

List, inspect, and export call history.

Webhooks

Subscribe to real-time events.