This page documents the complete Click2Call surface: authenticate and refresh tokens, inspect your assigned C2C DIDs, distribute concurrent channels across them, create async click2call jobs, receive callback events, configure inbound webhooks, list recent call logs, and fetch or retry transcripts. The same Click2Call endpoint can also place calls handled by an Indus voice agent: pass an AGT_ agent ID as agent_number (see Voice-Agent Calls).
Base URL
Production: https://developer.induslabs.io
Click2Call API route prefix: /api.
API Inventory
Endpoint
Purpose
POST /api/login
Authenticate and obtain access and refresh tokens.
POST /api/refresh_token
Issue a new access token from a refresh token.
POST /api/calls/click2call
Create a Click2Call request, provider or voice agent.
GET /api/calls/recent
List and filter recent Click2Call records.
GET /api/calls/{call_id}/transcript
Fetch one call result, recording, and transcript.
POST /api/calls/{call_id}/transcript/retry
Reprocess a failed or stalled transcript.
GET /api/mobile-numbers
List assigned C2C DIDs with a channel snapshot.
GET /api/mobile-numbers/channel-allocation
Read the shared pool and per-DID allocation state.
PUT /api/mobile-numbers/channel-allocation
Replace the full DID allocation map.
PATCH /api/mobile-numbers/channel-allocation
Update selected DID allocations only.
GET /api/mobile-numbers/channel-allocation/utilization
Live utilization counters for pre-call checks.
POST /api/calls/inbound-callback-url
Set the inbound webhook and transcript flag for a DID.
GET /api/calls/inbound-callback-url
List inbound webhook settings across your DIDs.
POST /api/calls/call-log
Provider-facing ingest for final call results.
Common Response Envelope
{ "status_code":200, "message":"string or null", "error":"string/object or null", "data":{} }
Auth/dependency failures may return FastAPI default errors like:
{"detail":"Could not validate credentials"}
POST/api/login
Developer Login
Authenticate a verified active user and return access + refresh tokens.
Functionality
Public endpoint for email/password authentication.
Returns bearer access and refresh tokens on success.
Inputs
Name
Type
Default
Description
email*
string
required
User email address.
password*
string
required
User password.
Outputs
Status
Type
Description
200 OK
application/json
Login successful with access and refresh tokens.
400 Bad Request
application/json
Incorrect credentials, inactive user, or unverified user.
Create a click2call job for the authenticated user. Provider calls are queued and processed asynchronously. Voice-agent (AGT_) calls are dialed immediately and handled by an Indus voice agent.
Functionality
Requires bearer access token.
For provider Click2Call, did is required. The backend sends the call to the provider in the background.
For voice-agent calls, pass a voice agent ID such as AGT_E882B100 as agent_number and put call infields in agent_config. did is not needed. See Voice-Agent Calls (AGT_) below.
If callback_url is provided, the backend posts call and transcript lifecycle events to that URL as the call progresses.
If callback_url is not provided, use the recent logs and transcript endpoints to poll status.
Inputs
Name
Type
Default
Description
Authorization*
header
required
Bearer <access_token>
customer_number*
string
required
Customer phone number with country code, digits only, e.g. 919999999999.
agent_number*
string
required
Human agent phone number (e.g. 918888888888) for provider Click2Call, or a voice agent ID (e.g. AGT_E882B100) to have an Indus voice agent take the call.
did
string
required for provider flow
Caller DID/provider number used to place the Click2Call request. Ignored for AGT_ voice-agent calls.
callback_url
url
optional
Public webhook URL that receives backend POST callbacks for this call.
transcript
boolean
false
Set true to process the call transcript. Provider flow: runs after the provider call-log callback arrives. AGT_ flow: uses the voice agent conversation transcript.
transcript_language
string
null
Optional transcript language. If omitted, the default hi-en transcription flow is used. Ignored for AGT_ voice-agent calls.
agent_config
object
{}
AGT_ voice-agent calls only. Flat call infields (e.g. customer_name, crm_id) plus optional user_name, voice_id, and language. Ignored in the provider flow. See Voice-Agent Calls (AGT_).
Outputs
Status
Type
Description
200 OK
application/json
Click2Call request created. The provider flow returns status queued; the AGT_ flow returns success or failed.
401 Unauthorized
application/json
Missing or invalid token.
402 Payment Required
application/json
AGT_ flow: insufficient credits, or no channels purchased for the agent outbound number.
403 Forbidden
application/json
Provider flow: DID is not assigned to you. AGT_ flow: the agent is not owned by you or your team.
404 Not Found
application/json
Provider flow: DID not found. AGT_ flow: agent not found.
409 Conflict
application/json
Provider flow: unable to reserve a DID channel at this time.
422 Unprocessable Entity
application/json
did is missing for provider flow, callback_url is invalid, or request validation failed.
429 Too Many Requests
application/json
AGT_ flow: channel limit reached for the agent outbound number.
Fetch the shared channel pool summary and the full per-DID allocation and utilization state.
Functionality
Requires bearer access token.
pool_total_channels is your total concurrent capacity. It comes from your C2C batch totals, falling back to the sum of purchased channels across assigned C2C numbers.
available_channels is allocated_channels minus active_channels for that DID, floored at zero.
pool_active_channels is always your account-wide live channel count.
If you have never saved an explicit allocation map, the backend seeds one from purchased channel values. Save an explicit map with PUT to normalize the configuration.
Inputs
Name
Type
Default
Description
Authorization*
header
required
Bearer <access_token>
Outputs
Status
Type
Description
200 OK
application/json
Pool summary plus per-DID allocated, active, and available channel counts.
Lightweight live utilization counters for dashboards and pre-call capacity checks. Returns the same numbers as the allocation endpoint without the DID metadata.
Functionality
Requires bearer access token.
Poll this before a burst of calls to confirm you have free channels, rather than relying on the warning in the click2call response.
Pass did to narrow the numbers array to one DID. The pool counters stay account-wide either way.
The did field in each numbers item is currently always null. Use phone_number_id to identify the DID, or read the number from GET /api/mobile-numbers/channel-allocation.
Inputs
Name
Type
Default
Description
Authorization*
header
required
Bearer <access_token>
did
query string
optional
Restrict the numbers array to a single assigned C2C DID, e.g. 919484956750.
Outputs
Status
Type
Description
200 OK
application/json
Pool and per-DID utilization counters.
401 Unauthorized
application/json
Missing or invalid token.
404 Not Found
application/json
The did filter does not match any DID assigned to you.
cURL
curl -X GET \ "https://developer.induslabs.io/api/mobile-numbers/channel-allocation/utilization" \ -H "Authorization: Bearer <access_token>" \ -H "Accept: application/json" # Single DID curl -X GET \ "https://developer.induslabs.io/api/mobile-numbers/channel-allocation/utilization?did=919484956750" \ -H "Authorization: Bearer <access_token>" \ -H "Accept: application/json"
Re-run transcript processing for a provider Click2Call whose transcript failed or stalled. The stored recording is reprocessed; the call is not dialed again.
Functionality
Requires bearer access token.
Only provider Click2Call transcripts can be retried. Voice-agent (AGT_) calls are rejected with 409.
The call must have been created with transcript: true, and a stored recording must be available.
Retrying resets transcript_status to pending and clears any previously sent transcript.ready, transcript.failed, and transcript.disabled callback events, so those events can be delivered again.
Processing runs in the background. Poll GET /api/calls/{call_id}/transcript or wait for the callback.
Inputs
Name
Type
Default
Description
Authorization*
header
required
Bearer <access_token>
call_id*
path
required
Call ID returned by the click2call endpoint.
Outputs
Status
Type
Description
200 OK
application/json
Retry queued. transcript_status is set back to pending.
401 Unauthorized
application/json
Missing or invalid token.
403 Forbidden
application/json
The call belongs to another user.
404 Not Found
application/json
call_id not found.
409 Conflict
application/json
Transcript is disabled for the call, already ready, already processing, a voice-agent call, or no stored recording is available.
cURL
curl -X POST \ "https://developer.induslabs.io/api/calls/call_ab12cd34ef56gh78/transcript/retry" \ -H "Authorization: Bearer <access_token>"
Set the webhook URL and transcript behavior for calls that arrive on one of your C2C DIDs. Outbound Click2Call uses the per-call callback_url; inbound calls use this per-DID setting.
Functionality
Requires bearer access token.
Identify the DID with either phone_number_id or phone_number. Sending both is rejected; sending neither is rejected.
Send callback_url: null to clear the webhook for that DID.
transcript is a per-DID setting: it decides whether inbound recordings on that number are transcribed. It is sent on every request and defaults to false when omitted.
When an inbound callback URL is configured, the terminal call event is held until the recording is stored in S3, so the callback carries a usable recording link.
The DID must already be assigned to a user before a callback URL can be set.
Inputs
Name
Type
Default
Description
Authorization*
header
required
Bearer <access_token>
phone_number_id
string
either this or phone_number
DID phone_number_id to configure, e.g. PN_1234567890.
phone_number
string
either this or phone_number_id
DID number to configure, e.g. 919484956750.
callback_url
url
null
Public webhook URL for inbound call lifecycle events. Send null to clear it.
transcript
boolean
false
Set true to transcribe inbound recordings on this DID.
Outputs
Status
Type
Description
200 OK
application/json
Inbound callback configuration updated.
401 Unauthorized
application/json
Missing or invalid token.
403 Forbidden
application/json
The DID is assigned to another user.
404 Not Found
application/json
Phone number not found.
409 Conflict
application/json
The phone number is not assigned to any user yet.
422 Unprocessable Entity
application/json
Neither or both of phone_number_id and phone_number were sent, or callback_url is not a valid URL.
Receives the final call result from the telephony provider and closes out the Click2Call record: it stores status, durations, hangup cause, and recording, then starts recording storage and transcript processing.
Functionality
Most integrations never call this endpoint. It is the provider-facing ingest hook; consume the outcome through your callback_url or the recent-logs API instead.
It is documented here because it is the step that turns a queued call into a terminal status, and because partners running their own telephony can post results into it.
Unlike the other endpoints on this page, this route is served at the platform API root and is not behind the developer route prefix.
Accepts application/json, application/x-www-form-urlencoded, and multipart/form-data.
Field names are case and spacing tolerant. "Customer Number", "customer_number", "Agent Number", "agen_number", "DID", "Virtual Number", and "Hangup Cause" are all accepted.
customer_crm_id carries the Indus call_id and is how the provider result is matched back to the original Click2Call request.
Ingest is idempotent. A repeated post for a call already processed updates the stored record but does not re-run the recording pipeline or resend a delivered callback event.
A terminal status releases the DID channel reservation held by that call.
Inputs
Name
Type
Default
Description
X-Greeter-Username / X-Greeter-Password
header
one auth mode required
Provider credential headers.
Authorization
header
one auth mode required
Basic <provider_credentials> for the provider, or Bearer <access_token> for a user posting their own call result.
X-API-Key
header
one auth mode required
Developer API key.
auth_user / auth_pass
query string
one auth mode required
Provider credentials as query parameters, for providers that cannot set headers.
customer_number*
string
required
Customer number on the call.
status*
string
required
Provider call status, e.g. Answered, NoAnswered, failed.
recording
string
""
Provider recording URL or identifier.
agent_number
string
null
Agent number on the call.
did
string
null
Caller DID used for the call.
call_date
string
null
Provider call date/time string, e.g. 2026-04-06 18:20:00.
duration
string
null
Total call duration in seconds.
answer_duration
string
null
Connected talk time in seconds.
call_type
string
null
Call direction/type, e.g. outbound or inbound.
customer_crm_id
string
null
The Indus call_id this result belongs to.
hangup_cause
string
null
Provider hangup cause, e.g. ANSWER, BUSY, CANCEL, NOANSWER.
Outputs
Status
Type
Description
200 OK
application/json
Call log stored. Returns the resolved call_id, stored status, and recording.
401 Unauthorized
application/json
No auth mode supplied, or the supplied credentials are invalid.
403 Forbidden
application/json
Bearer auth was used for a call belonging to another user.
422 Unprocessable Entity
application/json
customer_number or status is missing, or the payload failed validation.
400 Bad Request
application/json
The body could not be parsed in any supported content type.
{ "status_code":401, "message":null, "error":"Missing auth. Use X-Greeter-Username/X-Greeter-Password, Authorization Basic/Bearer, or X-API-Key", "data":null }
Voice-Agent Calls (AGT_)
POST /api/calls/click2call can connect the customer to an Indus voice agent instead of a human agent. Pass the voice agent ID (for example AGT_E882B100) as agent_number. The platform dials customer_number and the voice agent takes the call. Use agent_config to pass call infields: per-call values such as the customer name, an amount, or a CRM ID.
How it differs from provider Click2Call
Voice-agent mode is used when agent_number starts with AGT_ (uppercase). Any other value uses the provider flow.
did is not required and is ignored. The call is placed from the phone number assigned to the agent for outbound calls, or the platform default when none is assigned.
The agent must belong to your account, or you must be a member of the agent's team.
The request waits up to about 15 seconds for the customer to answer. data.status is success if the customer answered, or failed if not. It is never queued.
Calls use the agent owner's credits and the purchased channels of the agent's outbound number. Click2Call DID channel allocation does not apply.
The stored call has call_type: agent_telephony. Its status changes from success to completed when the call ends. Filter these calls with GET /api/calls/recent?call_type=agent_telephony.
transcript_language is ignored. With transcript: true, the transcript is the voice agent's own conversation log.
Passing call infields in agent_config
Define each infield on the agent: in the dashboard (Agent tab, Call Infields), or with POST /api/agents/{agent_id}/call_infields (see Call Infields). The field_name you define, e.g. customer_name, is the key you send on each call. Defining fields is recommended so every integration uses the same names; substitution itself works for any key you send.
Use the field in the agent's system prompt or first message as a placeholder with single curly braces, e.g. {customer_name}. Make sure that prompt is in the agent's current (published) config.
On each click2call request, send the values as flat keys in agent_config. Key names must match the placeholders exactly (case-sensitive).
When the call connects, the agent replaces each placeholder with the value you sent for that call.
Do not nest the values under call_infields. Unlike POST /api/livekit, this endpoint does not unwrap a nested object.
If a placeholder has no matching key, the agent says it as-is (it speaks the text {customer_name}).
Values are inserted as text, so format dates and amounts the way the agent should say them.
Do not put secrets in agent_config.
agent_config Keys
Key
Type
Default
Description
user_name
string
customer_number
Name of the person being called. Also available to the agent as {user_name}.
voice_id
string
agent config
TTS voice for this call only. When omitted, the voice from the agent's current config is used.
language
string
agent config
Language code for this call only, e.g. hi or en.
target_agent_id
string
optional
Optional target AGT_ id used for agent routing.
sip_trunk_id
string
ignored
Ignored. The outbound number and trunk always come from the agent's number assignment.
<any other key>
string | number | boolean
optional
Custom call infield. Forwarded to the voice agent and substituted into matching {key} placeholders in the system prompt and first message.
Reserved keys
The platform sets these keys itself. Do not use them as infield names: agent_id, call_id, phone_number, from_number, phone_number_id, sip_call_to, room_id, voice_room_id, metadata, scheduled_time, timezone.
Voice-agent callbacks
If the call does not connect: call.failed with error Voice-agent call could not be connected.
With transcript: false: call.completed, then transcript.disabled, after the call ends.
With transcript: true: call.completed, then transcript.ready once the transcript and recording are linked. If they are not available within 30 minutes of the call ending, transcript.failed is sent instead.
The end of the call is checked about every 10 seconds, so events can arrive up to about 10 seconds after hang-up.
System prompt: You are a collections assistant for Acme Finance. You are speaking with {customer_name}. Their outstanding amount is Rs {loan_amount}, due on {due_date}. CRM reference: {crm_id}. First message: Namaste {customer_name} ji, main Acme Finance se baat kar rahi hoon. Kya aapke paas do minute hain?
import requests url ="https://developer.induslabs.io/api/calls/click2call" headers ={ "Content-Type":"application/json", "Authorization":"Bearer <access_token>", } payload ={ "customer_number":"919999999999", "agent_number":"AGT_E882B100", "callback_url":"https://example.com/webhooks/indus/click2call", "transcript":True, "agent_config":{ "user_name":"Aarav Sharma", "customer_name":"Aarav Sharma", "crm_id":"CRM_123", "loan_amount":250000, "due_date":"20 September 2026", }, } # The API waits up to ~15 seconds for the customer to answer, so use a longer timeout. resp = requests.post(url, json=payload, headers=headers, timeout=60) print(resp.status_code, resp.json())
{ "detail":"Insufficient credits to initiate call.", "request_id":"<request_id>" }
DID Channel Capacity Model
Provider Click2Call concurrency is governed by a shared channel pool that you distribute across your assigned C2C DIDs. Each accepted call reserves one channel on its DID and releases it when the call reaches a terminal status. Voice-agent (AGT_) calls are outside this model: they consume the purchased channels of the agent outbound number instead.
Terms
Field
Meaning
pool_total_channels
Total concurrent capacity for your account. Derived from your C2C batch totals, falling back to the sum of purchased channels across assigned C2C numbers.
pool_active_channels
Channels currently reserved by live Click2Call requests, counted account-wide from live call records.
pool_available_channels
pool_total_channels minus pool_active_channels.
max_channels
Hard ceiling for one DID, set when the number was provisioned. An allocation may never exceed it.
allocated_channels
Concurrent channels you have assigned to one DID. The sum across DIDs may not exceed pool_total_channels.
active_channels
Live reservations currently held on that DID.
available_channels
allocated_channels minus active_channels, floored at zero.
did_status
active or expired. A retired DID keeps its record and history rather than being deleted.
Over-limit behavior
Exceeding a DID allocation or the shared pool does not reject the call. The request is accepted and data.warning explains which limit was crossed.
Possible warnings: Allocated channel limit exceeded for this DID; call accepted, No channels allocated for this DID; call accepted, and Client channel pool exhausted; call accepted. Multiple warnings are joined with |.
Treat a non-null warning as a signal to slow down or rebalance allocations, not as a failure.
Check GET /api/mobile-numbers/channel-allocation/utilization before a burst if you need to stay strictly inside your limits.
A channel reserved by a call the provider never reports on is freed automatically after 5 minutes, and the call moves to temporaryfailed.
C2C Call Statuses
The main Click2Call status values currently used in the API are queued, Answered, NoAnswered, failed, and temporaryfailed. Compare status values case-insensitively: the provider sends mixed casing such as Answered and NoAnswered.
Status Reference
Status
Meaning
queued
The Click2Call request was accepted and is waiting for final provider processing.
answered
The provider reported that the call was answered.
noAnswered
The provider reported that the call was not answered.
failed
The call failed technically or could not be completed.
temporaryfailed
The provider never reported on the call within 5 minutes, so it was timed out. A late provider callback can still replace this with the real outcome.
afthrs
The provider rejected the call as outside permitted calling hours. Treated as a not-answered terminal result.
success
Voice-agent (AGT_) calls only: the customer answered and the voice agent is on the call.
completed
Voice-agent (AGT_) calls only: the call has ended.
Notes
Answered and NoAnswered are provider-style status values and should be treated as final call results.
queued is the initial state before a final provider callback is received.
call.completed is a callback event name, not the same thing as a stored call status value.
A call that reaches any terminal status releases the DID channel it reserved. Terminal statuses are Answered, NoAnswered, afthrs, failed, temporaryfailed, and completed.
temporaryfailed is provisional. If the provider reports late, the record is reprocessed and the corrected outcome is delivered to your callback_url.
Answered calls also carry hangup_cause from the provider, for example ANSWER, BUSY, CANCEL, or NOANSWER.
How Click2Call Callbacks Work
callback_url is your webhook endpoint. When you pass it in the create request, the backend sends POST requests to that URL as the call moves through completion and transcript states.
Callback sequence
Your server calls POST /api/calls/click2call with callback_url.
The API returns call_id immediately. Store it.
The backend sends the outbound call request to the provider.
The provider later calls the backend with call status and recording details.
The backend stores the recording and starts transcript processing when transcript is true.
The backend POSTs lifecycle events to your callback_url.
Important behavior
Your callback_url must be publicly reachable from the backend. Do not use localhost for production calls.
The callback is sent in the background. Your create request does not wait for the call, recording, or transcript.
The callback data object uses the same compact shape as GET /api/calls/{call_id}/transcript.
Return a 2xx response from your webhook to mark the event as delivered.
If no callback_url is supplied, no webhook is sent. Use GET /api/calls/recent or transcript polling.
Each event is delivered once per call. Retrying a transcript with POST /api/calls/{call_id}/transcript/retry re-arms the transcript events so they can be delivered again.
Inbound calls use the per-DID webhook set with POST /api/calls/inbound-callback-url instead of a per-call callback_url.
Callback Events
Event
Meaning
call.answered
The provider reported status Answered. The call connected.
call.not_answered
The provider reported NoAnswered or afthrs. The customer did not take the call.
call.temporaryfailed
The call was timed out after 5 minutes without a provider report. A later report can still supersede this with the real outcome.
call.completed
The call reached a terminal state that maps to none of the specific events above, and call-log data was stored.
call.failed
The call failed to connect, the provider rejected the request, or the provider reported a failed terminal state.
Login to get access_token, and refresh it with POST /api/refresh_token rather than logging in again on every run.
Call GET /api/mobile-numbers once to discover the C2C DIDs assigned to you and their channel ceilings.
Set your concurrency plan with PUT /api/mobile-numbers/channel-allocation, then retune individual DIDs later with PATCH.
Create a click2call request: with did for provider calls, or with an AGT_agent_number and agent_config infields for voice-agent calls. Add an optional callback_url, and transcript: true when a transcript is needed.
Store call_id from the create response, and check data.warning for capacity pressure.
Use callbacks as the primary async notification path when callback_url is configured.
Use GET /api/calls/recent to list and filter many calls efficiently.
Use GET /api/calls/{call_id}/transcript when you need a single call transcript payload, and POST /api/calls/{call_id}/transcript/retry if one failed.
For inbound traffic, register a webhook per DID with POST /api/calls/inbound-callback-url.
While running a campaign, poll GET /api/mobile-numbers/channel-allocation/utilization to pace dialing against free channels.
End-to-End Curl Flow (Quick Copy)
BASE_URL="https://developer.induslabs.io" LOGIN_RESP=$(curl -s -X POST "$BASE_URL/api/login" \ -H "Content-Type: application/json" \ -d '{"email":"user@example.com","password":"your_password"}') ACCESS_TOKEN=$(echo "$LOGIN_RESP" | jq -r '.data.access_token') # Discover the C2C DIDs assigned to you curl -s -X GET "$BASE_URL/api/mobile-numbers" \ -H "Authorization: Bearer $ACCESS_TOKEN" # Confirm there is free concurrency before dialing curl -s -X GET "$BASE_URL/api/mobile-numbers/channel-allocation/utilization?did=919484956750" \ -H "Authorization: Bearer $ACCESS_TOKEN" CALL_RESP=$(curl -s -X POST "$BASE_URL/api/calls/click2call" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -d '{ "customer_number":"919999999999", "agent_number":"918888888888", "did":"919484956750", "callback_url":"https://example.com/webhooks/indus/click2call", "transcript":true, "transcript_language":"hi" }') CALL_ID=$(echo "$CALL_RESP" | jq -r '.data.call_id') curl -s -X GET "$BASE_URL/api/calls/recent?limit=10&page=1" \ -H "Authorization: Bearer $ACCESS_TOKEN" curl -s -X GET "$BASE_URL/api/calls/$CALL_ID/transcript" \ -H "Authorization: Bearer $ACCESS_TOKEN" # If the transcript failed, reprocess the stored recording curl -s -X POST "$BASE_URL/api/calls/$CALL_ID/transcript/retry" \ -H "Authorization: Bearer $ACCESS_TOKEN"
Notes
Transcript endpoint may return 200 with transcript: null while processing; this is expected.
Transcript failures return 200 with transcript_status: failed and the detailed reason in data.error.
Provider phone-number Click2Call requires did.
Voice-agent (AGT_) calls ignore did and read call infields from flat keys in agent_config.
The recent logs API is better than per-call polling when you manage multiple concurrent calls.
Channel allocation applies only to provider Click2Call. Voice-agent (AGT_) calls draw on the purchased channels of the agent outbound number.
Requests over your DID or pool limit are accepted with a warning rather than rejected. Read data.warning on every create response.
PUT on channel allocation is a full replacement: any assigned DID you omit drops to zero. Use PATCH for partial updates.
Outbound calls use the per-call callback_url; inbound calls use the per-DID webhook from POST /api/calls/inbound-callback-url.
temporaryfailed is provisional and can still be corrected by a late provider report.
Keep tokens secure; do not expose them in frontend logs.