The Sinch CLI (@sinch/cli) puts the Sinch platform in one terminal command: sinch. You can get a phone number, route calls to a Voice API v2 service, build and deploy a Sinch Function, send a message over the Sinch Conversation API, and then script the same steps in CI with --json output and predictable exit codes.
The same commands work for coding agents. Claude Code, Kiro, Cursor, and GitHub Copilot all work through a terminal, so they can run sinch just like you do. They use --help to find commands, --json to read structured output, and exit codes to decide what to do next, without assembling raw API requests. Ask your agent to rent a number and point it at your function, and it can do that with the commands in this post.
This post walks one path from start to finish. You'll get a number, create a Voice service, scaffold a voice function, test it locally, deploy it, and place a call that speaks to you. After that comes messaging, the rest of the products the CLI covers, automation, and how to give your coding agent Sinch-specific skills.
You'll need
- Node.js 24 or newer
- A Sinch account and a project in the Sinch Build Dashboard
- A project ID, access key ID, and access key secret from the project's Access Keys page
- A Conversation App with at least one channel configured, for the messaging section
Use a development project for this walkthrough. The steps below repoint a Voice service's webhook, and you don't want that happening to a service that takes real customer calls.
Install and authenticate
npm install -g @sinch/cli
sinch auth login
auth login asks for your project ID, access key ID, and access key secret, then verifies the key pair. That one login covers Numbers, Voice API v2, Functions, the Sinch Conversation API, fax, SIP trunking, and porting.
Credentials go into your OS keychain: macOS Keychain, Windows Credential Manager, or Linux Secret Service. Short-lived session tokens go in ~/.sinch/. Check what's stored:
sinch auth status
Get a phone number
Search for a number with voice capability, rent it, and confirm it's active:
sinch numbers available search --region US --type LOCAL --capabilities SMS VOICE
sinch numbers available rent +12025550134
sinch numbers active list
active list shows each number with its capabilities:
+1 202-555-0134 [US] LOCAL SMS, VOICE
Some regions need supporting documents, such as proof of address or business identity, before a number can go live. Search results tag those numbers [docs required]. If a rental needs documents the CLI switches to a guided KYC order and runs that region's questionnaire, which it fetches from the API at run time. You can also start an order directly:
sinch numbers order create --region DE --type LOCAL
Create a Voice service
Voice API v2 routes inbound calls through a service. A service holds one setting that matters here: The webhook URL that receives call events. List what your project already has:
sinch voice services list
The list marks one service as the project default. Calls you place with sinch voice calls create run through the default service, so for this walkthrough use that one. If the project has no services yet, create one:
sinch voice services create --name "Support line"
The CLI prints the new service ID, a sinch voice services update command for setting the webhook, and the VOICE_SERVICE_ID=<id> line that routes a function to the service. You don't need to set a webhook yourself: The function steps below do it for you.
Build a voice function
Sinch Functions run your call-handling code on Sinch. The CLI covers the whole loop: Scaffold, test locally, deploy, and watch the logs.
sinch functions init simple-voice-ivr --name my-function
cd my-function
init uses the Node.js runtime for this template (add --runtime csharp for C#) and asks four questions:
- Company name. This goes into the voice prompts.
- Voice service. This is where the function receives calls. Pick the default service from the previous step.
-
Auto-configure voice integration on deploy. Say yes. This is what lets
deploypoint the service at your function. - Database. Pick No database for this example.
Then it installs dependencies and prints the next steps. Run sinch templates list to see the rest of the catalog: IVRs, call bridging, number masking, SMS and RCS responders, and realtime AI voice agents.
Make it speak on an outbound call
The template's voiceWebhook handles inbound calls with an incoming handler that plays a menu. For outbound calls, add an answered handler to the same onCall in function.ts:
export const voiceWebhook = onCall({
// incoming, manage, and completed from the template stay as they are
answered: (_call, builder) =>
builder.say('Your package is arriving today.').hangup(),
});
When a call you place is answered, Voice API v2 sends a call.answered event to the service webhook. The function replies with the commands to run: Say the message, then hang up. That's why calls create has no --say flag. The function decides what happens on the call.
Test it locally
sinch functions dev
dev builds the function, runs it on localhost:3000, and reloads on every save. Send it the event Voice API v2 would send and look at the commands it returns:
curl -X POST http://localhost:3000/ \
-H 'Content-Type: application/json' \
-d '{"event":"call.answered","callId":"01TEST","sessionId":"01TEST"}'
{
"commands": [
{
"command": "messages",
"messages": [
{ "type": "SAY", "say": { "text": "Your package is arriving today.", "voiceName": "Emma" } }
],
"events": {
"onFinish": [{ "command": "hangup" }],
"onFailure": [{ "command": "hangup" }]
}
}
]
}
dev --tunnel goes one step further. It opens a Sinch tunnel and points your Voice service's webhook at your laptop, so real calls reach the local server before you deploy anything. The service keeps pointing at the tunnel after you stop dev, so run deploy afterwards to move it back to the function.
Deploy it
Stop dev with Ctrl+C and deploy:
sinch functions deploy
deploy packages the source, audits the npm dependencies, and rolls it out. Because auto-configuration is on, it also points the Voice service webhook at the deployed function's URL. Check it with sinch voice services list.
Place the call
To see the request before it costs anything, add --dry-run. It prints the exact request body without placing the call:
sinch voice calls create +15551234567 --from +12025550134 --dry-run
{
"commands": [
{
"command": "dial",
"callName": "origin",
"from": { "type": "PHONE", "phone": { "number": "+12025550134" } },
"to": { "type": "PHONE", "phone": { "number": "+15551234567" } }
}
]
}
bridge, hangup, and patch take --dry-run, too. SIP destinations take the scheme, such as sip:agent@pbx.example.com, and the dry run shows how they're sent. Drop the flag to place the call:
sinch voice calls create +15551234567 --from +12025550134
Use your own phone as the destination. --from is the voice-capable number from earlier. Answer and you'll hear "Your package is arriving today."
create prints a session ID, the service that handled the call, and the command to look the call up:
Session ID: 01M3MWAGB1JPKE7VVBVN36BYG1
Service: YOUR_SERVICE_ID
Inspect with: sinch voice calls list --service-id YOUR_SERVICE_ID
The session ID is not a call ID. To inspect a single call, list the service's calls and use the ULID call ID from that list:
sinch voice calls list --service-id YOUR_SERVICE_ID
sinch voice calls get YOUR_CALL_ID
Call ID Dir Type From To Result Duration
---------------------------------------------------------------------------------------
01M3MWAGB6HH85Y9GMT8V79D57 out PHONE +12025550134 +15551234567 COMPLETED 4s
calls list filters with --call-result, --call-type, --from, --to, --start-time, and --end-time, all optional and combinable. sinch voice calls also has bridge to connect two parties, hangup to end legs of a live call, and patch to add a party to a call that's already up.
Watch the logs
sinch functions logs --follow
--follow streams each request the function handles, with its response and any console.log output. The call you just placed shows up as two events:
Time Method Status Duration URL
────────────────────────────────────────────────────────────────────────────────
22:47:02 POST 204 1ms /webhook/voice [call.hangup]
22:46:59 POST 200 1ms /webhook/voice [call.answered]
For an interactive view where the arrow keys move between requests and Enter expands one, use sinch functions logs --interactive. Filter with --level, --search, or --since.
The rest of the lifecycle: functions list, functions status, functions download to pull the deployed source back down, functions delete, and functions docs to generate a README from the source.
Secrets
Secrets live in your OS keychain and go up with the deployment, so the value never sits in your project. Run this from the function directory:
sinch secrets add CRM_API_KEY YOUR_SECRET_VALUE
The CLI stores the value in the keychain and adds an empty CRM_API_KEY= line to the function's .env. That empty line tells dev and deploy to fill the value in from the keychain. Your code reads it as process.env.CRM_API_KEY, locally and in production.
For a longer build on this same template, Build a Voice IVR with Sinch Functions walks through the menu logic and a live phone call.
Stream call audio or connect the Voice Relay
calls create can also bridge the call to a WebSocket endpoint instead of a function:
sinch voice calls create +15551234567 --stream wss://example.com/audio
sinch voice calls create +15551234567 --relay wss://example.com/relay --tts-voice Emma
--stream sends the raw call audio to your endpoint. --relay connects the Voice Relay, where Sinch runs the speech-to-text and text-to-speech and your endpoint only exchanges text. Both need a public wss:// endpoint. Add --dry-run to see the full command list either one sends. To build on them, start from the voice-relay-agent, voice-relay-echo, or realtime-* templates in sinch templates list.
Send an SMS with the Sinch Conversation API
sinch conversation send +15551234567 "Hello from Sinch" --channel SMS
send goes through your Conversation App and supports eight message types with --type: text, media, template, card, carousel, choice, location, and list. If the app doesn't have the channel you pass, the CLI lists the channels it does have. The rest of the Sinch Conversation API is in the same group: messages, contacts, conversations, apps, webhooks, and templates.
More in the CLI
-
Fax.
sinch fax send --to +12025550134 --file ./document.pdfsends a fax, andlist,get,status --wait, anddownloadtrack it. Fax services, cover pages, and fax-to-email have their own subcommands. -
SIP trunking.
sinch sipmanagestrunks,endpoints,acls,credential-lists,countries, andcalls. -
Porting.
sinch porting check +12025550134tells you whether a number can move to Sinch.orders,documents, andactivationhandle the port-in. -
Voice API v1. Projects built on a Voice application use
sinch voice v1:applications,callouts,calls, andconferences. These commands authenticate with the Voice application key and secret. SetSINCH_APPLICATION_KEYandSINCH_APPLICATION_SECRET.
Automate it
Most commands take --json and print a single JSON document with no table and no spinner, so the output pipes straight into jq:
sinch functions list --json | jq -r '.functions[] | select(.status != "Running") | .name'
sinch functions deploy --non-interactive --json | jq -r '.url'
In a pipeline, authenticate with environment variables instead of the keychain. They take precedence over stored credentials:
export SINCH_PROJECT_ID=YOUR_PROJECT_ID
export SINCH_KEY_ID=YOUR_KEY_ID
export SINCH_KEY_SECRET=YOUR_KEY_SECRET
sinch functions deploy --non-interactive
--non-interactive skips every prompt and uses defaults, and setting CI=true has the same effect on deploy. A non-interactive deploy is public unless you pass --private.
Exit codes follow BSD sysexits conventions, plus a Sinch range starting at 100. Two are worth branching on in a script, because retrying won't help with either:
| Exit code | Meaning |
|---|---|
0 |
Success |
1 |
General failure |
100 |
Authentication required: No credentials found |
101 |
Authentication failed: Credentials were rejected |
Profiles let one install work across several projects. Create a profile, switch to it, and log in:
sinch config profile create staging
sinch config profile use staging
sinch auth login
Or leave the active profile alone and pass --profile staging on a single command.
Updates are opt-in. Turn them on with sinch config set autoUpdate true, or check on demand with sinch upgrade. Install shell completions with sinch completion --install.
Use the Sinch CLI with coding agents
Every command in this post works the same when an agent runs it. These are the parts an agent relies on:
-
--helpon every command and group, to discover what exists -
--json, to read results as structured data - Exit codes, to tell an auth problem from a failed call
-
--non-interactive, to run without prompts -
--dry-runon call commands, to check a request before it costs anything
To give your assistant Sinch-specific knowledge, install Sinch's developer skills:
sinch skills install
This installs skills from github.com/sinch/skills into your coding assistants. It includes a sinch-cli skill that teaches the commands in this post, plus skills for Functions, Voice API v2, the Sinch Conversation API, Numbers, and the other Sinch APIs. After that you can describe what you need ("place a test call to my phone and show me the call log") and let the agent run the commands.
Troubleshooting
-
calls getreturns 404 for the ID fromcreate.createreturns a session ID.calls gettakes the ULID call ID fromsinch voice calls list --service-id YOUR_SERVICE_ID. -
Inbound calls stopped reaching your function after a
devsession. The Voice service webhook still points at the tunnel. Runsinch functions deploy, or set it by hand withsinch voice services update YOUR_SERVICE_ID --webhook-url YOUR_FUNCTION_URL. Check where it points withsinch voice services list. -
A secret is empty in production. Check that
.envhas the emptyCRM_API_KEY=line and that the key shows up insinch secrets list, then deploy again.sinch secrets addonly writes the.envline when you run it from the function directory. -
A SIP destination is rejected. Voice API v2 wants the scheme:
sip:agent@pbx.example.comorsips:for TLS. Run the command with--dry-runto see what's sent. -
A
sinch voice v1command asks for credentials. v1 uses the Voice application key and secret, not the project access key. SetSINCH_APPLICATION_KEYandSINCH_APPLICATION_SECRET. -
A script exits with
100or101.100means no credentials were found and101means they were rejected. Runsinch auth status, or check theSINCH_KEY_IDandSINCH_KEY_SECRETvalues in your pipeline.
Command reference
| Task | Command |
|---|---|
| Install | npm install -g @sinch/cli |
| Log in | sinch auth login |
| Find a number | sinch numbers available search --region US --type LOCAL --capabilities VOICE |
| Rent a number | sinch numbers available rent +12025550134 |
| List your numbers | sinch numbers active list |
| List Voice services | sinch voice services list |
| Create a Voice service | sinch voice services create --name "Support line" |
| Point a service at a URL | sinch voice services update YOUR_SERVICE_ID --webhook-url YOUR_URL |
| Scaffold a function | sinch functions init simple-voice-ivr --name my-function |
| Run locally | sinch functions dev |
| Route real calls to your laptop | sinch functions dev --tunnel |
| Deploy | sinch functions deploy |
| Stream logs | sinch functions logs --follow |
| Add a secret | sinch secrets add CRM_API_KEY YOUR_SECRET_VALUE |
| Place a call | sinch voice calls create +15551234567 --from +12025550134 |
| Preview a call request | sinch voice calls create +15551234567 --dry-run |
| List calls | sinch voice calls list --service-id YOUR_SERVICE_ID |
| Send an SMS | sinch conversation send +15551234567 "Hello" --channel SMS |
| Send a fax | sinch fax send --to +12025550134 --file ./document.pdf |
| Check portability | sinch porting check +12025550134 |
| Switch projects | sinch config profile use staging |
| Install agent skills | sinch skills install |
Where to go next
You now have a number, a Voice service, a function that answers and places calls, and the commands to script all of it. Build a Voice IVR with Sinch Functions goes deeper on the function side. An upcoming post builds an AI voice agent on --stream and --relay.
Use the dashboard for configuration you set once. Use the CLI for everything you repeat, debug, or run in a pipeline.
Additional resources
- Sinch Functions documentation
- Sinch CLI installation
- Sinch CLI reference
- Sinch Functions templates
- @sinch/cli on npm
- Sinch developer skills
What's the first thing you'd script with the Sinch CLI? Let us know in the comments.
Top comments (1)
The --help / --json / exit-codes trio is the right agent contract - what usually breaks it after launch is drift: the README shows flags the binary dropped, --help lags a new subcommand. What holds long-term: treat the CLI's doc surface as a tested artifact. Golden-file tests in CI that execute every example from the docs against a dry-run profile mean a flag change and a docs change must land in the same PR or CI stays red. The KYC piece is the same principle on the server side: fetching the questionnaire from the API at runtime is right - server state owns the truth, the binary just renders it. Agents mostly die on ambiguity, not on missing commands: keeping 'auth expired' and 'docs required' as distinct exit codes is what lets them recover without reading prose.