API

Voice API

Make outbound voice calls — plain or AI-powered — with a single endpoint.

Outbound voice calls route through SIP to the carrier and out to the PSTN. A plain call only needs from and to. Add a systemPrompt to make it an AI-powered conversation.

There is no agent_id field. AI calls are triggered by including systemPrompt in the request body. Without it, you get a plain outbound call.

Create call

POST /api/voice

Initiate an outbound call. Include systemPrompt to make it an AI call, or omit it for a plain call.

Request body

FieldTypeRequiredDescription
fromstringYesCaller phone number in E.164 format
tostringYesDestination phone number in E.164 format
systemPromptstringNoAI agent system prompt. Including this triggers an AI-powered call.
modelstringNoUltravox model (e.g. "fixie-ai/ultravox-70B")
voicestringNoVoice ID for TTS (e.g. "Mark")
toolsarrayNoHTTP tools the AI can call mid-conversation (see below)
firstSpeakerstringNoWho speaks first: "agent" or "user" (default: "agent")
temperaturenumberNoLLM temperature, 0 to 1
maxDurationstringNoMax call duration (e.g. "300s")
metadataobjectNoCustom key-value metadata (string values only)

Tool schema (for tools array)

Each tool in the tools array has the following shape:

FieldTypeRequiredDescription
modelToolNamestringYesName the AI uses to invoke this tool
descriptionstringYesWhat the tool does (shown to the AI)
dynamicParametersarrayNoParameters the AI can pass
httpobjectNoHTTP endpoint (baseUrlPattern, httpMethod)

Plain call example

curl -X POST https://api.trunx.io/api/voice \
  -H "Authorization: Bearer tk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+14155559876",
    "to": "+14155551234"
  }'
const res = await fetch("https://api.trunx.io/api/voice", {
  method: "POST",
  headers: {
    "Authorization": "Bearer tk_live_...",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "+14155559876",
    to: "+14155551234",
  }),
});
const data = await res.json();
import requests

res = requests.post(
    "https://api.trunx.io/api/voice",
    headers={"Authorization": "Bearer tk_live_..."},
    json={
        "from": "+14155559876",
        "to": "+14155551234",
    },
)
data = res.json()

AI call example

curl -X POST https://api.trunx.io/api/voice \
  -H "Authorization: Bearer tk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "+14155559876",
    "to": "+14155551234",
    "systemPrompt": "You are calling to confirm a dental appointment for tomorrow at 2pm. Be friendly and professional.",
    "voice": "Mark",
    "firstSpeaker": "agent",
    "temperature": 0.7,
    "maxDuration": "300s"
  }'
const res = await fetch("https://api.trunx.io/api/voice", {
  method: "POST",
  headers: {
    "Authorization": "Bearer tk_live_...",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "+14155559876",
    to: "+14155551234",
    systemPrompt:
      "You are calling to confirm a dental appointment for tomorrow at 2pm. Be friendly and professional.",
    voice: "Mark",
    firstSpeaker: "agent",
    temperature: 0.7,
    maxDuration: "300s",
  }),
});
const data = await res.json();
import requests

res = requests.post(
    "https://api.trunx.io/api/voice",
    headers={"Authorization": "Bearer tk_live_..."},
    json={
        "from": "+14155559876",
        "to": "+14155551234",
        "systemPrompt": "You are calling to confirm a dental appointment for tomorrow at 2pm. Be friendly and professional.",
        "voice": "Mark",
        "firstSpeaker": "agent",
        "temperature": 0.7,
        "maxDuration": "300s",
    },
)
data = res.json()

Response

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "callId": "CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "status": "initiated"
}

Get call details

GET /api/voice/{id}

Retrieve call details including status, duration, and AI transcript (if applicable).

curl "https://api.trunx.io/api/voice/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer tk_live_..."
const res = await fetch(
  "https://api.trunx.io/api/voice/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  { headers: { "Authorization": "Bearer tk_live_..." } }
);
const data = await res.json();
import requests

res = requests.get(
    "https://api.trunx.io/api/voice/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    headers={"Authorization": "Bearer tk_live_..."},
)
data = res.json()

Response (plain call)

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "from": "+14155559876",
  "to": "+14155551234",
  "status": "completed",
  "provider": "twilio",
  "direction": "outbound",
  "duration": 45,
  "createdAt": "2026-03-09T15:30:00.000Z"
}

Response (AI call)

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "from": "+14155559876",
  "to": "+14155551234",
  "status": "completed",
  "provider": "ultravox",
  "direction": "outbound",
  "duration": 142,
  "createdAt": "2026-03-09T15:30:00.000Z",
  "ai": {
    "status": "ended",
    "transcript": "Agent: Hi, this is Sarah calling from Downtown Dental...\nUser: Yes, this is John...",
    "duration": 140,
    "endReason": "hangup"
  }
}

The ai field is only present for AI-powered calls (those created with systemPrompt). Returns 404 if the call ID does not exist.


End call

DELETE /api/voice/{id}

Hang up an active call.

curl -X DELETE "https://api.trunx.io/api/voice/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "Authorization: Bearer tk_live_..."
const res = await fetch(
  "https://api.trunx.io/api/voice/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  {
    method: "DELETE",
    headers: { "Authorization": "Bearer tk_live_..." },
  }
);
const data = await res.json();
import requests

res = requests.delete(
    "https://api.trunx.io/api/voice/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    headers={"Authorization": "Bearer tk_live_..."},
)
data = res.json()

Response

{
  "ended": true
}

Returns 404 if the call ID does not exist.

Call statuses

StatusDescription
initiatedCall accepted and being placed
ringingCall is ringing at the destination
answeredCall connected
completedCall ended normally
failedCall could not be completed

For AI calls, the ai.transcript and ai.endReason fields are available after the call ends. Poll the get call endpoint or configure a webhook for real-time updates.

On this page