Carriers (BYOC)

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.
labelA 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,
    "transport": "udp",
    "codecs": ["PCMU", "PCMA"],
    "dtmf": "rfc2833"
  }
}

enabled reflects the deployment's SIP_ENABLED flag — when false, trunk calls won't connect until the operator turns the gateway on.

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
      }'
FieldRequiredNotes
nameYesTrunk label.
directioninbound, 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 / authPasswordDigest 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.
defaultCallerIdCaller ID used when an outbound leg has none.
statusactive or disabled (update only; default active).
dialFormatHow 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.
fromDomainHost used in the From (and P-Asserted-Identity) URI instead of the platform's own SIP host. Bare hostname or IP, optionally :port.
paiEnabledSend an RFC 3325 P-Asserted-Identity header (default false).

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 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.

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 datagram to the trunk's termination URI 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.