Calls API
A call is a voice conversation on one of your channels, answered or placed by the agent deployed there. Inbound and campaign calls are listed with the ones you place, each with its transcript, summary and outcome.
calls:writecalls:read11 endpointsShow
Access to calls
A key with calls:read reads every call of the workspace, with its transcript and recording. That is why only a person who sees every call can give a key that scope: owners, admins, and builders whose access to calls is not limited.
Team notes
A call's notes are the ones your team writes on the call's page. A key writes them as the person who created it, and only that person's own notes can be changed or deleted, as on the page. Each note names its author, never their email.
/v1/callsPlace an outbound call
Dials to from a voice channel, with the agent deployed on that channel. Calls go through NuTalk's call queue, which applies your plan's simultaneous-call and hourly limits and your credit balance. The request waits up to about 20 seconds for the queue: when the call has been placed you get 201 with its id, otherwise 202 with the queue job id (the call is still placed when a line frees up).
calls:writeTakes an Idempotency-KeyRetry a failed or timed-out request with the same Idempotency-Key: the first response is replayed and the number is not dialed twice.
A number on the do-not-call list is refused before anything is dialed or charged, as in a campaign. The match is by the person, not by how the number is written: any spelling of the number, and any other number the contact has. To call the person again, lift the opt-out first (PATCH /v1/contacts/{id} with do_not_contact: false). A test call is checked the same way once its number passed the test-number rule.
The list is looked at twice: when the request arrives, and again right before the dial, with the number as the line will dial it. So a call that waited in the queue is not placed to a person who opted out meanwhile. Refused at the dial while this request still waits, the answer is the same 409 do_not_contact; refused later, the job ends dead with error do_not_contact (GET /v1/call-jobs/{queue_job_id}) and no call is created.
Request body
channel_iduuidrequiredA voice channel (phone number) of your workspace with a deployed agent. See GET /v1/channels?type=voice.
tostring · up to 20requiredNumber to call, in E.164 with the country code, e.g. +14155551234. A number sent without its country code is dialed as the channel's line reads it (a Zoom Phone line reads ten digits as a Mexican number; another line may read them differently or not dial them), so always send the whole number.
caller_idstring · up to 20Caller ID to present, when the channel's carrier allows it: one of your workspace's numbers (this channel's number, or a number your workspace owns or is assigned).
notesstring · up to 1,000Context for this call, kept as a note on the call for your team. GET /v1/calls/{id} does not return it.
status_callback_urlstringPublic URL that receives one unsigned POST with the call's outcome when it ends (no retries). Must not be localhost or a private address. For signed, retried events use webhooks instead.
test_callbooleanPlaces a free test call: to must be one of the workspace's verified test numbers (at most 4, in Mexico, the US or Canada, added in the dashboard). It rings from NuTalk's test line, lasts at most 5 minutes and costs no credits; each workspace gets 10 a day, unanswered calls included.
Show 2 more fields
Placing a call charges your workspace for its minutes. When an AI agent drives the API for you, have it confirm the number and the agent before it places a call.
Errors
400validation_errorThe body is not JSON, or a field is missing or invalid (details.errors lists each field).400validation_errorchannel_id is not a voice channel, or status_callback_url is localhost, a private address or not http(s).400validation_errorcaller_id is not one of your workspace's numbers (error.details.field: caller_id).404not_foundNo channel with this id in your workspace (ids of other workspaces answer the same).409validation_errorNo agent with a deployed version is on this channel.400validation_errortest_call is true and to is not a valid mobile or landline number in Mexico, the US or Canada (error.details.reason: TEST_CALL_INVALID_NUMBER, TEST_CALL_COUNTRY_NOT_SUPPORTED or TEST_CALL_NUMBER_NOT_CALLABLE).403forbiddentest_call is true and to is not one of the workspace's verified test numbers (error.details.reason: TEST_CALL_NUMBER_NOT_ALLOWED).409conflicttest_call is true and the workspace used today's free test calls (error.details.reason: TEST_CALL_DAILY_LIMIT, with error.details.quota; a channel on the workspace's own HT813 line has no daily limit), test calls are paused (TEST_CALL_PAUSED, also for a workspace set to 0 a day), or to is the channel's own number (TEST_CALL_SAME_AS_CALLER_ID).503service_unavailabletest_call is true and test calls are not available yet (error.details.reason: TEST_CALL_NOT_AVAILABLE).409workspace_suspendedThe workspace is suspended and the call would be dialed directly (a test call to NuTalk's own test line). A call that goes through the queue waits there until the workspace is reactivated.409do_not_contactto is on the do-not-call list: the person opted out, or the number is on the workspace's exclusion list. Nothing was dialed or charged. Do not retry it. A key that also holds contacts:read is told who and since when (error.details.contact_id, error.details.since); a key without it is told only that the number may not be called. A number sent without its country code is also refused when a number a line could dial for it is on the list (the ten digits as a Mexican number, or as a US or Canadian one): error.details.dialed_as is that number, and no contact is named for any key.503service_unavailableThe do-not-call list could not be checked, so no call was placed. Retry with the same Idempotency-Key.Plus the errors every request can return (401, 403, 429, 500): Errors.
curl -X POST https://www.nutalk.ai/v1/calls \
-H "Authorization: Bearer $NUTALK_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "9f19ef32-eaef-452c-bebc-14d9759cdb64",
"to": "+14155551234",
"notes": "Follow-up call for demo scheduling",
"status_callback_url": "https://your-app.example.com/webhooks/call-status"
}'{
"success": true,
"data": {
"id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"channel_id": "9f19ef32-eaef-452c-bebc-14d9759cdb64",
"to": "+14155551234",
"from": "+525512345678",
"direction": "outbound",
"status": "queued",
"queued": true,
"queue_job_id": "9718d2fe-ecf2-4630-82c6-599dfcc0f9fd",
"queue_status": "completed"
}
}call_id is the call.{
"success": true,
"data": {
"queued": true,
"queue_job_id": "9718d2fe-ecf2-4630-82c6-599dfcc0f9fd",
"queue_status": "queued"
}
}queue_status: "dead", the reason in error)./v1/callsList calls
Lists inbound and outbound calls, newest first (by start time). A campaign's call names its enrollment (campaign_enrollment_id; null for other calls). Transcripts and summaries are on GET /v1/calls/{id}.
calls:readfailure_reason says why an outbound call reached nobody, and failure_detail says it in a sentence (English), e.g. declined with "The person or their phone company rejected the call.". Both are null for a call that connected, an inbound call and a call still going. failure_reason is one of: invalid_number, not_in_service, unreachable, busy, declined, blocked_spam, no_answer, voicemail, hung_up_right_away, answered_no_speech, ai_not_connected, caller_id_not_allowed, destination_not_allowed, needs_country_code, network_busy, line_in_use, no_credits, do_not_contact, not_interested, interested, callback, canceled, system_error, failed. It reads what the carrier said (the dial's refusal, the hangup's SIP code), so it can be more precise than call_outcome: a declined call keeps call_outcome: busy. failed means nothing more specific is known; its failure_detail then names the code the carrier gave, when it gave one.
Query parameters
limitinteger · 1–100Results per page. Default: 20.
offsetinteger · at least 0Results to skip. Default: 0.
statusqueued | ringing | in_progress | completed | failed | canceled | voicemail | no_answer | busy | rejectedOnly calls in this status. in-progress and no-answer are accepted as aliases.
directioninbound | outboundOnly inbound or only outbound calls.
fromstringExact caller number, e.g. +525512345678.
tostringExact called number, e.g. +14155551234.
channel_iduuidOnly calls on this channel.
campaign_iduuidOnly this campaign's calls (needs calls:read only).
enrollment_iduuidOnly the calls to one campaign contact (an enrollment id).
started_afterdatetimeOnly calls that started at or after this UTC time, e.g. 2026-10-01T00:00:00Z (a Z timestamp; offsets are refused).
started_beforedatetimeOnly calls that started at or before this UTC time (a Z timestamp).
Show 7 more fields
Errors
400validation_errorA query parameter is invalid (details.errors lists each one).Plus the errors every request can return (401, 403, 429, 500): Errors.
curl "https://www.nutalk.ai/v1/calls?status=completed&direction=outbound&limit=10" \ -H "Authorization: Bearer $NUTALK_API_KEY"
{
"success": true,
"data": [
{
"id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"direction": "outbound",
"status": "completed",
"from_number": "+525512345678",
"to_number": "+14155551234",
"started_at": "2026-10-02T02:14:49.108017+00:00",
"ended_at": "2026-10-02T02:16:49.108017+00:00",
"duration_seconds": 120,
"agent_name": "Support Agent",
"channel_name": "Support Agent - Voice",
"call_outcome": "completed",
"failure_reason": null,
"failure_detail": null,
"is_test": false,
"campaign_enrollment_id": null
}
],
"pagination": {
"total": 1,
"limit": 10,
"offset": 0,
"has_more": false
}
}/v1/calls/:idGet a call
Returns one call with its transcript, summary, outcome, quality metrics and credits charged.
calls:readcredits_charged is a whole number of credits: the charge with its fraction dropped, never rounded up. credits_charged_exact is the exact charge, a number with up to 2 decimals (a rate can have decimals; a call is charged per started minute).
failure_reason says why an outbound call reached nobody, and failure_detail says it in a sentence (English), e.g. declined with "The person or their phone company rejected the call.". Both are null for a call that connected, an inbound call and a call still going. failure_reason is one of: invalid_number, not_in_service, unreachable, busy, declined, blocked_spam, no_answer, voicemail, hung_up_right_away, answered_no_speech, ai_not_connected, caller_id_not_allowed, destination_not_allowed, needs_country_code, network_busy, line_in_use, no_credits, do_not_contact, not_interested, interested, callback, canceled, system_error, failed. It reads what the carrier said (the dial's refusal, the hangup's SIP code), so it can be more precise than call_outcome: a declined call keeps call_outcome: busy. failed means nothing more specific is known; its failure_detail then names the code the carrier gave, when it gave one.
recording_url tells where the recording is stored (supabase://… in NuTalk's private storage, or a carrier URL for older calls). It is not a public link: download the audio with GET /v1/calls/{id}/recording.
transcript and transcript_summary fill in a few minutes after the call ends, when post-call processing finishes.
metadata carries only where the call came from (source) and, for a call from your website, the page and the visitor's details. NuTalk's own routing data is not part of it, and neither are the notes sent with POST /v1/calls: those are kept as a note on the call for your team, and the API does not return them.
Path parameters
iduuidrequiredThe call id.
Errors
404not_foundNo call with this id in your workspace (ids of other workspaces answer the same).Plus the errors every request can return (401, 403, 429, 500): Errors.
curl https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6 \ -H "Authorization: Bearer $NUTALK_API_KEY"
{
"success": true,
"data": {
"id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"direction": "outbound",
"status": "completed",
"from_number": "+525512345678",
"to_number": "+14155551234",
"started_at": "2026-10-02T02:14:49.108017+00:00",
"ended_at": "2026-10-02T02:16:49.108017+00:00",
"duration_seconds": 120,
"agent_name": "Support Agent",
"channel_name": "Support Agent - Voice",
"call_outcome": "completed",
"failure_reason": null,
"failure_detail": null,
"is_test": false,
"campaign_enrollment_id": null,
"channel_id": "9f19ef32-eaef-452c-bebc-14d9759cdb64",
"transcript": "Agent: Hi, this is Acme support.\nCaller: Hi, I'd like a demo.",
"transcript_summary": "The caller asked for a demo; a meeting was offered for Thursday.",
"recording_url": "supabase://voice-recordings/d3b85d84-ef8f-4c4a-8d18-3fa23e7fd48a/1ba11348-e440-44de-a5cd-5fe62f4d9df6.mp3",
"metadata": {},
"quality_metrics": {
"mos": null,
"jitter_ms": null,
"packet_loss_pct": null
},
"answered_by": "human",
"twilio_status": null,
"credits_charged": 16,
"credits_charged_exact": 16.5
}
}/v1/call-jobs/:idGet a queued call's job
Returns the queue job that POST /v1/calls answered with (queue_job_id) when it could not place the call within the request. Read it again at its next_attempt_at while it waits (about every 30 s when it has none): once status is completed, call_id is the placed call (read it with GET /v1/calls/{id}); dead means the queue gave up, with the reason in error. Every read counts against the workspace rate limit, and a job can wait for hours while lines are busy.
calls:readstatus is queued (waiting for a free line, or for the next attempt at next_attempt_at), waiting_for_line (an AiBox line is busy; tried again at next_attempt_at), leased or running (being placed), completed, dead (no attempts left) or cancelled. next_attempt_at is null whenever no attempt time is ahead, for instance while the job waits for a line to free up.
Only outbound-call jobs of your workspace are found; any other id answers 404.
A dead job whose error is do_not_contact was refused at the dial: by then the number was on the do-not-call list (the person opted out while the call waited, or the number as the line dials it is on the list). Nobody was called; do not ask for that call again.
Path parameters
iduuidrequiredThe call job id.
Errors
404not_foundNo call job with this id in your workspace (ids of other workspaces answer the same).Plus the errors every request can return (401, 403, 429, 500): Errors.
curl https://www.nutalk.ai/v1/call-jobs/1ba11348-e440-44de-a5cd-5fe62f4d9df6 \ -H "Authorization: Bearer $NUTALK_API_KEY"
{
"success": true,
"data": {
"id": "9718d2fe-ecf2-4630-82c6-599dfcc0f9fd",
"status": "completed",
"call_id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"channel_id": "9f19ef32-eaef-452c-bebc-14d9759cdb64",
"to": "+14155551234",
"attempts": 1,
"max_attempts": 3,
"next_attempt_at": null,
"error": null,
"created_at": "2026-10-02T02:14:30.000000+00:00",
"completed_at": "2026-10-02T02:14:49.108017+00:00"
}
}/v1/calls/:id/recordingDownload a call's recording
Returns the call's audio, as the dashboard plays it: a 302 redirect to a signed link that expires in 5 minutes (recordings in NuTalk's storage), or the carrier's audio streamed through (audio/mpeg or audio/wav, with Range support). Follow redirects (curl -L -o call.mp3) and fetch it again for a fresh link instead of storing it. Every request it serves is recorded in the workspace's audit log with the key's name and prefix (a resumed range is a request too).
calls:readRecordings carry what the caller said: treat them as customer data, and keep downloads to what your integration needs.
Path parameters
iduuidrequiredThe call id.
Errors
404not_foundNo call with this id in your workspace (ids of other workspaces answer the same).404not_foundThe call has no recording (not recorded, or not stored yet).502internal_errorThe carrier did not return the recording.Plus the errors every request can return (401, 403, 429, 500): Errors.
curl -L -o call-recording.mp3 https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6/recording \ -H "Authorization: Bearer $NUTALK_API_KEY"
Location is a signed link to the audio, valid for 5 minutes.Range request/v1/calls/:id/terminateHang up a live call
Ends a call that is still queued, ringing or in_progress. A call on a NuTalk phone number (Telnyx) hangs up at once and is marked canceled (call_outcome: terminated_via_api). A website call hangs up from the visitor's page within a few seconds: the answer keeps its current status, and the call then closes with its real length.
calls:writePath parameters
iduuidrequiredThe call id.
Errors
404not_foundNo call with this id in your workspace (ids of other workspaces answer the same).409validation_errorThe call is not live (it already ended).501validation_errorThe call's carrier cannot be hung up from the API (Twilio or AiBox lines).502internal_errorThe carrier refused the hang-up.Plus the errors every request can return (401, 403, 429, 500): Errors.
curl -X POST https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6/terminate \ -H "Authorization: Bearer $NUTALK_API_KEY"
{
"success": true,
"data": {
"id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"status": "canceled",
"terminated_at": "2026-10-02T02:24:56.096Z",
"provider": "telnyx"
}
}/v1/calls/:id/notesList a call's notes
The notes the team added to the call, oldest first, as the call's page shows them, each with its author's name (never their email).
calls:readauthor is null for a note NuTalk wrote itself (a test call, an automation).
Path parameters
iduuidrequiredThe call id.
Errors
404not_foundNo call with this id in your workspace (ids of other workspaces answer the same).Plus the errors every request can return (401, 403, 429, 500): Errors.
curl https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6/notes \ -H "Authorization: Bearer $NUTALK_API_KEY"
{
"success": true,
"data": [
{
"id": "2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70",
"call_id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"content": "Asked for a callback on Friday.",
"type": "note",
"author": {
"id": "4c8e2a6b-1d3f-4e5a-9b7c-0f2d4a6c8e14",
"name": "María Pérez"
},
"created_at": "2026-10-02T02:20:00.000+00:00",
"updated_at": "2026-10-02T02:20:00.000+00:00"
}
]
}/v1/calls/:id/notesAdd a note to a call
Adds a note to the call, as the note box on the call's page does. The member who created the key writes it.
calls:writeWorkspace keys only · acts as the key's creatorPath parameters
iduuidrequiredThe call id.
Request body
contentstring · 1–5,000 charactersrequiredThe note's text.
Errors
400validation_errorThe body is not JSON, or a field is missing or invalid (details.errors lists each field).404not_foundNo call with this id in your workspace (ids of other workspaces answer the same).403forbiddenThe member who created the key doesn't add notes to calls (owners, admins, builders, operators and clinic managers do), has left the workspace, or doesn't see every call (calls off, assigned only, some numbers or agents): a key can't apply that limit call by call, so it writes notes on no call.Plus the errors every request can return (401, 403, 429, 500): Errors.
curl -X POST https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6/notes \
-H "Authorization: Bearer $NUTALK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Asked for a callback on Friday."
}'{
"success": true,
"data": {
"id": "2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70",
"call_id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"content": "Asked for a callback on Friday.",
"type": "note",
"author": {
"id": "4c8e2a6b-1d3f-4e5a-9b7c-0f2d4a6c8e14",
"name": "María Pérez"
},
"created_at": "2026-10-02T02:20:00.000+00:00",
"updated_at": "2026-10-02T02:20:00.000+00:00"
}
}/v1/calls/:id/notes/:note_idGet a call note
Gets one of the call's notes.
calls:readPath parameters
iduuidrequiredThe call id.
note_iduuidrequiredThe note id.
Errors
404not_foundNo call or note with this id in your workspace (ids of other workspaces answer the same).Plus the errors every request can return (401, 403, 429, 500): Errors.
curl https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6/notes/2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70 \ -H "Authorization: Bearer $NUTALK_API_KEY"
{
"success": true,
"data": {
"id": "2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70",
"call_id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"content": "Asked for a callback on Friday.",
"type": "note",
"author": {
"id": "4c8e2a6b-1d3f-4e5a-9b7c-0f2d4a6c8e14",
"name": "María Pérez"
},
"created_at": "2026-10-02T02:20:00.000+00:00",
"updated_at": "2026-10-02T02:20:00.000+00:00"
}
}/v1/calls/:id/notes/:note_idChange a call note
Changes a note's text. Only a note the key's creator wrote, as on the call's page.
calls:writeCan wait for approvalWorkspace keys only · acts as the key's creatorA key whose changes wait for approval holds the change until an owner or admin approves it.
Path parameters
iduuidrequiredThe call id.
note_iduuidrequiredThe note id.
Request body
contentstring · 1–5,000 charactersrequiredThe note's text.
Nothing changes yet: the call answers 202 with data.change_request, and runs once an owner or admin approves it in Settings → API keys. Follow it with GET /v1/change-requests/:id.
Errors
400validation_errorThe body is not JSON, or a field is missing or invalid (details.errors lists each field).404not_foundNo call or note with this id in your workspace (ids of other workspaces answer the same).403forbiddenThe member who created the key doesn't add notes to calls (owners, admins, builders, operators and clinic managers do), has left the workspace, or doesn't see every call (calls off, assigned only, some numbers or agents): a key can't apply that limit call by call, so it writes notes on no call.403forbiddendetails.reason: not_the_author: only whoever wrote a note edits or deletes it, and the key writes as the member who created it.Plus the errors every request can return (401, 403, 429, 500): Errors.
curl -X PATCH https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6/notes/2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70 \
-H "Authorization: Bearer $NUTALK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Asked for a callback on Monday instead."
}'{
"success": true,
"data": {
"id": "2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70",
"call_id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"content": "Asked for a callback on Monday instead.",
"type": "note",
"author": {
"id": "4c8e2a6b-1d3f-4e5a-9b7c-0f2d4a6c8e14",
"name": "María Pérez"
},
"created_at": "2026-10-02T02:20:00.000+00:00",
"updated_at": "2026-10-02T03:05:00.000+00:00"
}
}/v1/calls/:id/notes/:note_idDelete a call note
Deletes a note the key's creator wrote, as on the call's page.
calls:writeWaits for approvalWorkspace keys only · acts as the key's creatorA key whose deletes wait for approval holds the delete until an owner or admin approves it.
Path parameters
iduuidrequiredThe call id.
note_iduuidrequiredThe note id.
Unless its key was set not to wait, nothing is removed yet: the call answers 202 with data.change_request, and runs once an owner or admin approves it in Settings → API keys. Follow it with GET /v1/change-requests/:id.
Errors
404not_foundNo call or note with this id in your workspace (ids of other workspaces answer the same).403forbiddenThe member who created the key doesn't add notes to calls (owners, admins, builders, operators and clinic managers do), has left the workspace, or doesn't see every call (calls off, assigned only, some numbers or agents): a key can't apply that limit call by call, so it writes notes on no call.403forbiddendetails.reason: not_the_author: only whoever wrote a note edits or deletes it, and the key writes as the member who created it.Plus the errors every request can return (401, 403, 429, 500): Errors.
curl -X DELETE https://www.nutalk.ai/v1/calls/1ba11348-e440-44de-a5cd-5fe62f4d9df6/notes/2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70 \ -H "Authorization: Bearer $NUTALK_API_KEY"
{
"success": true,
"data": {
"id": "2b4d6f8a-0c1e-4a3b-9d5f-7e9a1c3b5d70",
"deleted": true
}
}Call statuses
| Status | Meaning |
|---|---|
queued | Accepted, not dialed yet. |
ringing | Dialing the number. |
in_progress | The call is connected. |
completed | The call ended after it was connected. |
failed | The carrier could not place or keep the call. |
canceled | Ended before completion (including POST /v1/calls/:id/terminate). |
voicemail | Answered by voicemail. |
no_answer | Nobody answered. |
busy | The line was busy. |
rejected | An inbound call NuTalk refused (for example, the channel had no free line). |
Status callback
When a call placed with status_callback_url ends and NuTalk has processed it, NuTalk sends one POST to that URL. It is not signed and is not retried; for signed events with retries, register a webhook for call.completed and call.failed. status is always completed; read the result in outcome.
{
"event": "call.completed",
"call_id": "1ba11348-e440-44de-a5cd-5fe62f4d9df6",
"channel_id": "9f19ef32-eaef-452c-bebc-14d9759cdb64",
"to": "+14155551234",
"from": "+525512345678",
"direction": "outbound",
"status": "completed",
"outcome": "answered",
"duration_seconds": 180,
"completed_at": "2026-10-02T14:33:00.000Z"
}