TVH Voice docs

#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

This example is executed against the sandbox in CI (tests/e2e/specs/m6-docs.spec.ts).