Carriers & trunks API

Two ways to run calls over infrastructure you control:

  • Bring your own carrier (BYOC) — Plivo, Twilio, Vobiz, Exotel, Vonage, or Tata Tele Smartflo account credentials, so calls run over your account and rates while Telenow handles the agent pipeline, media, and recording. Managed under /api/orgs/{orgId}/carriers.
  • SIP trunks — your own telco gateway / PBX / wholesale carrier, terminated by Telenow's native SIP gateway. Managed under /api/orgs/{orgId}/trunks.

Both are organization-scoped and authenticated with a dashboard JWT (these routes are not on the API-key surface). RBAC is enforced server-side: listing needs membership; create/sync/update/validate/DID changes need the developer, admin, or owner role; delete needs admin or owner. Every response uses the standard envelope (conventions).

Carriers (BYOC)

MethodPathPurpose
GET/api/orgs/{orgId}/carriersList connected carriers ({ connections, total })
POST/api/orgs/{orgId}/carriersAdd carrier credentials (auto-syncs numbers on create)
POST/api/orgs/{orgId}/carriers/{id}/syncRe-pull the carrier's owned numbers
DELETE/api/orgs/{orgId}/carriers/{id}Disconnect (soft-delete) the carrier

Adding a carrier

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/carriers \
  -H "Authorization: Bearer <jwt>" -H "X-Org-Id: {orgId}" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "plivo", "label": "Acme Production", "authId": "MA…", "authToken": "…" }'
FieldRequiredPer-provider meaning
providerYesOne of plivo, twilio, vobiz, exotel, vonage, smartflo.
label—A friendly name shown in the dashboard.
authIdYesPlivo/Vobiz: Auth ID. Twilio: Account SID. Exotel/Vonage: API key. Smartflo: your Smartflo account identifier — it is not sent to Tata, and is only used to recognise the same account on a re-connect.
authTokenYesPlivo/Vobiz: Auth Token. Twilio: Auth Token. Exotel: API token. Vonage: API secret. Smartflo: the API token from Smartflo → API Connect.
accountSidExotel onlyThe Exotel Account SID (third credential part — required for Exotel).
subdomainExotel onlyAPI cluster host: api.exotel.com (Singapore, default) or api.in.exotel.com (Mumbai).
applicationIdVonage onlyThe Vonage Voice Application UUID.
clickToCallKeysSmartflo onlyObject mapping each number to its Click to Call Support API key — {"+918041234567": "…"}. Tata scopes this key per number, not per account: it selects both the caller-ID DID and the Voice-Streaming destination, so one account holds several. Required for outbound on a given number; an inbound-only connection can omit it entirely.
baseUrlSmartflo onlyAPI host override. Blank ⇒ api-smartflo.tatateleservices.com.

Credentials are stored encrypted and never returned by the API. On create, Telenow runs an initial sync and imports the account's numbers as source='byoc' rows; they then appear in GET /api/voice/numbers and can be assigned to agents like any other number (Phone numbers).

Outbound credentials. Outbound dials and live-call transfers use your credentials on Plivo, Twilio, Vobiz, Exotel, and Smartflo BYOC connections — the dial path resolves the owning connection's credentials for any number imported as BYOC. Vonage BYOC numbers take inbound on your account, but their outbound voice control is still signed by the platform Vonage application unless the connection carries a matching private key. See Telephony providers.

Smartflo is BYOC-only, and needs portal steps we cannot perform. There is no platform Smartflo account. Tata publishes no API that can point a DID at a voice bot, so after POST /carriers imports your numbers they stay marked needs manual routing until you bind each one in the Smartflo portal. Outbound additionally needs a Click-to-Call key per number, generated there. Full walkthrough: Tata Tele Smartflo setup.

Smartflo API tokens expire and cannot be refreshed — 1, 7, 15, 30, or 90 days, fixed at creation. Re-POST the same account's credentials to update the connection in place (it adopts rather than duplicating).

Sync

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/carriers/{id}/sync \
  -H "Authorization: Bearer <jwt>" -H "X-Org-Id: {orgId}"

