Calls
Place outbound calls with bidirectional audio streaming, and retrieve call records. Calls are scoped to your agent identity and span both origins — the dedicated phone numbers your organization owns (past and present) and your agent identity's shared iMessage line.
When you place a call, Inkbox dials the to_number and opens a WebSocket media stream to the client_websocket_url you provide. Your agent connects to that URL to send and receive audio (or text, depending on the pipeline mode). See Media Stream for details on pipeline modes. Alternatively, place the call with mode="hosted_agent" and Inkbox runs Inkbox Voice AI on the call — no WebSocket required.
Place call POST
POST /place-callInitiate an outbound call. Inkbox dials the to_number and opens a bidirectional media stream to your client_websocket_url.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
origination | string | No | "dedicated_number" (default) or "shared_imessage_number". Selects whether the call goes out from a dedicated number you own or from your agent identity's shared iMessage line |
from_number | string | null | Conditional | Caller ID in E.164 — REQUIRED for dedicated_number origination and must be an active number in your org. Ignored for shared_imessage_number (the outbound line is resolved automatically from the identity's active iMessage assignment to the recipient) |
to_number | string | Yes | Destination number in E.164 format |
agent_identity_id | UUID | null | Conditional | The agent identity placing the call. Required for shared_imessage_number when using an admin API key or the Inkbox Console; identity-scoped API keys resolve their own identity automatically (naming a different one returns 403) |
client_websocket_url | string | null | No | WebSocket URL (wss://) your agent listens on. Can carry text or audio, depending on how the connection is configured. Falls back to the identity's configured client_websocket_url (see Incoming calls) if omitted (400 if none is set). Must be absent when mode is "hosted_agent" (422) |
mode | string | No | Who drives the call: "client_websocket" (default — your agent, over the media stream) or "hosted_agent" (Inkbox Voice AI) |
reason | string | null | Conditional | The voice agent's task brief — what this call is for, in plain language. REQUIRED with mode="hosted_agent" (non-empty, max 2,000 characters); invalid with mode="client_websocket" (422) |
Request example
To place the call from your agent identity's shared iMessage line, omit from_number — the outbound line is resolved automatically:
To have Inkbox Voice AI run the call, pass mode and a reason instead of a WebSocket URL:
Response (200)
The place call response includes a rate_limit object with your organization's rolling 24-hour usage snapshot (calls_used, calls_remaining, calls_limit, minutes_used, minutes_remaining, minutes_limit).
For shared_imessage_number origination the response has "origin": "shared_imessage_number" and local_phone_number is null — the shared line is internal routing detail and is never surfaced.
Error responses
| Status | Description |
|---|---|
| 400 | from_number is not active, or no WebSocket URL is available |
| 403 | Recipient rejected for policy reasons (also returned if an identity-scoped key names a different identity) |
| 404 | from_number not found |
| 409 | (shared line) No active shared connection to the recipient, the agent identity is not active, or the agent identity is not iMessage-enabled |
| 422 | Cannot call the number itself; for shared lines, also returned when the line is not voice-enabled or not verified. Also returned for mode/reason shape violations: mode="hosted_agent" without a reason (or with a client_websocket_url), or a reason with mode="client_websocket" |
| 429 | Organization has exceeded 100 calls or 100 minutes in the last 24 hours. Response includes Retry-After (seconds), X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers |
| 502 | Upstream dial failed |
| 503 | hosted_agent_at_capacity — Voice AI capacity is momentarily saturated, retry shortly; or hosted_agent_unavailable — Inkbox Voice AI is not available. hosted_agent mode only |
Code examples
List calls GET
GET /callsList calls for an agent identity, newest first — spans both dedicated numbers and shared iMessage lines.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
agent_identity_id | UUID | — | The agent identity whose calls to list. Required for admin API keys and Console sessions (omitting it returns 422); identity-scoped keys resolve their own automatically and cannot name another (403) |
limit | integer | 50 | Number of results to return (1–200) |
offset | integer | 0 | Pagination offset |
is_blocked | boolean | — | Filter by blocked state. true returns only blocked rows, false returns only non-blocked rows, and omitting it applies the caller's default visibility |
start_datetime | string | — | Only calls created on or after this date/time |
end_datetime | string | — | Only calls created on or before this date/time; a bare date includes the whole day |
tz | string | UTC | IANA zone for interpreting zone-less start_datetime/end_datetime values. Defaults to UTC |
Identity-scoped API keys never see contact-rule-blocked calls, regardless of is_blocked. Admin API keys and human sessions see blocked and non-blocked calls by default; use is_blocked=true for an admin-side blocked listing.
start_datetime, end_datetime, and tz filter on created_at. Both bounds are optional and non-breaking — omit them for unchanged behavior. See Filtering by date.
Response (200)
Code examples
The number-scoped listing is deprecated.
GET /numbers/{phone_number_id}/callsonly returns calls on that dedicated number and omits shared iMessage calls. It accepts the samestart_datetime/end_datetime/tzdate filters for parity. UseGET /callsinstead.
Get call GET
GET /calls/{call_id}Get a single call by ID. Addresses calls of either origin — dedicated numbers or shared iMessage lines.
Path parameters
| Parameter | Type | Description |
|---|---|---|
call_id | UUID | Unique identifier of the call |
Response (200)
Returns a call object.
Error responses
| Status | Description |
|---|---|
| 404 | Call not found (for identity-scoped keys, cross-identity reads are masked as 404) |
Code examples
The number-scoped route is deprecated.
GET /numbers/{phone_number_id}/calls/{call_id}cannot address shared iMessage calls. UseGET /calls/{call_id}.
Hang up call POST
POST /calls/{call_id}/hangupEnd a live call from outside it. The agent on the call can always end it in-band over the media WebSocket; this endpoint is the lever for everything else — tests, operator tooling, or any process that only holds the call ID. It works identically on Inkbox Voice AI calls, where it's the operator kill switch.
The carrier confirms the teardown asynchronously, so the returned call may still show its live status for a moment; the final status, ended_at, and transcript land through the normal call lifecycle (including the call.ended webhook).
Path parameters
| Parameter | Type | Description |
|---|---|---|
call_id | UUID | Unique identifier of the call |
Response (200)
Returns the call object as of the hangup request, normally with hangup_reason set to "local". If the call ended on its own at the same moment — the carrier reports it already over before the hangup lands — the request still returns 200, but the call closes with hangup_reason "remote". Don't branch on hangup_reason to confirm the hangup took effect.
Error responses
| Status | Description |
|---|---|
| 404 | Call not found (for identity-scoped keys, cross-identity calls are masked as 404) |
| 409 | call_already_ended — the call already finished; or call_not_active — the call has no active carrier leg yet |
Code examples
Call object
| Field | Type | Description |
|---|---|---|
id | UUID | Unique call identifier |
origin | string | "dedicated_number" or "shared_imessage_number" |
local_phone_number | string | null | Your number (E.164) for dedicated_number calls. null for shared_imessage_number calls — the shared line is never surfaced. |
remote_phone_number | string | The other party's number (E.164) |
direction | string | "outbound" or "inbound" |
status | string | Call status (see below) |
client_websocket_url | string | null | WebSocket URL the call was streamed to |
mode | string | Who drove the call: "client_websocket" or "hosted_agent" |
reason | string | null | The voice agent's task brief on outbound hosted_agent calls; null otherwise |
post_call_action_items | array | Open action items Inkbox Voice AI recorded, ordered by seq — each { id, seq, action, details, status }, the same shape the call.ended webhook delivers. Always present; empty for client_websocket calls and Voice AI calls with none |
use_inkbox_tts | boolean | null | Whether Inkbox handles text-to-speech (null until negotiated via WebSocket) |
use_inkbox_stt | boolean | null | Whether Inkbox handles speech-to-text (null until negotiated via WebSocket) |
hangup_reason | string | null | Why the call ended: "local", "remote", "max_duration", "voicemail", "rejected", or "failed" |
started_at | string | null | When the call was answered (ISO 8601) |
ended_at | string | null | When the call ended (ISO 8601) |
is_blocked | boolean | Whether the call was rejected by a contact rule or by default-block in whitelist mode. Identity-scoped API keys never receive rows where this is true |
created_at | string | Creation timestamp (ISO 8601) |
updated_at | string | Last updated timestamp (ISO 8601) |
Call statuses
| Status | Description |
|---|---|
initiated | Call created, dialing in progress |
ringing | Remote party is ringing |
answered | Call connected and active |
completed | Call ended normally |
canceled | Call ended before being answered |
failed | Call could not be connected |