#Recipe: Connect a carrier
A carrier is the customer's own account at Telnyx, Twilio or any SIP trunk provider; carrier costs
stay with that account. Connecting validates the credentials, provisions the SIP side and creates the
gateway. Health is reported asynchronously (carrier.health.changed).
Production examples (same call, different body):
{ "type": "telnyx", "name": "Telnyx", "settings": { "allowedDestinationCountries": ["NL"], "inboundSourceCidrs": ["<telnyx signaling IPs>"] }, "credentials": { "apiKey": "KEY..." } }
{ "type": "twilio", "name": "Twilio", "settings": { "authMode": "credentials", "regulatoryEmail": "ops@example.com" }, "credentials": { "accountSid": "AC...", "apiKeySid": "SK...", "apiKeySecret": "...", "authToken": "..." } }
{ "type": "generic-sip", "name": "My trunk", "settings": { "proxy": "sip.trunk.example:5060", "authMode": "register" }, "credentials": { "username": "...", "password": "..." } }
Create a dedicated API key at the carrier for this platform, so it can be revoked on its own.
#TypeScript (SDK)
// Connect a carrier account. The sandbox uses the "mock" carrier; production uses "telnyx",
// "twilio" or "generic-sip" with the same call — only type/settings/credentials change.
import { createVoiceClient } from '@voice/sdk';
const voice = createVoiceClient({ baseUrl: process.env.VOICE_API_URL!, apiKey: process.env.VOICE_API_KEY! });
// 1. See which carrier types exist, their settings and capabilities.
const { data: types } = await voice.GET('/v1/carrier-types');
console.log(types!.data.map((t) => `${t.type}: ${t.credential_fields.join(', ')}`));
// 2. Connect. Credentials are validated at the carrier and never returned again.
const { data: carrier, error } = await voice.POST('/v1/carriers', {
body: { type: 'mock', name: 'Sandbox carrier', settings: { authMode: 'register' }, credentials: { apiKey: 'mock_docs_example_key' } },
});
if (error) throw new Error(`${error.error.code}: ${error.error.message}`);
console.log('carrier', carrier.id, carrier.status, 'supports ordering:', carrier.capabilities.supports_ordering);
// 3. Health becomes "healthy" once the SIP gateway is up (also sent as carrier.health.changed).
for (let i = 0; i < 30; i++) {
const { data } = await voice.GET('/v1/carriers/{id}', { params: { path: { id: carrier.id } } });
if (data!.health.state === 'healthy') break;
await new Promise((r) => setTimeout(r, 1000));
}
console.log('CARRIER_ID=' + carrier.id);
#curl
curl -sS -X POST "$VOICE_API_URL/v1/carriers" \
-H "Authorization: Bearer $VOICE_API_KEY" -H "Content-Type: application/json" \
-d '{"type":"mock","name":"Sandbox carrier (curl)","settings":{"authMode":"ip_auth"},"credentials":{"apiKey":"mock_docs_example_key"}}'
#Notes
CARRIER_AUTH_FAILED: the carrier rejected the credentials (nothing was stored as active).VALIDATION_FAILED:details.issues/details.fieldssay which setting or credential field is wrong (values are never echoed).- Use
capabilitiesto decide which number features to show.
This example is executed against the sandbox in CI (tests/e2e/specs/m6-docs.spec.ts).