Threll.ai
REST API

REST API Reference

The Threll REST API is organized around accounts, workers, and phone calls. All endpoints accept and return JSON, over HTTPS.

BASE URL https://api.threll.io/

Conventions

  • All resources are scoped under /v1/accounts/{accountId}.
  • Requests and responses use application/json.
  • Authenticate with the x-api-key header — see Authentication.
  • Errors use conventional HTTP status codes with structured bodies — see Errors.

Pick an endpoint from the sidebar to see its parameters and request/response examples.

Overview

Authentication

Authenticate every request with your API key in the x-api-key header. Keys are created in the platform under Settings → Developer. Requests with a missing or invalid key are rejected with 401.

Authenticated request
curl https://api.threll.io//v1/accounts/9c3e8f02-7a14-4b62-bc59-1d8e5fa3027b/workers \
  -H "x-api-key: $THRELL_API_KEY"
Workers
GET /v1/accounts/{accountId}/workers

Get workers for the account

Responses

StatusDescription
200

200 response body

200 response example
[
  {
    "id": "string",
    "accountId": "string",
    "name": "string",
    "language": "string",
    "type": {
      "id": "string",
      "name": "string"
    },
    "inboundPhoneNumber": "string",
    "outboundPhoneNumber": "string"
  }
]
Workers
GET /v1/accounts/{accountId}/workers/{workerId}

Get worker by id

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
idstringyes
accountIdstringyes
namestringyes
languagestringyes
typeobject
inboundPhoneNumberstring
outboundPhoneNumberstring
200 response example
{
  "id": "string",
  "accountId": "string",
  "name": "string",
  "language": "string",
  "type": {
    "id": "string",
    "name": "string"
  },
  "inboundPhoneNumber": "string",
  "outboundPhoneNumber": "string"
}
Workers
GET /v1/accounts/{accountId}/workers/{workerId}/config

Get the effective configuration for a worker

Returns voice, llm, and routing settings the worker uses when answering or placing a call. Mirrors what 3rd parties receive in call.worker_request hooks.

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
workerIdstringyes
accountIdstringyes
languagestringyes
voiceIdstring
llmProviderstring
llmTemperaturenumber
ringTimeoutnumber
firstSpeechTimeoutnumber
inboundStaticMessageEnabledboolean
inboundStaticMessageTextstring
serviceDownRedirectEnabledboolean
serviceDownRedirectNumberstring
emailEnabledboolean
testModeEnabledboolean
recordingChannelMode"mixed" | "split"Recording channel layout: mixed (both parties mixed, default) or split (agent on left channel, caller on right)
dimensionsobject[]Worker-defined dimensions that drive call analysis
nextActionsobject[]Worker-defined next actions
200 response example
{
  "workerId": "string",
  "accountId": "string",
  "language": "string",
  "voiceId": "string",
  "llmProvider": "string",
  "llmTemperature": 0,
  "ringTimeout": 0,
  "firstSpeechTimeout": 0,
  "inboundStaticMessageEnabled": true,
  "inboundStaticMessageText": "string",
  "serviceDownRedirectEnabled": true,
  "serviceDownRedirectNumber": "string",
  "emailEnabled": true,
  "testModeEnabled": true,
  "recordingChannelMode": "mixed",
  "dimensions": [
    {}
  ],
  "nextActions": [
    {}
  ]
}
Workers
GET /v1/accounts/{accountId}/workers/{workerId}/documents/outbound-call-scripts

Get available outbound call script file names for a worker

Responses

StatusDescription
200 List of available outbound call scripts for the worker

200 response body

200 response example
[
  {
    "id": "string",
    "fileName": "string"
  }
]
Phone Calls
GET /v1/accounts/{accountId}/phone-calls/{phoneCallId}

