Open in
Publishable Keys
Create, list, and revoke the pk_live_ publishable keys that let the embeddable web widget start browser voice sessions with your agents from your own site.
Publishable keys (pk_live_...) are public credentials that the
embeddable web widget uses from the browser. Unlike secret API keys,
they are scoped to a specific agent (or a webhook that will dynamically
resolve one) and gated to a list of allowed origins — safe to ship in
your front-end code. The origin list stops casual reuse on other sites;
it is not authentication, since a non-browser client can send any
Origin header. That is why the session settings a browser may send
are off by default (see allowed_overrides).
Each key is associated with exactly one of:
mode = "agent": fixed binding to an agent id. Simplest setup.mode = "webhook": the widget calls yourcall.incomingwebhook to dynamically resolve an agent per visitor.
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/publishable-key | List publishable keys |
POST | /v1/publishable-key | Create a publishable key |
GET | /v1/publishable-key/{key_id} | Retrieve a key |
PATCH | /v1/publishable-key/{key_id} | Update name / allowed domains / mode / browser overrides |
DELETE | /v1/publishable-key/{key_id} | Deactivate a key |
POST | /v1/publishable-key/{key_id}/transfer | Copy or move to another org |
Publishable key object
{
"id": 9,
"name": "Marketing site (prod)",
"key": "pk_live_abc123...",
"key_prefix": "pk_live_abc",
"allowed_domains": ["example.com", "*.example.com"],
"is_active": true,
"is_enabled": true,
"mode": "agent",
"agent_id": 12,
"agent_name": "Customer Support Agent",
"webhook_url": null,
"allowed_overrides": ["context"],
"max_call_duration_seconds": 1800,
"created_at": "2026-04-20T18:24:10.113Z",
"updated_at": "2026-04-20T18:24:10.113Z"
}| Field | Type | Description |
|---|---|---|
id | integer | Key id |
name | string | Display name |
key | string | The raw pk_live_... value. Publishable keys are public, so the full value is retrievable at any time |
key_prefix | string | First 12 chars for display (e.g. pk_live_abc) |
allowed_domains | array of string | Origin allowlist — exact hostnames and *.subdomain wildcards. localhost is not allowed automatically in production; list it to test locally |
is_active | boolean | false after deactivation; a deactivated key stops working |
is_enabled | boolean | Temporary disable toggle (separate from is_active) |
mode | string | agent or webhook |
agent_id | integer | null | Required for mode="agent" |
agent_name | string | null | Read-only convenience — the linked agent's name |
webhook_url | string | null | Required for mode="webhook" — overrides the org-level webhook |
allowed_overrides | array of string | Session settings the browser may send to POST /v1/widget/session: any of context, variables, voice, language. A request carrying one that is not listed fails with 400 override_not_allowed. [] for new keys; keys created before the setting existed allow all four |
max_call_duration_seconds | integer | null | Longest a widget session may run, in seconds: 60–21600, default 1800 (30 minutes). A session ends at this or at the agent's own max call duration, whichever is shorter. null uses the agent's (or webhook config's) own limit alone |
created_at, updated_at | timestamp |
List publishable keys
curl https://api.thunderphone.com/v1/publishable-key \
-H "Authorization: Bearer sk_live_YOUR_API_KEY"Returns an array of Publishable key objects.
Create a publishable key
curl -X POST https://api.thunderphone.com/v1/publishable-key \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Marketing site (prod)",
"mode": "agent",
"agent_id": 12,
"allowed_domains": ["example.com", "*.example.com"]
}'curl -X POST https://api.thunderphone.com/v1/publishable-key \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Dynamic widget",
"mode": "webhook",
"webhook_url": "https://example.com/thunderphone-webhook",
"allowed_domains": ["example.com"]
}'| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | Defaults to "Web Widget" |
mode | string | no | agent (default) or webhook |
agent_id | integer | yes (if mode="agent") | |
webhook_url | string | yes (if mode="webhook") | HTTPS URL |
allowed_domains | array of string | yes | At least one entry required. See rules below |
is_enabled | boolean | no | Defaults to true |
allowed_overrides | array of string | no | Any of context, variables, voice, language. Defaults to [] (the browser may set none) |
max_call_duration_seconds | integer | null | no | 60–21600, default 1800 (30 minutes). null leaves only the agent's own limit |
Allowed-domain rules
- An exact match:
example.com - A wildcard match:
*.example.com(matchesapi.example.combut not bareexample.com) localhostand127.0.0.1are not allowed automatically in production. List them to test a local page against the production API- Bare wildcards (
*or*.*) are rejected with400 - At least one entry is required — passing an empty array returns
400 allowed_domains: "At least one allowed domain is required."
Returns 201 Created with the new Publishable key object.
The key field is included on creation and is retrievable later
via GET — publishable keys are public by design.
Update / delete
PATCH accepts any subset of name, allowed_domains, mode,
agent_id, webhook_url, is_enabled, embed_config,
allowed_overrides, max_call_duration_seconds. Switching between modes
requires providing the new required field (agent_id or
webhook_url).
DELETE sets is_active=false — the key stops working but remains
visible in list responses so you can audit prior deployments. There is
no hard delete.
Transfer a publishable key
Copy or move a publishable key to another organization where you are an admin.
An sk_live_ API key is limited to its own organization: with a key,
target_org_id must be the key's organization, and any other target
returns 403. Copy or move between organizations from a signed-in session
(Authorization: Token <session-token> plus X-ThunderPhone-Org for the
source org), as below.
curl -X POST https://api.thunderphone.com/v1/publishable-key/9/transfer \
-H "Authorization: Token YOUR_SESSION_TOKEN" \
-H "X-ThunderPhone-Org: 42" \
-H "Content-Type: application/json" \
-d '{"target_org_id": 77, "mode": "copy"}'| Field | Type | Required | Description |
|---|---|---|---|
target_org_id | integer | yes | |
mode | string | yes | copy or move |
name | string | no | Override the copy's name |