Reconciles Telenow with the carrier (for example, picking up numbers added directly in the carrier console). Returns { imported, updated, total_remote }. Run it after making changes on the carrier side.

SIP trunks

For your own SIP infrastructure. Concepts are in SIP trunking.

MethodPathPurpose
GET/api/orgs/{orgId}/trunks/configThe platform's SIP endpoint to point trunks at
GET/api/orgs/{orgId}/trunksList trunks with their DIDs ({ trunks, total })
POST/api/orgs/{orgId}/trunksCreate a trunk
PUT/api/orgs/{orgId}/trunks/{id}Update a trunk
DELETE/api/orgs/{orgId}/trunks/{id}Delete a trunk (releases its DIDs)
POST/api/orgs/{orgId}/trunks/{id}/validateSIP OPTIONS reachability ping
POST/api/orgs/{orgId}/trunks/{id}/numbersAttach a DID (manual E.164)
DELETE/api/orgs/{orgId}/trunks/{id}/numbers/{numId}Detach / release a DID

Gateway config

GET …/trunks/config returns what to configure on your side — the values are derived from the deployment's settings, not hardcoded:

{
  "success": true,
  "data": {
    "enabled": true,
    "host": "sip.telenow.ai",
    "ip": "203.0.113.10",
    "port": 5060,
    "tlsPort": 5061,
    "transports": { "udp": true, "tcp": true, "tls": false, "streamConnections": 3 },
    "transport": "udp",
    "codecs": ["PCMU/8000", "PCMA/8000", "G722/8000", "opus/48000/2", "G726-32/8000", "AAL2-G726-32/8000", "L16/16000", "L16/8000"],
    "srtp": "off",
    "srtpSuites": ["AES_CM_128_HMAC_SHA1_80", "AES_CM_128_HMAC_SHA1_32", "AES_256_CM_HMAC_SHA1_80", "AES_256_CM_HMAC_SHA1_32", "AEAD_AES_128_GCM", "AEAD_AES_256_GCM"],
    "dtmf": "rfc4733",
    "rtcpMux": true
  }
}

enabled reflects the deployment's SIP_ENABLED flag — when false, trunk calls won't connect until the operator turns the gateway on. transports says which signalling listeners are up (tls needs a certificate on the deployment; streamConnections is the live TCP/TLS connection count). codecs is every codec this deployment's build negotiates, in the platform's preference order and in a=rtpmap spelling — these are the names codecPrefs accepts. srtp is the platform's default media-encryption policy, which a trunk's own srtp overrides. transport is kept for older clients and always reads udp.

Create / update a trunk

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/trunks \
  -H "Authorization: Bearer <jwt>" -H "X-Org-Id: {orgId}" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Contoso PRI East",
        "direction": "both",
        "inboundIpAcl": ["203.0.113.10", "198.51.100.0/28"],
        "terminationUri": "sip.yourtelco.com:5060",
        "authUsername": "telenow",
        "authPassword": "…",
        "defaultCallerId": "+14155550142",
        "status": "active",
        "dialFormat": "e164",
        "countryCode": "91",
        "fromDomain": "sip.yourtelco.com",
        "paiEnabled": false,
        "transport": "tls",
        "srtp": "required",
        "codecPrefs": ["PCMA", "G722", "opus"]
      }'