Get phone call by ID

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
idstringyes
status"scheduled" | "queued" | "in_progress" | "completed"yesStatus of the call.
direction"inbound" | "outbound"yesCall direction
postProcessingStatus"none" | "in_progress" | "completed" | "failed"yesStatus of phone call post processing, i.e. generating transcript.
fromNumberstringPhone number to call from. Not set for web calls.
toNumberstringPhone number to call. Not set for web calls.
nextActionanyFollowup action determined from the phone call analysis. Matches one of the entries in the worker config `next_actions`. Omitted when no configured action applies.
dimensionsobjectExtracted dimension values based on worker configuration. Keys match the dimension keys defined in the worker config.
startedAtstringDate when the call started
endedAtstringDate when the call ended
durationnumberDuration in seconds
customerobject
workerIdstring
scheduledAtstringScheduled date for the call
200 response example
{
  "id": "string",
  "status": "scheduled",
  "direction": "inbound",
  "postProcessingStatus": "none",
  "fromNumber": "string",
  "toNumber": "string",
  "nextAction": {},
  "dimensions": {
    "interested_product": "Product A",
    "expressed_urgency": true,
    "budget_range": "10000-50000"
  },
  "startedAt": "string",
  "endedAt": "string",
  "duration": 0,
  "customer": {
    "id": "string",
    "accountId": "string",
    "externalId": "string",
    "type": "customer",
    "phoneNumber": "string",
    "lastName": "string",
    "firstName": "string",
    "email": "string",
    "streetAddress": "string",
    "country": "string",
    "postalCode": "string",
    "stateOrProvince": "string",
    "city": "string",
    "language": "string",
    "jobTitle": "string",
    "facebookUrl": "string",
    "websiteUrl": "string",
    "linkedInUrl": "string"
  },
  "workerId": "string",
  "scheduledAt": "string"
}
Phone Calls
GET /v1/accounts/{accountId}/phone-calls/{phoneCallId}/transcript

Get phone call transcript

Responses

StatusDescription
200

200 response body

200 response example
[
  {
    "timestamps": {
      "from": "string",
      "to": "string"
    },
    "speaker": "user",
    "text": "string",
    "confidence": {},
    "source": {}
  }
]
Phone Calls
GET /v1/accounts/{accountId}/phone-calls/{phoneCallId}/audio/url

Get phone call audio download URL

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
idstringyes
urlstringyes
200 response example
{
  "id": "string",
  "url": "string"
}
Phone Calls
POST /v1/accounts/{accountId}/phone-calls

Initiates a phone call

Request body

FieldTypeRequiredDescription
workerIdstringyes
contextstringThe context for the phone call
phoneNumberstringThe phone number to call
customerobject
callScriptFileNamesstring[]Optional array of specific call script filenames to use for this call
scheduledAtstringISO 8601 datetime to schedule the call. If not provided, the call is made immediately.
Request example
{
  "workerId": "string",
  "context": "string",
  "phoneNumber": "string",
  "customer": {
    "externalId": "string",
    "type": "customer",
    "phoneNumber": "string",
    "lastName": "string",
    "firstName": "string",
    "email": "string",
    "streetAddress": "string",
    "country": "string",
    "postalCode": "string",
    "stateOrProvince": "string",
    "city": "string",
    "language": "string",
    "jobTitle": "string",
    "facebookUrl": "string",
    "websiteUrl": "string",
    "linkedInUrl": "string"
  },
  "callScriptFileNames": [
    "string"
  ],
  "scheduledAt": "string"
}

Responses

StatusDescription
200
400 Configuration errors for telephony or call script

200 response body

FieldTypeRequiredDescription
idstringyes
status"scheduled" | "queued" | "in_progress" | "completed"yesStatus of the call.
direction"inbound" | "outbound"yesCall direction
postProcessingStatus"none" | "in_progress" | "completed" | "failed"yesStatus of phone call post processing, i.e. generating transcript.
fromNumberstringPhone number to call from. Not set for web calls.
toNumberstringPhone number to call. Not set for web calls.
nextActionanyFollowup action determined from the phone call analysis. Matches one of the entries in the worker config `next_actions`. Omitted when no configured action applies.
dimensionsobjectExtracted dimension values based on worker configuration. Keys match the dimension keys defined in the worker config.
startedAtstringDate when the call started
endedAtstringDate when the call ended
durationnumberDuration in seconds
customerobject
workerIdstring
scheduledAtstringScheduled date for the call
200 response example
{
  "id": "string",
  "status": "scheduled",
  "direction": "inbound",
  "postProcessingStatus": "none",
  "fromNumber": "string",
  "toNumber": "string",
  "nextAction": {},
  "dimensions": {
    "interested_product": "Product A",
    "expressed_urgency": true,
    "budget_range": "10000-50000"
  },
  "startedAt": "string",
  "endedAt": "string",
  "duration": 0,
  "customer": {
    "id": "string",
    "accountId": "string",
    "externalId": "string",
    "type": "customer",
    "phoneNumber": "string",
    "lastName": "string",
    "firstName": "string",
    "email": "string",
    "streetAddress": "string",
    "country": "string",
    "postalCode": "string",
    "stateOrProvince": "string",
    "city": "string",
    "language": "string",
    "jobTitle": "string",
    "facebookUrl": "string",
    "websiteUrl": "string",
    "linkedInUrl": "string"
  },
  "workerId": "string",
  "scheduledAt": "string"
}
Accounts
GET /v1/accounts/current

