In my previous post on Sinch Functions, I kept reaching for the CLI to test things. At some point I realized I'd been treating @sinch/cli as a Functions tool when it actually covers the whole product surface: numbers, voice, messaging, fax, SIP trunking, porting. This post goes wider.
You can do a lot with the Sinch CLI before you've written a line of application code. No Postman, no dashboard tab-switching.
The sections below are independent: different prerequisites, different credentials, no assumed order. Pick whichever is relevant to what you're building.
You'll need:
- A Sinch account (Project ID, Key ID, Key Secret from the dashboard)
- For voice callouts: a Voice App with a Key and Secret
- For messaging: a Conversation App with at least one channel configured
- For webhook testing: a Sinch Function project running locally (see Getting Started with Sinch Functions)
- For log streaming: a deployed Sinch Function (same Functions post)
Installing and authenticating
npm install -g @sinch/cli
Then:
sinch auth login
The CLI walks you through Project ID, Key ID, and Key Secret, then two follow-up questions:
? Project ID (from Sinch Dashboard): <your-project-id>
? Key ID (from Project → Access Keys): <your-key-id>
? Key Secret: **********************
✔ API credentials verified!
? Add Voice application credentials? (for voice templates and callbacks) (Y/n)
? Voice Application Key: <your-voice-app-key>
? Voice Application Secret: **********************
✔ Voice credentials verified!
? Install Sinch developer skills for AI assistants? (Claude Code, Copilot, Cursor) (Y/n)
✅ Authenticated
Project ID: <your-project-id>
Key ID: <your-key-id>
Voice App Key: <your-voice-app-key>
Say yes to Voice Application credentials; you'll need them for callouts later. The Skills prompt only appears if Sinch Skills aren't already installed in your AI coding assistant. If you've done this before, it's skipped automatically. Either way, you don't need them for this walkthrough; more on this at the end.
If you've logged in before, the CLI detects existing credentials and offers to reuse them.
All credentials go into your OS keychain: macOS Keychain, Windows Credential Manager, or Linux Secret Service. Nothing lands in a config file in plaintext. sinch auth status will tell you which storage mode is active.
Confirm the connection:
sinch health
✔ API is healthy
🏥 Health Status:
Status: healthy
API URL: https://functions.api.sinch.com
Project ID: <your-project-id>
Getting a number
sinch numbers available search --region US --type LOCAL --capabilities SMS VOICE
Even with all flags passed, the CLI still asks for an optional digit pattern to filter by. This is part of the interactive mode that runs on top of the flags you've already provided. Hit Enter to skip it and get any available number. Add --non-interactive to skip all optional prompts entirely. Results show as a selectable list with capabilities and cost in your account currency:
✔ Found 20 available number(s)
? Select a number to rent (20 available):
❯ +1 202-519-9630 SMS, VOICE 0.80 EUR setup + 0.80 EUR/mo [docs required]
+1 202-773-9769 SMS, VOICE 0.80 EUR setup + 0.80 EUR/mo [docs required]
...
The [docs required] tag on some numbers means regulatory documentation is required. Check the Sinch dashboard for details on what's needed for your region.
For a specific number:
sinch numbers available check +12025550134
To rent:
sinch numbers available rent +12025550134
The interactive rent flow walks you through configuration for each capability the number supports:
? Configure SMS for this number? (y/N)
? Voice configuration:
❯ None (configure later)
RTC — Real-time Communication
EST — Elastic SIP Trunking
FAX
? Callback URL for provisioning events (optional):
? Rent +1 202-555-0172? This will activate it on your project. (Y/n)
If you say yes to SMS, it asks for your SMS service plan ID (find it in the Sinch SMS dashboard) and optionally a campaign ID for US 10DLC compliance. For this walkthrough, say no. You can configure it later from the dashboard. Voice lets you pick RTC, EST (which fetches your SIP trunks for selection), or Fax. Everything you'd normally do across three dashboard tabs, in one wizard. On confirmation:
✔ +1 202-555-0172 rented successfully!
Phone Number: +1 202-555-0172
Region: US
Type: LOCAL
Capabilities: SMS, VOICE
Cost: 0.80 EUR/month
Manage this number: sinch numbers active get +12025550172
After renting, confirm it's yours:
sinch numbers active list
The command prompts for an optional region filter and number type before listing. Leave both blank to see everything:
✔ Found 3 active number(s)
+1 202-555-0172 [US] LOCAL SMS, VOICE
+44 20-555-0134 [GB] LOCAL VOICE
+46 70-555-0180 [SE] MOBILE SMS
For the full picture on a specific number, pass it in E.164 format (no spaces or dashes):
sinch numbers active get +12025550172
✔ Number retrieved
ℹ Phone Number: +1 202-555-0172
ℹ Region: US
ℹ Type: LOCAL
ℹ Capabilities: SMS, VOICE
ℹ Cost: 0.80 EUR/month
ℹ Next Charge: 8/23/2026
ℹ SMS Service Plan: n/a
ℹ Voice Type: RTC
When you're done with it:
sinch numbers active release +12025550172
Prompts for confirmation unless you pass --force.
Making a call
For this you need a Voice App with a Key and Secret. If you set one up during sinch auth login, you're ready. If not, run sinch auth login again and say yes when it asks for Voice Application credentials.
The examples below use separate numbers from the ones in "Getting a number". These are standalone demos, not a continuation of that workflow.
sinch voice callouts tts +46700000000 "Your shipment has been picked up."
The CLI infers the destination type from the format: E.164 numbers route as PSTN, sip:user@host routes to SIP, a bare string routes to an in-app endpoint. You don't need to think about it for a standard phone call.
✔ TTS callout queued
ℹ Call ID: 5b5accdf-bd0d-4448-a5d3-4cca076c732b
ℹ Destination: +46700000000
ℹ Voice: en-US
ℹ Track status with: sinch voice calls get 5b5accdf-bd0d-4448-a5d3-4cca076c732b
A few flags worth knowing:
-
--voice en-US-Neural2-Fsets a specific voice. Accepts full locale strings. -
--cli +12039710337sets the caller ID shown to the recipient. -
--dtmf "1w2"sends DTMF tones after pickup;wis a 500ms pause.
Poll for the result. For a basic TTS callout with no voice function deployed, expect MANAGERHANGUP: the call connected, the recipient answered, the message played, and the platform ended the call. That's a successful delivery.
sinch voice calls get 5b5accdf-bd0d-4448-a5d3-4cca076c732b
✔ Call info retrieved
ℹ Call ID: 5b5accdf-bd0d-4448-a5d3-4cca076c732b
ℹ Status: FINAL
ℹ Result: ANSWERED
ℹ Reason: MANAGERHANGUP
ℹ Duration: 2s
ℹ Domain: pstn
ℹ To: +46700000000 (Number)
ℹ Timestamp: 7/23/2026, 3:45:25 PM
ℹ Rate/min: 0.323 EUR
ℹ Total cost: 0.323 EUR
Once you have a voice function deployed to handle the ICE callback, you can keep the call alive, play a menu, collect input, and respond accordingly.
Sending a message
The examples below use separate numbers from the ones in "Getting a number". These are standalone demos, not a continuation of that workflow.
This requires a Conversation App with at least one channel configured. SMS, WhatsApp, RCS, and others are each set up separately in the Sinch dashboard. Which channels you can actually send through depends on what's configured in your app.
sinch conversation send +46700000000 "Your driver is 10 minutes away."
If you don't specify a Conversation app with -a <app-id>, the CLI prompts you to pick one and offers to save it as the default so you're not asked again:
? Select a Conversation app:
❯ 01KTFHP5SF8SQFSNK5V7NQVKZH — My Conversation App
Enter app ID manually
? Save as default Conversation App ID? (Y/n)
✅ Default app ID saved. You won't be asked again.
It then asks which channel to use:
? Select channel:
❯ SMS
WHATSAPP
MESSENGER
INSTAGRAM
VIBER
TELEGRAM
RCS
LINE
Add -c SMS or -c WHATSAPP to skip the prompt and target a specific channel directly. Only channels you've configured in the Conversation App will actually work; the others will be accepted by the CLI but fail at the API level.
Output on success:
✔ Message sent successfully!
ℹ Message ID: 01KY7KS5059DMD3WDFAD3V3J8E
ℹ Recipient: +46700000000
ℹ Channel: SMS
ℹ Type: text
Testing a webhook locally
Requires a Sinch Function project on your machine (see Getting Started with Sinch Functions). Use a dedicated dev Voice App, not one handling live traffic: this rewrites its callback URL.
From your project directory:
sinch functions dev --tunnel
🎯 Starting my-simple-voice-ivr (node)
ℹ Port: 3000
✅ ✅ Server running on http://localhost:3000
[nodemon] starting `npm run build && sinch-runtime`
Function server running on http://localhost:3000
Detected functions:
ice (voice): POST http://localhost:3000/ice
pie (voice): POST http://localhost:3000/pie
dice (voice): POST http://localhost:3000/dice
Starting tunnel...
Tunnel connected successfully!
✅ Updated voice webhook URL
📱 Test Phone Numbers:
☎️ +12025550172
💡 Call any of these numbers to test your voice function!
The CLI starts a local server, opens a Cloudflare tunnel, and rewrites your Voice App's callback URL to point at the tunnel. Real Sinch traffic now hits your machine. From a second terminal, call the test number shown in the startup output:
sinch voice callouts tts +12025550172 "Hello from your local function."
The ICE callback fires against your local handler. Incoming requests and responses appear in real time in the dev server output, in the same columnar format as sinch functions logs --follow. File watching is on, so any code change restarts the server automatically.
Streaming logs
After deploying a function, sinch functions logs --follow streams live request and response data as a columnar table. Each row shows method, status code (green for 2xx, red for 4xx/5xx), duration, and URL. Expand any row to see the full request body, response body, and console.log output from inside the function.
sinch functions logs --follow
ℹ 📋 Logs for function: my-voice-function (✅ Running)
Time Method Status Duration URL
────────────────────────────────────────────────────────────────────────────────
14:32:39 POST 200 2ms / [ice]
┌ Request
│ {
│ "event": "ice",
│ "callId": "8ce5c8b8-d70f-42f3-ad00-750706738f71",
│ "cli": "46700000000",
│ "to": { "type": "did", "endpoint": "+12025550172" },
│ "domain": "pstn",
│ ...
│ }
├ Response
│ {
│ "instructions": [{ "name": "say", "text": "Press 1 for yes.", "locale": "en-US" }],
│ "action": { "name": "runMenu", "menus": [...] }
│ }
└─────────────────────────────────────
If the function has console.log calls, the output appears in a Logs section between Response and the closing separator. -i opens a full-screen TUI instead. Arrow keys navigate between requests, Enter expands the detail, Esc closes it, and Q quits.
Filter by level, text, or time window:
sinch functions logs --follow --level error
sinch functions logs --follow --search "timeout"
sinch functions logs --since 30m
If you're not in a project directory with a sinch.json, pass the function ID directly:
sinch functions logs <function-id> --follow
What else the CLI covers
More messaging types. sinch conversation send supports cards, carousels, choice messages, location messages, media with captions, and pre-approved templates. Card, carousel, and choice messages are built interactively by default; pass --json-message with the full message body for non-interactive use. Templates are fetched from the API and presented as a picker.
Debugging. SINCH_DEBUG=2 on any command shows request method, URL, and status code. SINCH_DEBUG=3 adds response bodies and headers. Useful when you're chasing an auth issue and want to see exactly what's being sent.
SINCH_DEBUG=2 sinch voice callouts tts +46700000000 "Test."
Conference calls. sinch voice callouts conference <destination> <conference-id> dials a number into a conference room, created automatically if it doesn't exist.
Mid-call SVAML. sinch voice calls update <call-id> --svaml '...' injects SVAML into an active call without needing a webhook.
Fax. sinch fax send --from <number> --to <number> --file invoice.pdf
SIP trunking. sinch sip trunks covers trunks, endpoints, ACLs, and credential lists.
Number porting. sinch porting orders create starts a porting order; sinch porting documents upload handles the paperwork.
Everything accepts --json for machine-readable output and --profile <name> to switch between credential sets. With those two flags, the whole CLI is scriptable.
Troubleshooting
-
Auth errors on callouts. Voice callouts use a separate set of credentials from your Project Key/Secret: the Voice Application Key and Secret. If
sinch voice callouts ttsfails with an auth error, you probably skipped that step duringsinch auth login. Runsinch auth loginagain and say yes when it asks for Voice Application credentials. -
sinch conversation sendfails after picking a channel. The CLI accepts any channel in the list, but only channels configured in your Conversation App will go through. Check your app's channel configuration in the Sinch dashboard. -
sinch numbers active listoutput isn't copy-paste compatible withget.listshows numbers in formatted display format (+1 202-555-0172), butgetexpects E.164 (+12025550172). Strip the spaces and dashes before passing it. -
--non-interactivehangs in CI. The CLI prompts for missing required values if you don't provide them as flags. Pass all required flags explicitly alongside--non-interactive. -
sinch functions dev --tunnelrewrites your Voice App's callback URL. Use a dedicated dev Voice App, not one handling live traffic. If the tunnel connection fails, check whether port 3000 is already in use. -
sinch functions logssays the function wasn't found. Log streaming requires a deployed function. If you've only run it locally withsinch functions dev, deploy it first withsinch functions deploy.
What comes next
The whole point of the CLI is that the integration workflow starts in the terminal, not in code. If you want to take that further, an AI coding assistant can drive the CLI commands for you too: describe what you need, and let it figure out which commands to run. The Sinch Skills installed during sinch auth login give Claude Code, Cursor, and Copilot knowledge of the Sinch APIs, so the same assistant writing your integration code can handle number management, callouts, and message sends alongside it. Same local-first pattern as the Functions post: real Sinch infrastructure, no context switching.
The dashboard is good for configuration you set once. The CLI is better for everything else.
Additional Resources
Curious whether you're using the CLI for anything I didn't cover here. Let me know in the comments.
Top comments (0)