FieldRequiredNotes
nameYesTrunk label.
direction—inbound, outbound, or both (default both).
inboundIpAclFor inboundArray of source IPs/CIDRs; inbound INVITEs are authorized by source IP.
terminationUriFor outboundhost[:port] Telenow dials for outbound (port defaults to 5060).
authUsername / authPassword—Digest credentials, used only when your trunk challenges our outbound INVITEs. Stored encrypted. On update, omit them to leave the stored values unchanged; send an empty string to clear them.
defaultCallerId—Caller ID used when an outbound leg has none.
status—active or disabled (update only; default active).
dialFormat—How the dialled number and caller ID are rendered into the outbound INVITE: e164 (default on create — keep the leading +), digits (legacy — strip everything but digits), national (drop the country code), national_0 (drop it and prepend 0). The two national forms only strip from a number written internationally (+… or 00…), so an already-national number is never truncated.
countryCodeFor the national formatsCalling code without +, e.g. 91. Must be sent together with dialFormat.
fromDomain—Host used in the From (and P-Asserted-Identity) URI instead of the platform's own SIP host. Bare hostname or IP, optionally :port.
paiEnabled—Send an RFC 3325 P-Asserted-Identity header (default false).
transport—Signalling transport for the calls we place to this trunk: udp (default), tcp, tls. A sips: termination URI or ;transport= parameter on it overrides this. Inbound calls use whatever the trunk connects with.
srtp—Media-encryption policy: off (plain RTP; an RTP/SAVP offer is refused), optional (SRTP whenever the peer offers SDES keys, and we offer keys ourselves), required (SRTP or 488). Empty string / omitted on create = the platform default from the gateway config.
codecPrefs—Codec preference order, most preferred first, using the names GET …/trunks/config lists (PCMA, G722, opus, G729…; the /clock suffix is optional). Names this deployment cannot run are rejected with 400 and the list of what is available. [] = the platform default (offer everything, G.711 first).
registrationEnabled—true = Telenow REGISTERs to the termination URI with the digest credentials (see Registration-based trunks). Requires terminationUri and credentials (typed in this request, or already stored). The four registration* fields move together: on update, sending registrationEnabled writes all four; omitting it leaves them unchanged.
registrationDomain—Address-of-record domain; omitted = the termination host. Bare domain only.
registrationUser—Address-of-record user; omitted = the digest username.
registrationExpirySecs—Requested binding lifetime, 60–3600 (default 600).

Create defaults dialFormat to e164, which is what most carriers expect. Trunks created before this field existed carry digits and are never migrated — send dialFormat explicitly if you want to move one.

On update, every dial-plan field — and transport, srtp and codecPrefs — behaves like the credentials above: each is preserved unless the request mentions it. A PUT that omits dialFormat leaves the stored format alone — it is not reset to the create default — so a partial update can never move a trunk's wire format behind your back. countryCode is the one exception: it is validated against dialFormat, so sending it without dialFormat is a 400 rather than being paired with a guess.

POST returns 201 with { id }. PUT returns { success: true }. Trunk rows expose has_auth (whether digest credentials are set) but never the credentials themselves; they also carry transport, srtp (null = platform default), codec_prefs (canonical name/clock spellings, [] = platform default), the four registration_* columns and, when the trunk registers, a live registration object:

"registration": {
  "status": "registered",
  "detail": "registered for 600 s",
  "expiresInSecs": 412,
  "registrarIp": "203.0.113.7",
  "lastCode": 200,
  "attempts": 3
}

status is registering, registered, failed (with the registrar's reason in detail) or unregistered; null when the trunk does not register or the gateway is off.

Validate

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/trunks/{id}/validate \
  -H "Authorization: Bearer <jwt>" -H "X-Org-Id: {orgId}"

Sends a real SIP OPTIONS to the trunk's termination URI — over the trunk's configured transport, so a TLS-only SBC is probed with a TLS handshake and a TCP trunk over TCP — and waits up to 4 seconds. Returns { reachable, latencyMs, detail } — reachable is true/false and latencyMs is the round-trip when reachable. An inbound-only trunk (no termination URI) returns reachable: false with guidance to verify your carrier points at the platform endpoint. This is the same check the dashboard's import wizard runs on its last step.

DIDs on a trunk

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/trunks/{id}/numbers \
  -H "Authorization: Bearer <jwt>" -H "X-Org-Id: {orgId}" \
  -H "Content-Type: application/json" \
  -d '{ "e164": "+16465550198", "agentId": "agent-uuid" }'

DIDs are entered by hand — there's no search/purchase for trunk numbers. e164 is required (digits are normalized to + E.164); agentId is optional and binds the agent immediately. Returns 201 with { id, e164 }. Passing agentId for a number a member already holds for inbound returns 409 Conflict (inbound exclusivity). DELETE …/numbers/{numId} releases the DID.