Get the account associated with the API key

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
idstringyes
createdAtstring
updatedAtstring
namestring
slugstring
200 response example
{
  "id": "string",
  "createdAt": "string",
  "updatedAt": "string",
  "name": "string",
  "slug": "string"
}
Web Calls
POST /v1/accounts/{accountId}/web-calls

Create a web call (WebRTC voice session)

Creates a browser voice session with the worker. Returns a signaling URL, a single-use token and ICE servers. Connect a WebSocket to the signaling URL with ?token=... appended, send an SDP offer and exchange ICE candidates to establish two-way audio. The call is visible on the phone-calls endpoints and emits the same call.* webhook events as phone calls.

Request body

FieldTypeRequiredDescription
workerIdstringyesWorker that handles the call.
instructionsstringFull prompt for the voice agent. When provided, replaces the instructions generated from the worker call script.
customerobject
contextstringSession context merged into the prompt. Plain text or a JSON object.
localestringBCP-47 locale for the conversation, e.g. nb-NO.
voiceIdstringVoice to use for speech synthesis.
temperaturenumberLLM temperature.
llmProvider"ultravox" | "gemini_live"
externalToolsobject[]Tools the voice agent can call during this session. Invocations are delivered as signed call.tool_call webhooks to each tool deliveryUrl.
allowedOriginsstring[]Web origins allowed to open the signaling WebSocket for this session, e.g. ["https://www.example.com"]. When set, connections from other origins are rejected. Omit to allow any origin.
metadataobjectOpaque correlation data stored on the call and echoed as data.metadata on every call.* webhook (call.tool_call, call.transcript, call.status_update, call.ended). Use it to tie events back to your own records, e.g. { "conversationId": "..." }. A value returned from call.worker_request overrides this.
Request example
{
  "workerId": "string",
  "instructions": "string",
  "customer": {
    "externalId": "string",
    "type": "customer",
    "phoneNumber": "string",
    "lastName": "string",
    "firstName": "string",
    "email": "string",
    "streetAddress": "string",
    "country": "string",
    "postalCode": "string",
    "stateOrProvince": "string",
    "city": "string",
    "language": "string",
    "jobTitle": "string",
    "facebookUrl": "string",
    "websiteUrl": "string",
    "linkedInUrl": "string"
  },
  "context": "string",
  "locale": "string",
  "voiceId": "string",
  "temperature": 0,
  "llmProvider": "ultravox",
  "externalTools": [
    {
      "name": "string",
      "description": "string",
      "inputSchema": {},
      "deliveryUrl": "string",
      "signingSecret": "string"
    }
  ],
  "allowedOrigins": [
    "https://www.example.com"
  ],
  "metadata": {
    "conversationId": "ccv_123"
  }
}

Responses

StatusDescription
200
400 Configuration or capacity errors for the web call

200 response body

FieldTypeRequiredDescription
callIdstringyesCall identifier. Use with the phone-calls endpoints and to correlate call.* webhook events.
sessionIdstringyesVoice session identifier.
signalingUrlstringyesWebSocket signaling URL. Connect with the token appended as ?token=...
tokenstringyesSingle-use signaling token. Expires at expiresAt if the client has not connected.
iceServersobject[]yes
expiresAtstringyesISO 8601 time when the unused signaling token expires.
200 response example
{
  "callId": "string",
  "sessionId": "string",
  "signalingUrl": "string",
  "token": "string",
  "iceServers": [
    {
      "urls": {},
      "username": "string",
      "credential": "string"
    }
  ],
  "expiresAt": "string"
}
Webhook Subscriptions
GET /v1/accounts/{accountId}/webhook-subscriptions

List webhook subscriptions for the account

Returns every subscription on the account by default. Pass `workerId` to filter to subscriptions scoped to that worker (account-wide subscriptions are not included when `workerId` is set).

Query parameters

NameTypeRequiredDescription
workerId string

Responses

StatusDescription
200

200 response body

200 response example
[
  {
    "id": "string",
    "accountId": "string",
    "workerId": {},
    "eventType": "call.worker_request",
    "deliveryMode": "sync",
    "url": "string",
    "signingSecret": {},
    "headers": {},
    "timeoutMs": 0,
    "enabled": true,
    "required": true,
    "createdAt": "2026-06-08T10:00:15Z",
    "updatedAt": "2026-06-08T10:00:15Z"
  }
]
Webhook Subscriptions
POST /v1/accounts/{accountId}/webhook-subscriptions

