Tata Smartflo setup
Agent routing on Tata Smartflo — streaming-native specifics, API-first inbound bindings with server-resolved credentials, and saved-agent attachments.
Tata Smartflo is streaming-native: instead of POSTing to a per-call HTTP webhook (like Twilio/Vobiz), it opens a media stream to a single, static, signed WSS URL. Two consequences:
- Inbound routing needs a one-time portal step (below) — there's no per-number webhook for zoxaAI to provision.
POST /callrequests for Smartflo carry no inline credentials — zoxaAI resolves them server-side from the saved telephony configuration that owns the number.
Prerequisites
- A Smartflo telephony configuration with an
auth_token. - The number added and active under that config (add a phone number).
- The config's Voice Bot WSS URL pasted into the Tata portal (one-time, below).
One-time: point Tata at zoxaAI
Fetch voice_bot_wss_url from GET /telephony-configs/{config_id} and paste it into Settings → Channels → Voice Bot in the Tata Smartflo portal.
Signed per config
The URL is signed with your config_id, so every inbound stream on this Smartflo account arrives at zoxaAI. There's no per-number webhook to register — routing to the right agent happens after the stream connects, from the dialed DID.
Inbound via the API (transient agents)
Register the DID with POST /call type=inbound — same request shape as Twilio/Vobiz, except callConfig has no auth (sending one returns a 400 with Extra inputs are not permitted):
curl -X POST https://dashboard.zoxa.ai/api/v1/call \
-H "X-API-Key: zsk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "inbound",
"callConfig": {
"provider": "smartflo",
"phoneNumber": "+919000000000"
},
"agent": {
"name": "Reception bot",
"systemPrompt": "You are the front desk. Greet callers and route them.",
"greeting": { "firstMessages": ["Thanks for calling! How can I help?"] },
"llm": { "provider": "openai", "model": "gpt-5.4-mini" },
"tts": { "provider": "elevenlabs" },
"stt": { "provider": "soniox" }
}
}'import httpx
httpx.post(
"https://dashboard.zoxa.ai/api/v1/call",
headers={"X-API-Key": "zsk_..."},
json={
"type": "inbound",
"callConfig": {
"provider": "smartflo",
"phoneNumber": "+919000000000",
},
"agent": {
"name": "Reception bot",
"systemPrompt": "You are the front desk. Greet callers and route them.",
"greeting": {"firstMessages": ["Thanks for calling! How can I help?"]},
"llm": {"provider": "openai", "model": "gpt-5.4-mini"},
"tts": {"provider": "elevenlabs"},
"stt": {"provider": "soniox"},
},
},
)All three agent modes work — inline agent (transient), agentId (persistent), or agentId + overrides.
How credentials are resolved
zoxaAI looks up the requested phoneNumber among your org's active Smartflo phone numbers (format-tolerant: +919000000000, 919000000000, and 9000000000 all match the same stored DID), loads the owning configuration's credentials, then runs a live token check and a number-ownership probe against Tata before registering — the DID must actually belong to the Tata account, same guarantee as Twilio's "not owned by this account" check. The binding stores:
- the number's canonical E.164 (
address_normalized), and - a credentials snapshot from the config — rotated your token? Re-POST the binding to re-snapshot.
Registration errors
| Status | detail | When |
|---|---|---|
400 | "phone_number_not_configured: add this number to a Smartflo telephony configuration first" | The number isn't added (or isn't active) under any of your org's Smartflo configs. |
400 | "smartflo_config_missing_token: ..." | The owning config has no auth token. |
400 | "invalid_credentials: <reason>" | The saved token failed the live Tata probe (rotated/expired). |
400 | "Phone number ... is not owned by this Smartflo account." | The DID isn't among the numbers your Tata account exposes — you can only bind numbers your account owns. |
400 | {"error": "Agent configuration validation failed", "details": [{"path": "callConfig.smartflo.auth", "message": "Extra inputs are not permitted", ...}]} | You sent auth for smartflo, or other schema violations (POST /call formats schema errors as 400). |
501 | not-implemented message | type=outbound with smartflo (see below). |
Managing the binding
Standard inbound-bindings lifecycle: GET to list, PATCH to swap the agent (no re-provisioning), DELETE to remove. Deleting the binding falls routing back to the number's dashboard agent attachment (if any).
Inbound via a saved agent (dashboard-style)
Alternatively, attach a saved agent to the phone number with its inbound_agent_uuid:
curl -X PUT https://dashboard.zoxa.ai/api/v1/organizations/telephony-configs/12/phone-numbers/88 \
-H "X-API-Key: zsk_..." -H "Content-Type: application/json" \
-d '{ "inbound_agent_uuid": "550e8400-e29b-41d4-a716-446655440000" }'Editing the agent later (PATCH /agents/{agent_uuid}) needs no re-bind — the config is resolved fresh on each inbound call.
How an inbound stream resolves to an agent
When Tata streams in, zoxaAI checks, in order:
- A signed identifier in the stream params → a call we originated (outbound).
- An inbound binding for the dialed DID (format-tolerant match) → runs
binding.agent_snapshot. Wallet and concurrency limits are enforced before the pipeline starts. - The phone number with an
inbound_agent_uuid→ runs that saved agent.
A binding (step 2) wins over a saved-agent attachment (step 3) on the same number — re-registering via the API takes routing back.
Outbound
Place Smartflo outbound calls with POST /api/v1/call and a callConfig of { "provider": "smartflo", "phoneNumber": "+91..." } — no auth object. The dial routes through the saved Smartflo configuration that owns phoneNumber, so the number must be added and active under that configuration (otherwise 400 phone_number_not_configured). The configuration also needs a click_to_call_api_key.
Because Smartflo's originate API is async and returns no call id, zoxaAI attaches a signed custom_identifier to each dial so the returning media stream correlates back to the right call.
Related
- Register an inbound number — the full request/response contract
- Create a Smartflo configuration
- Get a configuration — where
voice_bot_wss_urlcomes from - Add a phone number
- Inbound Bindings — lifecycle endpoints