sinch voice is the Voice API v2 surface — calls and services. The older Voice API v1 is still reachable, one level down, under sinch voice v1:
sinch voice
├── calls — list, get, place, bridge, hang up and patch calls
├── services — inbound call routing
└── v1 — Voice API v1
├── applications — callbacks + number assignments
├── callouts — outbound calls (TTS, conference dial-in, custom SVAML)
├── calls — query and control in-progress calls
└── conferences — manage conference rooms and participantsThe two trees are separate APIs, not two versions of the same commands:
- Credentials. v2 authenticates as the project with the Access Key ID + Secret. v1 needs voice application credentials (Application Key + Application Secret), which you supply per command with
-a, --app-keyor through theSINCH_APPLICATION_KEY/SINCH_APPLICATION_SECRETenvironment variables. See Authentication. - Identifiers. v2 call IDs are ULIDs and are not interchangeable with v1 call IDs.
- Options. The 17
v1leaf commands accept-a, --app-key <key>where applicable — it defaults to the authenticated app. The 11 v2 leaf commands do not: v2 has no concept of a voice application. - Scope. v2 covers list, get, create, bridge, hangup and patch.
patchhas no v1 equivalent: v1 can end a leg and play into one, but it cannot add a leg to a call that is already up. There is still no v2 equivalent ofsinch voice v1 calls manage-leg— v2 addresses legs by name inside apatch, not by acaller/calleeposition.
The four v1 groups mirror the @sinch/voice SDK's four namespaces.
All 28 leaf commands accept --json for machine-readable output.
Place and inspect Voice API v2 calls.
List v2 call records for the project. All filters are optional and combine.
sinch voice calls list [options]| Option | Description |
|---|---|
--service-id <id> | Only calls handled by this service |
--from <number> | Only calls from this origin |
--to <number> | Only calls to this destination |
--call-type <type> | PHONE, SIP, STREAM or VOICE_RELAY (upper-cased before sending, so lowercase is fine) |
--call-result <result> | QUEUED, INITIATED, IN_PROGRESS, COMPLETED, REJECTED, NO_ANSWER, CANCEL, BUSY or FAILED (also upper-cased) |
--start-time <iso> | Calls that started at or after this RFC 3339 timestamp |
--end-time <iso> | Calls that ended before this RFC 3339 timestamp (exclusive) |
-l, --limit <number> | Page size, 1–100 (default 20) |
--page <number> | Page number, 1-based (default 1) |
--json | Output as JSON |
The API pages at 20, so a bare list on a busy project shows one page. The table output prints the total call and page count underneath; --json prints the calls array only, without those totals.
Example
sinch voice calls list --service-id svc-abc123 --call-result COMPLETED --limit 100Get one v2 call record. The table prints the whole call resource — project, service, session, bridge and batch IDs, direction, type, origination type, endpoints, result and reason, timestamps, duration, the per-minute rate and the call's resource URL. --json emits the API object untouched.
sinch voice calls get <call-id> [--json]| Option | Description |
|---|---|
<call-id> | Call ID (ULID), as shown by sinch voice calls list |
--json | Output as JSON |
This is not
sinch voice v1 calls get, which takes a v1 call ID. It is also not thesessionIdreturned bycalls create— see below.
Place a v2 call, optionally bridging a media stream or the Voice Relay onto it.
sinch voice calls create <to> [options]| Option | Description |
|---|---|
<to> | Destination: an E.164 number (+15551234567) or a sip: / sips: URI |
--from <origin> | Caller identity presented to the callee (E.164 or SIP address) |
--stream <endpoint> | Bridge a WebSocket audio leg (wss://…). Mutually exclusive with --relay |
--relay <endpoint> | Bridge a Voice Relay leg — platform STT/TTS (wss://…). Mutually exclusive with --stream |
--sample-rate <hz> | Stream sample rate: 8000, 16000, 24000 or 48000 (default 16000; --stream only) |
--tts-voice <name> | Platform TTS voice (default Emma; --relay only) |
--stt-language <tag> | Speech-to-text BCP-47 tag (default en-US; --relay only) |
--no-interruptions | Disable barge-in over the platform TTS (--relay only). Omit it and the platform default stands |
--header <key=value> | Custom call header, repeatable. Only the first = splits, so values may contain = |
--bridge-name <name> | Name of the bridge joining the two legs (default bridge) |
--timeout <seconds> | Seconds to wait for an answer (max 60) |
--max-duration <seconds> | Maximum call duration in seconds (max 14400) |
--dry-run | Print the request body that would be sent, without placing a billable call |
--json | Output as JSON |
--stream and --relay take a full ws:// / wss:// URL when run from the CLI. The relative /path form the underlying package accepts is resolved against a function's public URL, which the CLI does not have — passing a path here fails with "this function's public URL is unknown".
Naming no events (no --stream, no --relay) hands the call to the webhook configured on the Voice service (callBehavior: WEBHOOK). --stream and --relay name events, so those calls are answered inline instead.
Example
sinch voice calls create +15551234567 --from +15559876543
sinch voice calls create +15551234567 --stream wss://example.com/audio --sample-rate 8000
sinch voice calls create +15551234567 --relay wss://example.com/relay --tts-voice Emma
sinch voice calls create sip:agent@pbx.example.com --dry-runCall two parties and bridge them together. <a> is dialled first; <b> only once <a> answers.
sinch voice calls bridge <a> <b> [options]| Option | Description |
|---|---|
<a> | First party, dialled first: an E.164 number or a sip: / sips: URI |
<b> | Second party, dialled once <a> answers |
--from <origin> | Caller identity presented to both parties |
--bridge-name <name> | Name of the bridge joining the two legs (default bridge) |
--timeout <seconds> | Seconds to wait for an answer (max 60) |
--max-duration <seconds> | Maximum call duration in seconds (max 14400) |
--dry-run | Print the request body, without placing a billable call |
--json | Output as JSON |
The two legs' lifetimes are tied together: either one hanging up, and every way <b> fails to connect, ends the other. The bridge name matters afterwards — calls patch --dial joins the same bridge and defaults to the same name, so a bridge left unnamed here needs no --bridge-name there either.
Example
sinch voice calls bridge +15551110000 +15552220000 --from +15559876543
sinch voice calls bridge +15551110000 sip:agent@pbx.example.com --dry-runEnd one or more legs of a live call.
sinch voice calls hangup <target> [options]| Option | Description |
|---|---|
<target> | Call ID, or a session ID when --call-name is given |
--call-name <name> | Leg the commands run in — makes <target> a session ID |
--leg <name> | Leg to end, repeatable. Ends the addressed leg when omitted |
--dry-run | Print the commands that would be sent, without touching the call |
--json | Output as JSON |
Ending a bridged leg does not necessarily end the call. Legs placed by this CLI tie their lifetimes together, so ending one usually takes the other with it — but a leg added later by calls patch may not. Name every leg you mean to end.
Example
sinch voice calls hangup 01JD3Q2XYZ
sinch voice calls hangup sess-abc123 --call-name origin --leg origin --leg streamRun commands against a live call. --dial is the capability v1 has no equivalent of: it rings a third party and joins them to the bridge the call is already using, so everything already on that bridge — the other party, a media stream, the Voice Relay — stays attached through the handover. That is a transfer without dropping anyone, and it is the same operation a voice agent runs when it escalates to a human.
sinch voice calls patch <target> [options]| Option | Description |
|---|---|
<target> | Call ID, or a session ID when --call-name is given |
--call-name <name> | Leg the commands run in — makes <target> a session ID |
--dial <destination> | Add a party to the call: an E.164 number or a sip: / sips: URI |
--end-leg <name> | Leg to end if --dial never answers, or hangs up afterwards. Strongly recommended |
--dial-leg <name> | Name for the leg --dial creates (default transfer) |
--bridge-name <name> | Bridge the live legs are already in (default bridge) |
--from <origin> | Caller identity presented to the dialled party |
--timeout <seconds> | Seconds to wait for --dial to answer (default 30, max 60) |
--commands <source> | Raw v2 command list: inline JSON, @file.json, or - for stdin |
--dry-run | Print the commands that would be sent, without touching the call |
--json | Output as JSON |
Exactly one of --dial or --commands is required. --commands is the escape hatch for anything the flags do not cover; it is passed through unvalidated, so the API is what rejects a malformed command.
Naming any outcome inline detaches the service webhook from all of them. So if --dial is busy or never answers, nothing at all runs, and whoever was just told they are being put through sits on a live, billed call with nobody on the other side of the bridge. --end-leg names the leg to end in that case — usually the leg you addressed.
v2 identifies a live call two ways, and they are alternatives rather than a pair:
| Form | Sends to |
|---|---|
patch <call-id> | PATCH /calls/{callId} |
patch <session-id> --call-name <leg> | PATCH /sessions/{sessionId}/calls/{callName} |
calls create and calls bridge print a session ID; calls list prints call IDs.
Example
sinch voice calls patch sess-abc123 --call-name origin --dial +15550001111 --end-leg origin
sinch voice calls patch 01JD3Q2XYZ --dial sip:agent@pbx.example.com --bridge-name agent
echo '[{"command":"hangup"}]' | sinch voice calls patch 01JD3Q2XYZ --commands -POST /calls — what calls create sends — returns a sessionId (plus serviceId, and batchId where applicable). calls get takes a callId, a ULID. They are different identifiers, and feeding the sessionId to calls get returns 404. To find the call the session produced:
sinch voice calls list --service-id <service-id>The v2 sip.endpoint schema is pattern: "^sips?:", so the scheme is required: sip:agent@pbx.example.com. A bare agent@pbx.example.com is rejected.
Voice v1 is the exact opposite. sinch voice v1 callouts wants a bare address and fails a scheme-prefixed one with CONGESTION. Nothing on the wire distinguishes these two cases, so a working v1 destination string copied into voice calls create (or the reverse) fails, and the error does not say why.
The scheme also picks the transport when none is sent explicitly: sip: means UDP, sips: means TLS. --dry-run shows no transport key in that case, because the server is applying the default rather than the CLI.
A service is the v2 object that owns inbound call routing. A function opts into it by declaring VOICE_SERVICE_ID; sinch functions deploy then points the service's webhook at the deployed function.
Voice v2 signs webhooks with Authorization: service <serviceId>:<HMAC-SHA256> plus an x-timestamp header. The runtime verifies the signature once VOICE_SERVICE_SECRET is set; until then it allows the callback through with a warning, because Sinch does not yet expose the per-service secret.
List the v2 services in this project — IDs, which one is the default, and where each one routes inbound calls.
sinch voice services list [--json]GET /services does not return callBehavior, so the command reads each service back to fill the webhook column in. --json adds a derived webhookUrl alongside the untouched callBehavior, so a script can read the URL without walking the union.
Get one service, including its full callBehavior — the webhook URL and fallback, or the static SVAML script.
sinch voice services get <service-id> [--json]| Option | Description |
|---|---|
<service-id> | Service ID, as shown by sinch voice services list |
--json | Output as JSON, exactly as the API returned it |
Create a service.
sinch voice services create [options]| Option | Description |
|---|---|
--name <name> | Service name. Prompts if omitted and interactive |
--description <text> | Service description |
--webhook-url <url> | Inbound call webhook URL (sets callBehavior to WEBHOOK) |
--json | Output as JSON (also implies non-interactive — --name is required) |
--non-interactive | Skip prompts, fail if --name is missing |
Without --webhook-url the service is created with no webhook, and inbound calls to it reach nothing — the command warns and prints the services update line to fix it.
Example
sinch voice services create --name "Support line" --webhook-url https://my-function.fn.sinch.comPoint a service's inbound webhook at a URL. This is the same call sinch functions deploy makes for a function that declares VOICE_SERVICE_ID, and it sets callBehavior to WEBHOOK — the mode inbound calls require.
sinch voice services update <service-id> --webhook-url <url> [--json]| Option | Description |
|---|---|
<service-id> | Service ID |
--webhook-url <url> | Inbound call webhook URL (required) |
--json | Output as JSON |
Delete a service. Prompts for confirmation by default; every function routed through the service stops receiving inbound calls.
sinch voice services delete [service-id] [--force] [--non-interactive] [--json]| Option | Description |
|---|---|
[service-id] | Service ID. Prompts with a picker when omitted and interactive |
--force | Skip the confirmation prompt |
--json | Output as JSON (also implies non-interactive — the ID is required) |
--non-interactive | Skip prompts, fail if the service ID is missing |
Get the webhook callback URLs configured for a Voice application.
sinch voice v1 applications callbacks get [options]| Option | Description |
|---|---|
-a, --app-key <key> | Application key (defaults to the authenticated app) |
--json | Output as JSON |
--curl | Also print the equivalent curl command |
Set the primary (and optionally fallback) callback URL for a Voice application.
sinch voice v1 applications callbacks set [url] [options]| Option | Description |
|---|---|
[url] | Primary URL. Prompts if omitted and interactive. |
-a, --app-key <key> | Application key |
--primary-only | Only set the primary URL, leave fallback unset |
--fallback <url> | Explicit fallback URL (default: same as primary) |
--non-interactive | Fail if URL is missing instead of prompting |
--debug | Print full error details on failure |
--json | Output as JSON |
Example
sinch voice v1 applications callbacks set https://my-function.fn.sinch.comNOTE: These commands manage numbers attached to a Voice application — i.e. which DIDs route to this app's webhook. This is not the same as the account-wide number inventory managed by
sinch numbers.
List voice-capable numbers on your account and their current application assignments. Pass -a to filter client-side to numbers bound to a specific app.
sinch voice v1 applications numbers list [options]| Option | Description |
|---|---|
-a, --app-key <key> | Filter to numbers currently routed to this app |
--json | Output as JSON |
Query metadata (country, type, rate) for any phone number. Returns number details, not app-routing information.
sinch voice v1 applications numbers get <number> [--json]Assign one or more numbers to a Voice application.
sinch voice v1 applications numbers assign <numbers...> [-a <key>] [--capability voice|sms] [--json]Un-assign a number from a Voice application.
sinch voice v1 applications numbers unassign <number> [-a <key>] [--capability voice|sms] [--json]Initiate outbound voice calls.
Place a text-to-speech outbound call.
sinch voice v1 callouts tts <destination> <text> [options]| Option | Description |
|---|---|
<destination> | Phone number (+15551234567), sip:addr, or bare username |
<text> | Text to speak (≤600 characters) |
--voice <locale> | TTS voice or locale (default en-US) |
--cli <number> | Caller ID to display to the callee |
--dtmf <seq> | DTMF tones after pickup (0-9, #, w = 500ms pause) |
--domain <domain> | pstn or mxp (default: inferred from destination) |
--enable-ace | Request an ACE callback when the call is answered |
--enable-dice | Request a DiCE callback when the call disconnects |
--enable-pie | Request a PIE callback after menu interaction |
--json | Output as JSON |
Example
sinch voice v1 callouts tts +15551234567 "Your package is arriving today" --voice en-USCall a phone number and connect them to a conference room. Creates the conference if it doesn't exist.
sinch voice v1 callouts conference <destination> <conference-id> [options]| Option | Description |
|---|---|
--cli <number> | Caller ID |
--greeting <text> | Spoken greeting before joining |
--locale <locale> | TTS locale for the greeting |
--moh <music> | Music on hold while alone: ring, music1, music2, music3 |
--max-duration <seconds> | Maximum call duration |
--dtmf <seq> | DTMF tones after pickup |
--domain <domain> | pstn or mxp |
--json | Output as JSON |
Place a custom callout driven by inline SVAML (Sinch Voice Application Markup Language). At least one of --ice, --ace, or --pie is required.
sinch voice v1 callouts custom [destination] [options]| Option | Description |
|---|---|
--ice <svaml> | ICE SVAML: inline JSON string or @path/to/file.json |
--ace <svaml> | ACE SVAML: inline JSON or @file.json |
--pie <svaml> | PIE SVAML: inline JSON or @file.json |
--cli <number> | Caller ID |
--dtmf <seq> | DTMF tones after pickup |
--max-duration <seconds> | Maximum call duration |
--json | Output as JSON |
Example
sinch voice v1 callouts custom +15551234567 --ice @./ice-payload.jsonQuery and control in-progress calls. These methods apply to PSTN / SIP calls only.
NOTE — this is not
sinch voice calls. Both trees have acalls get, and they take different identifier types.sinch voice v1 calls gettakes a v1 call ID (the leg ID from a v1 callout, ICE/ACE/DiCE callback, orconferences get).sinch voice calls gettakes a ULID fromsinch voice calls list. An ID from one API returns404on the other. Both trees can control a live call, and v2 can do one thing v1 cannot:sinch voice calls patch --dialadds a party to a call that is already up. See sinch voice calls.
Get information about an ongoing or completed call.
sinch voice v1 calls get <call-id> [--json]Hang up an in-progress call (thin wrapper around voice v1 calls update with the hangup SVAML action).
sinch voice v1 calls hangup <call-id> [--json]Send a SVAML body to an in-progress call.
sinch voice v1 calls update <call-id> --svaml <json-or-@file> [--json]| Option | Description |
|---|---|
<call-id> | The unique call ID |
--svaml <svaml> | SVAML body: inline JSON or @path/to/file.json (required) |
--json | Output as JSON |
The body is a JSON object with two fields:
action— required. The top-level call-control directive that runs after all instructions finish. An empty or missingactionis rejected witherrorCode: 40001 "SVAML 'action' must not be empty."instructions— optional. An array of things to run before the action (say text, play a file, start recording, etc.).
These are actions confirmed against a live call on a PSTN leg. The endpoint accepts only a narrow subset of the full SVAML action list — actions that re-route a call (for example connectPstn, connectMxp, connectConf) are rejected with errorCode: 40003 "Unknown SVAML action '<name>'" and are intended for initial-call routing via ICE callback responses, not for runtime updates of a connected call.
| Action | Works? | Notes |
|---|---|---|
{"name": "hangup"} | ✅ | Terminates the call. calls get afterwards shows reason: MANAGERHANGUP. |
{"name": "continue"} | ✅ | No-op "keep going" action. Used as a placeholder when instructions do the real work. |
{"name": "Say", ...} | ❌ | Say is an instruction, not an action. See example below for the correct wrapping. |
{"name": "connectPstn", ...} | ❌ | Re-routing is not supported on runtime updates. Use callouts custom to place a new leg instead. |
{"name": "park"} | ❌ | Not recognized on this endpoint. |
Case variants of connectPstn (ConnectPstn, ConnectPSTN, connectPSTN) all return the same 40003 error — the server preserves the name verbatim in the response.
sinch voice v1 calls update <call-id> --svaml '{"action":{"name":"hangup"}}'Or equivalently, the thin wrapper:
sinch voice v1 calls hangup <call-id>Say is an instruction and must be wrapped with a required action. The minimal "speak and keep going" shape:
sinch voice v1 calls update <call-id> --svaml '{
"instructions": [
{"name": "Say", "text": "Your call is being transferred.", "locale": "en-US"}
],
"action": {"name": "continue"}
}'Note on conference audio paths: If the target call is a leg inside a conference, the server will accept this request (HTTP 200) but the Say audio may not be audible to the participant because the leg is mixed through the conference audio path. To play audio to a specific conference participant, use
sinch voice v1 calls manage-legwith--leg calleeinstead (that endpoint is designed for per-leg audio injection).
Inject audio into a specific leg of a connected call. Intended for playing sound or speaking to one side of a PSTN / SIP call without affecting the other, including inside a conference.
sinch voice v1 calls manage-leg <call-id> --leg caller|callee|both --svaml <json-or-@file> [--json]| Option | Description |
|---|---|
<call-id> | The unique call ID |
--leg <leg> | Which leg receives the audio: caller, callee, or both (required) |
--svaml <svaml> | SVAML body with PlayFiles or Say instructions (required) |
--json | Output as JSON |
The manage-leg endpoint is the SVAML-injection counterpart to calls update: instead of controlling call-control state, it plays audio into a specific leg's RTP path. Per the Sinch API docs only PlayFiles and Say instructions are valid here; other instruction types will be rejected by the server.
Known gap: The exact request-body layout for
SayandPlayFileson this endpoint was not fully reverse-engineered during the voice CLI rewrite's smoke testing — several payload variants returned400 Bad Request. See the Sinch SVAML reference at </docs/voice/api-reference/svaml/> for the authoritative shapes. Known-working examples will be added here as they are confirmed in production.
Manage conference rooms and their participants. Participants are identified by their per-leg call ID (from conferences get), not by phone number.
Get the participant list for a conference.
sinch voice v1 conferences get <conference-id> [--json]Remove a single participant from a conference.
sinch voice v1 conferences kick <conference-id> <call-id> [--json]Remove all participants from a conference. Prompts for confirmation by default.
sinch voice v1 conferences kick-all <conference-id> [--yes] [--non-interactive] [--json]Mute/unmute or hold/resume a conference participant. Exactly one action flag is required.
sinch voice v1 conferences manage <conference-id> <call-id> [--mute|--unmute|--hold|--resume] [--moh <music>] [--json]To play audio or speak text to a participant (as opposed to mute/hold them), use sinch voice v1 calls manage-leg instead — those operations hit a different SDK method.
sinch voice calls and sinch voice services authenticate as the project with the Access Key ID + Access Key Secret — the same credentials .env exposes as PROJECT_ID_API_KEY / PROJECT_ID_API_SECRET, stored by sinch auth login. calls create sends them as HTTP Basic; the read and CRUD endpoints (calls list/get, services *) exchange them for an OAuth2 token like the CLI's other REST clients. No application key is involved, which is why none of these commands takes -a, --app-key.
sinch voice v1 commands (applications, callouts, calls, conferences) use the Application Key + Application Secret instead. sinch auth login no longer asks for that pair, so supply it per command with -a, --app-key <key>, or set SINCH_APPLICATION_KEY and SINCH_APPLICATION_SECRET in the environment.
Voice templates published by the template registry are Voice v2. They route inbound calls through a service rather than a voice application, so they carry VOICE_SERVICE_ID and no application key pair.