Create a webhook subscription

Returns the subscription including the generated signing_secret in plaintext. The secret is shown only once; store it on your side.

Request body

FieldTypeRequiredDescription
workerIdstringRestrict the subscription to one worker. Omit for an account-wide subscription that fires for every worker.
eventType"call.worker_request" | "call.tool_call" | "call.transcript" | "call.status_update" | "call.ended" | "*"yesThe event type to subscribe to, or '*' for a single endpoint that receives every event.
urlstringyes
headersobject
timeoutMsnumberPer-subscription timeout for sync hooks. Hard cap is 10000 ms to keep the post-pickup dead-air window inside what carriers tolerate.
enabledboolean
requiredbooleanOnly valid for call.worker_request or '*'. When true, the call is aborted if this hook fails to respond within the timeout. Defaults to fail-open (false).
Request example
{
  "workerId": "string",
  "eventType": "call.worker_request",
  "url": "string",
  "headers": {},
  "timeoutMs": 0,
  "enabled": true,
  "required": true
}

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
idstringyes
accountIdstringyes
workerIdobjectWhen set, the subscription fires only for events on this worker. When null/absent the subscription is account-wide.
eventType"call.worker_request" | "call.tool_call" | "call.transcript" | "call.status_update" | "call.ended" | "*"yesUse '*' to receive every event type on a single subscription (one endpoint, one signing secret).
deliveryMode"sync" | "async" | "both"yes'both' is reported for wildcard ('*') subscriptions; the actual delivery mode is determined per event.
urlstringyes
signingSecretobjectReturned in plaintext only on creation responses. Subsequent reads return null.
headersobject
timeoutMsnumber
enabledbooleanyes
requiredbooleanyesWhen true and the sync hook fails (timeout, non-2xx, transport error), the call is aborted instead of falling back to worker defaults. Only valid for call.worker_request or '*' subscriptions.
createdAtstring <date-time>yes
updatedAtstring <date-time>yes
200 response example
{
  "id": "string",
  "accountId": "string",
  "workerId": {},
  "eventType": "call.worker_request",
  "deliveryMode": "sync",
  "url": "string",
  "signingSecret": {},
  "headers": {},
  "timeoutMs": 0,
  "enabled": true,
  "required": true,
  "createdAt": "2026-06-08T10:00:15Z",
  "updatedAt": "2026-06-08T10:00:15Z"
}
Webhook Subscriptions
GET /v1/accounts/{accountId}/webhook-subscriptions/{subscriptionId}

Get a webhook subscription by id

Path parameters

NameTypeDescription
subscriptionId string

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
idstringyes
accountIdstringyes
workerIdobjectWhen set, the subscription fires only for events on this worker. When null/absent the subscription is account-wide.
eventType"call.worker_request" | "call.tool_call" | "call.transcript" | "call.status_update" | "call.ended" | "*"yesUse '*' to receive every event type on a single subscription (one endpoint, one signing secret).
deliveryMode"sync" | "async" | "both"yes'both' is reported for wildcard ('*') subscriptions; the actual delivery mode is determined per event.
urlstringyes
signingSecretobjectReturned in plaintext only on creation responses. Subsequent reads return null.
headersobject
timeoutMsnumber
enabledbooleanyes
requiredbooleanyesWhen true and the sync hook fails (timeout, non-2xx, transport error), the call is aborted instead of falling back to worker defaults. Only valid for call.worker_request or '*' subscriptions.
createdAtstring <date-time>yes
updatedAtstring <date-time>yes
200 response example
{
  "id": "string",
  "accountId": "string",
  "workerId": {},
  "eventType": "call.worker_request",
  "deliveryMode": "sync",
  "url": "string",
  "signingSecret": {},
  "headers": {},
  "timeoutMs": 0,
  "enabled": true,
  "required": true,
  "createdAt": "2026-06-08T10:00:15Z",
  "updatedAt": "2026-06-08T10:00:15Z"
}
Webhook Subscriptions
PATCH /v1/accounts/{accountId}/webhook-subscriptions/{subscriptionId}

Update a webhook subscription

Path parameters

NameTypeDescription
subscriptionId string

Request body

FieldTypeRequiredDescription
urlstring
signingSecretstringSet a new signing secret. If omitted the existing secret is preserved.
headersobject
timeoutMsnumber
enabledboolean
requiredbooleanToggle the required flag. Only valid for call.worker_request or '*' subscriptions.
Request example
{
  "url": "string",
  "signingSecret": "string",
  "headers": {},
  "timeoutMs": 0,
  "enabled": true,
  "required": true
}

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
idstringyes
accountIdstringyes
workerIdobjectWhen set, the subscription fires only for events on this worker. When null/absent the subscription is account-wide.
eventType"call.worker_request" | "call.tool_call" | "call.transcript" | "call.status_update" | "call.ended" | "*"yesUse '*' to receive every event type on a single subscription (one endpoint, one signing secret).
deliveryMode"sync" | "async" | "both"yes'both' is reported for wildcard ('*') subscriptions; the actual delivery mode is determined per event.
urlstringyes
signingSecretobjectReturned in plaintext only on creation responses. Subsequent reads return null.
headersobject
timeoutMsnumber
enabledbooleanyes
requiredbooleanyesWhen true and the sync hook fails (timeout, non-2xx, transport error), the call is aborted instead of falling back to worker defaults. Only valid for call.worker_request or '*' subscriptions.
createdAtstring <date-time>yes
updatedAtstring <date-time>yes
200 response example
{
  "id": "string",
  "accountId": "string",
  "workerId": {},
  "eventType": "call.worker_request",
  "deliveryMode": "sync",
  "url": "string",
  "signingSecret": {},
  "headers": {},
  "timeoutMs": 0,
  "enabled": true,
  "required": true,
  "createdAt": "2026-06-08T10:00:15Z",
  "updatedAt": "2026-06-08T10:00:15Z"
}
Webhook Subscriptions
DELETE /v1/accounts/{accountId}/webhook-subscriptions/{subscriptionId}

Delete a webhook subscription

Path parameters

NameTypeDescription
subscriptionId string

Responses

StatusDescription
204
Webhook Subscriptions
POST /v1/accounts/{accountId}/webhook-subscriptions/{subscriptionId}/test

Deliver a synthetic event to a subscription

For async subscriptions: signs and POSTs a synthetic payload, reports the destination status. For sync subscriptions: same, and also returns the parsed response body in the error field on failure.

Path parameters

NameTypeDescription
subscriptionId string

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
deliveredbooleanyes
statusCodenumber
errorstring
200 response example
{
  "delivered": true,
  "statusCode": 0,
  "error": "string"
}
MCP
GET /v1/accounts/{accountId}/workers/{workerId}/mcp/tools

List the MCP tools available for a worker

Returns the tools discoverable for the given worker based on enabled integrations. Pass `phoneCallId` (preferred) or `customerPhone` to include caller-identity-gated tools.

Path parameters

NameTypeDescription
accountId string
workerId string

Query parameters

NameTypeRequiredDescription
phoneCallId string
customerPhone string

Responses

StatusDescription
200

200 response body

FieldTypeRequiredDescription
toolsobject[]yes
200 response example
{
  "tools": [
    {
      "name": "string",
      "provider": "string",
      "description": "string",
      "inputSchema": {
        "type": "object",
        "properties": {},
        "required": [
          "string"
        ]
      },
      "capabilityKey": "string"
    }
  ]
}
MCP
POST /v1/accounts/{accountId}/workers/{workerId}/mcp/tools/call

Execute an MCP tool

Dispatches a tool call against the configured provider. Pass `phoneCallId` to inherit caller identity, customer context, and any externally-registered tool bindings from the call.

Path parameters

NameTypeDescription
accountId string
workerId string

Request body

FieldTypeRequiredDescription
toolNamestringyesName of the tool to call
providerstringyesProvider/integration that owns the tool. Use "external" to invoke a tool that the call has registered via the call.worker_request hook.
argumentsobjectyesArguments to pass to the tool
customerPhonestringCustomer phone number on the call
phoneCallIdstringPhone call ID used to load the authoritative persisted context (incl. externally-registered tool bindings)
Request example
{
  "toolName": "string",
  "provider": "string",
  "arguments": {},
  "customerPhone": "string",
  "phoneCallId": "string"
}

Responses

StatusDescription
200
400 Invalid tool name or arguments

200 response body

FieldTypeRequiredDescription
resultobjectyes
200 response example
{
  "result": {
    "status": "success",
    "data": {},
    "error": {
      "code": "CALLER_IDENTITY_REQUIRED",
      "message": "string"
    }
  }
}