Skip to main content

Click to Call (C2C) Service APIs

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

EndpointPurpose
POST /api/loginAuthenticate and obtain access and refresh tokens.
POST /api/refresh_tokenIssue a new access token from a refresh token.
POST /api/calls/click2callCreate a Click2Call request, provider or voice agent.
GET /api/calls/recentList and filter recent Click2Call records.
GET /api/calls/{call_id}/transcriptFetch one call result, recording, and transcript.
POST /api/calls/{call_id}/transcript/retryReprocess a failed or stalled transcript.
GET /api/mobile-numbersList assigned C2C DIDs with a channel snapshot.
GET /api/mobile-numbers/channel-allocationRead the shared pool and per-DID allocation state.
PUT /api/mobile-numbers/channel-allocationReplace the full DID allocation map.
PATCH /api/mobile-numbers/channel-allocationUpdate selected DID allocations only.
GET /api/mobile-numbers/channel-allocation/utilizationLive utilization counters for pre-call checks.
POST /api/calls/inbound-callback-urlSet the inbound webhook and transcript flag for a DID.
GET /api/calls/inbound-callback-urlList inbound webhook settings across your DIDs.
POST /api/calls/call-logProvider-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

NameTypeDefaultDescription
email*stringrequiredUser email address.
password*stringrequiredUser password.

Outputs

StatusTypeDescription
200 OKapplication/jsonLogin successful with access and refresh tokens.
400 Bad Requestapplication/jsonIncorrect credentials, inactive user, or unverified user.

cURL

curl -X POST \
"https://developer.induslabs.io/api/login" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "your_password"
}'

Success Response (200)

{
"status_code": 200,
"message": "Login successful",
"error": null,
"data": {
"access_token": "<jwt_access_token>",
"refresh_token": "<jwt_refresh_token>",
"token_type": "bearer"
}
}
POST/api/calls/click2call

Create Click2Call Request

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

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
customer_number*stringrequiredCustomer phone number with country code, digits only, e.g. 919999999999.
agent_number*stringrequiredHuman 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.
didstringrequired for provider flowCaller DID/provider number used to place the Click2Call request. Ignored for AGT_ voice-agent calls.
callback_urlurloptionalPublic webhook URL that receives backend POST callbacks for this call.
transcriptbooleanfalseSet 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_languagestringnullOptional transcript language. If omitted, the default hi-en transcription flow is used. Ignored for AGT_ voice-agent calls.
agent_configobject{}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

StatusTypeDescription
200 OKapplication/jsonClick2Call request created. The provider flow returns status queued; the AGT_ flow returns success or failed.
401 Unauthorizedapplication/jsonMissing or invalid token.
402 Payment Requiredapplication/jsonAGT_ flow: insufficient credits, or no channels purchased for the agent outbound number.
403 Forbiddenapplication/jsonProvider flow: DID is not assigned to you. AGT_ flow: the agent is not owned by you or your team.
404 Not Foundapplication/jsonProvider flow: DID not found. AGT_ flow: agent not found.
409 Conflictapplication/jsonProvider flow: unable to reserve a DID channel at this time.
422 Unprocessable Entityapplication/jsondid is missing for provider flow, callback_url is invalid, or request validation failed.
429 Too Many Requestsapplication/jsonAGT_ flow: channel limit reached for the agent outbound number.
400 Bad Requestapplication/jsonInactive user or validation issues.

cURL

curl -X POST \
"https://developer.induslabs.io/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"
}'

Success Response (200)

{
"status_code": 200,
"message": "Click2Call request created",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"status": "queued",
"did": "919484956750",
"callback_url": "https://example.com/webhooks/indus/click2call",
"transcript": true,
"transcript_language": "hi",
"warning": null
}
}

cURL: Voice-Agent Call (AGT_)

curl -X POST \
"https://developer.induslabs.io/api/calls/click2call" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"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"
}
}'

Success Response: Voice-Agent Call (200)

{
"status_code": 200,
"message": "Click2Call request created",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"status": "success",
"did": null,
"callback_url": "https://example.com/webhooks/indus/click2call",
"transcript": true,
"transcript_language": null,
"warning": null
}
}
GET/api/calls/recent

Get Recent Click2Call Logs

Fetch paginated Click2Call records for the authenticated user with filters for call status, transcript status, phone numbers, DID, and date range.

Functionality
  • Requires bearer access token.
  • Use this endpoint to monitor many calls without polling every call_id individually.
  • When transcript_status is ready, each call can include transcript data. Otherwise transcript is null.
  • recording and recording_url are signed recording URLs when available. Before provider callback or upload, they can be pending.

Inputs

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
statusquery stringoptionalExact call status filter, e.g. queued, Answered, NoAnswered, or failed.
call_typequery stringoptionalExact call type filter, e.g. C2C, outbound, agent_telephony.
transcript_statusquery stringoptionalFilter by pending, processing, ready, disabled, or failed.
customer_numberquery stringoptionalCase-insensitive partial match on customer number.
agent_numberquery stringoptionalCase-insensitive partial match on agent number or AGT_ id.
didquery stringoptionalCase-insensitive partial match on DID.
date_fromISO datetimeoptionalReturn records created at or after this date. Z timezone is accepted.
date_toISO datetimeoptionalReturn records created at or before this date. Z timezone is accepted.
limitinteger20Page size. Must be between 1 and 200.
pageinteger1Page number. Must be 1 or higher.

Outputs

StatusTypeDescription
200 OKapplication/jsonPaginated recent Click2Call records.
401 Unauthorizedapplication/jsonMissing or invalid token.
422 Unprocessable Entityapplication/jsonInvalid pagination or date filter.

cURL

curl -X GET \
"https://developer.induslabs.io/api/calls/recent?limit=10&page=1&transcript_status=ready" \
-H "Authorization: Bearer <access_token>"

Success Response (200)

{
"status_code": 200,
"message": "Recent call logs fetched",
"error": null,
"data": {
"total": 1,
"page": 1,
"limit": 10,
"has_more": false,
"filters": {
"status": null,
"call_type": null,
"transcript_status": "ready",
"customer_number": null,
"agent_number": null,
"did": null,
"date_from": null,
"date_to": null
},
"calls": [
{
"call_id": "call_ab12cd34ef56gh78",
"status": "Answered",
"customer_number": "919999999999",
"agent_number": "918888888888",
"did": "919484956750",
"duration": "52",
"answer_duration": "48",
"hangup_cause": "ANSWER",
"call_type": "C2C",
"transcript_enabled": true,
"transcript_status": "ready",
"recording": "https://signed-recording-url",
"recording_url": "https://signed-recording-url",
"transcript": {
"summary": "Call summary text",
"call_outcome": "Interested",
"history": []
},
"created_at": "2026-04-14T06:50:00Z",
"updated_at": "2026-04-14T06:52:00Z"
}
]
}
}
GET/api/calls/{call_id}/transcript

Get Click2Call Transcript by Call ID

Fetch transcript metadata/content for a previously created click2call request.

Functionality
  • Requires bearer access token.
  • Polling may return transcript: null with transcript_status: pending while processing.
  • If processing fails, the API still returns 200 with transcript_status: failed and the failure reason in data.error.
  • Phone numbers are masked in this endpoint response.

Inputs

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
call_id*pathrequiredCall ID returned by click2call endpoint.

Outputs

StatusTypeDescription
200 OKapplication/jsonTranscript pending, ready, disabled, or failed payload.
403 Forbiddenapplication/jsonUser is not authorized for this call.
404 Not Foundapplication/jsoncall_id not found.
401 Unauthorizedapplication/jsonMissing or invalid token.

cURL

curl -X GET \
"https://developer.induslabs.io/api/calls/call_ab12cd34ef56gh78/transcript" \
-H "Authorization: Bearer <access_token>"

Success: Transcript Not Ready (200)

{
"status_code": 200,
"message": "Transcript not available yet",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"transcript_status": "pending",
"customer_number": "xxxxx9999999",
"agent_number": "xxxxx8888888",
"duration": null,
"recording": "pending",
"transcript": null
}
}

Success: Transcript Ready (200)

{
"status_code": 200,
"message": "Transcript found",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"transcript_status": "ready",
"customer_number": "xxxxx9999999",
"agent_number": "xxxxx8888888",
"duration": "52",
"hangup_cause": "ANSWER",
"recording": "https://signed-recording-url",
"transcript": {
"transcript_id": "67c7....",
"summary": "Call summary text",
"call_outcome": "Interested",
"history": [],
"createdAt": "2026-03-05T10:00:00Z",
"updatedAt": "2026-03-05T10:01:00Z"
}
}
}

Success: Transcript Failed (200)

{
"status_code": 200,
"message": "Transcript processing failed",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"transcript_status": "failed",
"customer_number": "xxxxx9999999",
"agent_number": "xxxxx8888888",
"duration": "52",
"recording": "https://signed-recording-url",
"transcript": null,
"error": "Client error '401 Unauthorized' for url 'https://voice.induslabs.io/v1/audio/transcribe/diarize'"
}
}
POST/api/refresh_token

Refresh Access Token

Exchange a valid refresh token for a new access token without asking the user to log in again.

Functionality
  • refresh_token is sent as a query parameter, not in the request body.
  • The refresh token itself is not rotated. The same refresh token is returned and stays valid until it expires.
  • The refresh token must carry the session it was issued with. Tokens issued outside a login session are rejected.

Inputs

NameTypeDefaultDescription
refresh_token*query stringrequiredRefresh token returned by POST /api/login.

Outputs

StatusTypeDescription
200 OKapplication/jsonNew access token issued.
400 Bad Requestapplication/jsonRefresh token could not be decoded or carries no session.
401 Unauthorizedapplication/jsonRefresh token is expired, revoked, or invalid.
422 Unprocessable Entityapplication/jsonrefresh_token query parameter is missing.

cURL

curl -X POST \
"https://developer.induslabs.io/api/refresh_token?refresh_token=<refresh_token>" \
-H "Accept: application/json"

Success Response (200)

{
"status_code": 200,
"message": "Token refreshed successfully",
"error": null,
"data": {
"access_token": "<new_jwt_access_token>",
"refresh_token": "<same_refresh_token>",
"token_type": "bearer"
}
}
GET/api/mobile-numbers

List Assigned C2C DIDs

List the C2C DIDs assigned to the authenticated user, with a lightweight channel snapshot for each number.

Functionality
  • Requires bearer access token.
  • Only numbers assigned to you whose metadata type is c2c are returned. Voice-agent numbers are not listed here.
  • Use this endpoint to discover the did values you can pass to POST /api/calls/click2call.
  • active_channels is counted from live call records, so it reflects real concurrency rather than a stored counter.
  • Stale channel reservations are swept before the response is built, so the counts are current at read time.
  • Up to 500 numbers are returned. There is no pagination on this endpoint.

Inputs

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>

Outputs

StatusTypeDescription
200 OKapplication/jsonAssigned C2C numbers with pool totals and per-DID channel counts.
401 Unauthorizedapplication/jsonMissing or invalid token.

cURL

curl -X GET \
"https://developer.induslabs.io/api/mobile-numbers" \
-H "Authorization: Bearer <access_token>" \
-H "Accept: application/json"

Success Response (200)

{
"status_code": 200,
"message": "Mobile numbers fetched successfully",
"error": null,
"data": {
"user_id": "USR_2079589C",
"number_type": "c2c",
"count": 2,
"pool_total_channels": 50,
"pool_active_channels": 7,
"numbers": [
{
"phone_number_id": "PN_1234567890",
"mobile_number": "919484956750",
"did_status": "active",
"max_channels": 20,
"allocated_channels": 5,
"active_channels": 3
},
{
"phone_number_id": "PN_1234567891",
"mobile_number": "919484956751",
"did_status": "expired",
"max_channels": 20,
"allocated_channels": 0,
"active_channels": 0
}
]
}
}
GET/api/mobile-numbers/channel-allocation

Get DID Channel Allocation

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

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>

Outputs

StatusTypeDescription
200 OKapplication/jsonPool summary plus per-DID allocated, active, and available channel counts.
401 Unauthorizedapplication/jsonMissing or invalid token.

cURL

curl -X GET \
"https://developer.induslabs.io/api/mobile-numbers/channel-allocation" \
-H "Authorization: Bearer <access_token>" \
-H "Accept: application/json"

Success Response (200)

{
"status_code": 200,
"message": "Channel allocation fetched successfully",
"error": null,
"data": {
"user_id": "USR_2079589C",
"pool_total_channels": 50,
"pool_active_channels": 7,
"pool_available_channels": 43,
"count": 2,
"numbers": [
{
"phone_number_id": "PN_1234567890",
"mobile_number": "919484956750",
"did_status": "active",
"max_channels": 20,
"allocated_channels": 5,
"active_channels": 3,
"available_channels": 2
},
{
"phone_number_id": "PN_1234567891",
"mobile_number": "919484956751",
"did_status": "active",
"max_channels": 20,
"allocated_channels": 10,
"active_channels": 4,
"available_channels": 6
}
]
}
}
PUT/api/mobile-numbers/channel-allocation

Replace DID Channel Allocation

Replace the entire DID channel allocation map for the authenticated user. Any assigned DID left out of the request is treated as zero allocation.

Functionality
  • Requires bearer access token.
  • This is a full replacement. Omitted DIDs drop to zero allocated channels, so always send your complete map. Use PATCH to change only some DIDs.
  • Identify each DID by either did or phone_number_id. Sending both on one item is rejected; sending neither is rejected.
  • No DID may appear twice in one request.
  • Each allocated_channels must be between 0 and that DID max_channels, and may not drop below the DID current active_channels.
  • The sum of all allocated_channels must not exceed pool_total_channels.
  • The response body is the same shape as GET /api/mobile-numbers/channel-allocation.

Inputs

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
allocations*arrayrequiredComplete list of allocation items. Must contain at least one item.
allocations[].didstringeither this or phone_number_idAssigned C2C DID number, e.g. 919484956750.
allocations[].phone_number_idstringeither this or didAssigned C2C DID phone_number_id, e.g. PN_1234567890.
allocations[].allocated_channels*integerrequiredMaximum concurrent channels for this DID. Must be 0 or greater.

Outputs

StatusTypeDescription
200 OKapplication/jsonAllocation replaced. Returns the updated full allocation state.
401 Unauthorizedapplication/jsonMissing or invalid token.
404 Not Foundapplication/jsonA DID in the request is not assigned to the current user.
422 Unprocessable Entityapplication/jsonEmpty allocations, duplicate DID target, allocation above max_channels, allocation below live active channels, or total above the shared pool.

cURL

curl -X PUT \
"https://developer.induslabs.io/api/mobile-numbers/channel-allocation" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"allocations": [
{ "did": "919484956750", "allocated_channels": 5 },
{ "did": "919484956751", "allocated_channels": 10 },
{ "phone_number_id": "PN_1234567892", "allocated_channels": 0 }
]
}'

Success Response (200)

{
"status_code": 200,
"message": "Channel allocation fetched successfully",
"error": null,
"data": {
"user_id": "USR_2079589C",
"pool_total_channels": 50,
"pool_active_channels": 7,
"pool_available_channels": 43,
"count": 2,
"numbers": [
{
"phone_number_id": "PN_1234567890",
"mobile_number": "919484956750",
"did_status": "active",
"max_channels": 20,
"allocated_channels": 5,
"active_channels": 3,
"available_channels": 2
},
{
"phone_number_id": "PN_1234567891",
"mobile_number": "919484956751",
"did_status": "active",
"max_channels": 20,
"allocated_channels": 10,
"active_channels": 4,
"available_channels": 6
}
]
}
}

Validation Error (422)

{
"status_code": 422,
"message": null,
"error": "Total allocated channels (60) exceed the shared pool (50).",
"data": null
}
PATCH/api/mobile-numbers/channel-allocation

Update Some DID Channel Allocations

Update only the DIDs listed in the request. Every DID not named keeps its current allocation.

Functionality
  • Requires bearer access token.
  • Use this instead of PUT when you only need to retune a few DIDs and do not want to resend the whole map.
  • The same identification, max_channels, active-channel, and shared-pool rules as the PUT endpoint apply.
  • The response body is the same shape as GET /api/mobile-numbers/channel-allocation.

Inputs

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
allocations*arrayrequiredPartial list of allocation items. Must contain at least one item. Unlisted DIDs are left unchanged.
allocations[].didstringeither this or phone_number_idAssigned C2C DID number, e.g. 919484956750.
allocations[].phone_number_idstringeither this or didAssigned C2C DID phone_number_id, e.g. PN_1234567890.
allocations[].allocated_channels*integerrequiredNew maximum concurrent channels for this DID. Must be 0 or greater.

Outputs

StatusTypeDescription
200 OKapplication/jsonAllocation updated. Returns the updated full allocation state.
401 Unauthorizedapplication/jsonMissing or invalid token.
404 Not Foundapplication/jsonA DID in the request is not assigned to the current user.
422 Unprocessable Entityapplication/jsonEmpty allocations, duplicate DID target, allocation above max_channels, allocation below live active channels, or total above the shared pool.

cURL

curl -X PATCH \
"https://developer.induslabs.io/api/mobile-numbers/channel-allocation" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"allocations": [
{ "did": "919484956750", "allocated_channels": 8 }
]
}'

Success Response (200)

{
"status_code": 200,
"message": "Channel allocation fetched successfully",
"error": null,
"data": {
"user_id": "USR_2079589C",
"pool_total_channels": 50,
"pool_active_channels": 7,
"pool_available_channels": 43,
"count": 2,
"numbers": [
{
"phone_number_id": "PN_1234567890",
"mobile_number": "919484956750",
"did_status": "active",
"max_channels": 20,
"allocated_channels": 5,
"active_channels": 3,
"available_channels": 2
},
{
"phone_number_id": "PN_1234567891",
"mobile_number": "919484956751",
"did_status": "active",
"max_channels": 20,
"allocated_channels": 10,
"active_channels": 4,
"available_channels": 6
}
]
}
}

Validation Error (422)

{
"status_code": 422,
"message": null,
"error": "Total allocated channels (60) exceed the shared pool (50).",
"data": null
}
GET/api/mobile-numbers/channel-allocation/utilization

Get Live Channel Utilization

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

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
didquery stringoptionalRestrict the numbers array to a single assigned C2C DID, e.g. 919484956750.

Outputs

StatusTypeDescription
200 OKapplication/jsonPool and per-DID utilization counters.
401 Unauthorizedapplication/jsonMissing or invalid token.
404 Not Foundapplication/jsonThe 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"

Success Response (200)

{
"status_code": 200,
"message": "Channel utilization fetched successfully",
"error": null,
"data": {
"user_id": "USR_2079589C",
"pool_total_channels": 50,
"pool_active_channels": 7,
"pool_available_channels": 43,
"numbers": [
{
"phone_number_id": "PN_1234567890",
"did": null,
"allocated_channels": 5,
"active_channels": 3,
"available_channels": 2
}
]
}
}
POST/api/calls/{call_id}/transcript/retry

Retry Click2Call Transcript

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

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
call_id*pathrequiredCall ID returned by the click2call endpoint.

Outputs

StatusTypeDescription
200 OKapplication/jsonRetry queued. transcript_status is set back to pending.
401 Unauthorizedapplication/jsonMissing or invalid token.
403 Forbiddenapplication/jsonThe call belongs to another user.
404 Not Foundapplication/jsoncall_id not found.
409 Conflictapplication/jsonTranscript 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>"

Success Response (200)

{
"status_code": 200,
"message": "Transcript retry queued",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"transcript_status": "pending",
"status": "Answered",
"recording": "https://signed-recording-url"
}
}
POST/api/calls/inbound-callback-url

Configure Inbound DID Callback

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

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
phone_number_idstringeither this or phone_numberDID phone_number_id to configure, e.g. PN_1234567890.
phone_numberstringeither this or phone_number_idDID number to configure, e.g. 919484956750.
callback_urlurlnullPublic webhook URL for inbound call lifecycle events. Send null to clear it.
transcriptbooleanfalseSet true to transcribe inbound recordings on this DID.

Outputs

StatusTypeDescription
200 OKapplication/jsonInbound callback configuration updated.
401 Unauthorizedapplication/jsonMissing or invalid token.
403 Forbiddenapplication/jsonThe DID is assigned to another user.
404 Not Foundapplication/jsonPhone number not found.
409 Conflictapplication/jsonThe phone number is not assigned to any user yet.
422 Unprocessable Entityapplication/jsonNeither or both of phone_number_id and phone_number were sent, or callback_url is not a valid URL.

cURL

curl -X POST \
"https://developer.induslabs.io/api/calls/inbound-callback-url" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"phone_number": "919484956750",
"callback_url": "https://example.com/webhooks/indus/inbound",
"transcript": true
}'

Success Response (200)

{
"status_code": 200,
"message": "Inbound callback configuration updated",
"error": null,
"data": {
"phone_number_id": "PN_1234567890",
"phone_number": "919484956750",
"assigned_user_id": "USR_2079589C",
"callback_url": "https://example.com/webhooks/indus/inbound",
"transcript": true
}
}

cURL: Clear the callback URL

curl -X POST \
"https://developer.induslabs.io/api/calls/inbound-callback-url" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"phone_number_id": "PN_1234567890",
"callback_url": null,
"transcript": false
}'
GET/api/calls/inbound-callback-url

List Inbound DID Callback Settings

List the inbound webhook and transcript settings for every C2C DID assigned to you.

Functionality
  • Requires bearer access token.
  • Use configured_only=true to see just the DIDs that already have a webhook, which is the quickest way to audit inbound coverage.
  • callback_url is null for DIDs with no webhook configured.
  • Up to 500 numbers are returned. There is no pagination on this endpoint.

Inputs

NameTypeDefaultDescription
Authorization*headerrequiredBearer <access_token>
configured_onlyquery booleanfalseSet true to return only DIDs that have an inbound callback URL configured.
user_idquery stringoptionalSuperusers only. Inspect another user DID callback configuration. Ignored for regular users.

Outputs

StatusTypeDescription
200 OKapplication/jsonInbound callback configuration for each assigned C2C DID.
401 Unauthorizedapplication/jsonMissing or invalid token.

cURL

curl -X GET \
"https://developer.induslabs.io/api/calls/inbound-callback-url?configured_only=true" \
-H "Authorization: Bearer <access_token>"

Success Response (200)

{
"status_code": 200,
"message": "Inbound callback configurations fetched",
"error": null,
"data": {
"user_id": "USR_2079589C",
"configured_only": true,
"count": 1,
"callbacks": [
{
"phone_number_id": "PN_1234567890",
"phone_number": "919484956750",
"assigned_user_id": "USR_2079589C",
"assigned_org_id": null,
"assigned_agent_id": null,
"assigned_workflow_id": null,
"callback_url": "https://example.com/webhooks/indus/inbound",
"transcript": true,
"status": "active",
"provider_name": "greeter",
"metadata_type": "c2c"
}
]
}
}
POST/api/calls/call-log

Ingest Provider Call Log

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

NameTypeDefaultDescription
X-Greeter-Username / X-Greeter-Passwordheaderone auth mode requiredProvider credential headers.
Authorizationheaderone auth mode requiredBasic <provider_credentials> for the provider, or Bearer <access_token> for a user posting their own call result.
X-API-Keyheaderone auth mode requiredDeveloper API key.
auth_user / auth_passquery stringone auth mode requiredProvider credentials as query parameters, for providers that cannot set headers.
customer_number*stringrequiredCustomer number on the call.
status*stringrequiredProvider call status, e.g. Answered, NoAnswered, failed.
recordingstring""Provider recording URL or identifier.
agent_numberstringnullAgent number on the call.
didstringnullCaller DID used for the call.
call_datestringnullProvider call date/time string, e.g. 2026-04-06 18:20:00.
durationstringnullTotal call duration in seconds.
answer_durationstringnullConnected talk time in seconds.
call_typestringnullCall direction/type, e.g. outbound or inbound.
customer_crm_idstringnullThe Indus call_id this result belongs to.
hangup_causestringnullProvider hangup cause, e.g. ANSWER, BUSY, CANCEL, NOANSWER.

Outputs

StatusTypeDescription
200 OKapplication/jsonCall log stored. Returns the resolved call_id, stored status, and recording.
401 Unauthorizedapplication/jsonNo auth mode supplied, or the supplied credentials are invalid.
403 Forbiddenapplication/jsonBearer auth was used for a call belonging to another user.
422 Unprocessable Entityapplication/jsoncustomer_number or status is missing, or the payload failed validation.
400 Bad Requestapplication/jsonThe body could not be parsed in any supported content type.

cURL

curl -X POST \
"https://developer.induslabs.io/api/calls/call-log" \
-H "Content-Type: application/json" \
-H "X-Greeter-Username: <provider_username>" \
-H "X-Greeter-Password: <provider_password>" \
-d '{
"recording": "https://example.com/recording.wav",
"customer_number": "919876543210",
"agent_number": "918888888888",
"did": "919484956750",
"call_date": "2026-04-06 18:20:00",
"duration": "120",
"answer_duration": "110",
"status": "Answered",
"call_type": "outbound",
"customer_crm_id": "call_ab12cd34ef56gh78",
"hangup_cause": "ANSWER"
}'

Success Response (200)

{
"status_code": 200,
"message": "Call log stored",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"status": "Answered",
"recording": "https://example.com/recording.wav"
}
}

Missing Auth (401)

{
"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
  1. 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.
  2. 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.
  3. On each click2call request, send the values as flat keys in agent_config. Key names must match the placeholders exactly (case-sensitive).
  4. 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

KeyTypeDefaultDescription
user_namestringcustomer_numberName of the person being called. Also available to the agent as {user_name}.
voice_idstringagent configTTS voice for this call only. When omitted, the voice from the agent's current config is used.
languagestringagent configLanguage code for this call only, e.g. hi or en.
target_agent_idstringoptionalOptional target AGT_ id used for agent routing.
sip_trunk_idstringignoredIgnored. The outbound number and trunk always come from the agent's number assignment.
<any other key>string | number | booleanoptionalCustom 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.

Step 1: Define an Infield on the Agent

curl -X POST \
"https://developer.induslabs.io/api/agents/AGT_E882B100/call_infields" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"field_name": "customer_name",
"field_type": "TEXT",
"is_visible": true
}'

Step 2: Agent Prompt Using Infields

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?

Step 3: Place the Call (cURL)

curl -X POST \
"https://developer.induslabs.io/api/calls/click2call" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-d '{
"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"
}
}'

Step 3: Place the Call (Python)

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())

Success: Customer Answered (200)

{
"status_code": 200,
"message": "Click2Call request created",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"status": "success",
"did": null,
"callback_url": "https://example.com/webhooks/indus/click2call",
"transcript": true,
"transcript_language": null,
"warning": null
}
}

Call Not Connected (200)

{
"status_code": 200,
"message": "Click2Call request created",
"error": null,
"data": {
"call_id": "call_ab12cd34ef56gh78",
"status": "failed",
"did": null,
"callback_url": "https://example.com/webhooks/indus/click2call",
"transcript": true,
"transcript_language": null,
"warning": null
}
}

Error: Insufficient Credits (402)

{
"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

FieldMeaning
pool_total_channelsTotal 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_channelsChannels currently reserved by live Click2Call requests, counted account-wide from live call records.
pool_available_channelspool_total_channels minus pool_active_channels.
max_channelsHard ceiling for one DID, set when the number was provisioned. An allocation may never exceed it.
allocated_channelsConcurrent channels you have assigned to one DID. The sum across DIDs may not exceed pool_total_channels.
active_channelsLive reservations currently held on that DID.
available_channelsallocated_channels minus active_channels, floored at zero.
did_statusactive 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

StatusMeaning
queuedThe Click2Call request was accepted and is waiting for final provider processing.
answeredThe provider reported that the call was answered.
noAnsweredThe provider reported that the call was not answered.
failedThe call failed technically or could not be completed.
temporaryfailedThe 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.
afthrsThe provider rejected the call as outside permitted calling hours. Treated as a not-answered terminal result.
successVoice-agent (AGT_) calls only: the customer answered and the voice agent is on the call.
completedVoice-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
  1. Your server calls POST /api/calls/click2call with callback_url.
  2. The API returns call_id immediately. Store it.
  3. The backend sends the outbound call request to the provider.
  4. The provider later calls the backend with call status and recording details.
  5. The backend stores the recording and starts transcript processing when transcript is true.
  6. 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

EventMeaning
call.answeredThe provider reported status Answered. The call connected.
call.not_answeredThe provider reported NoAnswered or afthrs. The customer did not take the call.
call.temporaryfailedThe call was timed out after 5 minutes without a provider report. A later report can still supersede this with the real outcome.
call.completedThe call reached a terminal state that maps to none of the specific events above, and call-log data was stored.
call.failedThe call failed to connect, the provider rejected the request, or the provider reported a failed terminal state.
transcript.readyRecording transcript processing completed successfully.
transcript.failedTranscript processing failed. The failure reason is sent in top-level error and data.error.
transcript.disabledtranscript was false, so transcript processing was skipped.

Callback Payload: Call Completed

{
"status_code": 200,
"message": "Call completed",
"error": null,
"event": "call.completed",
"data": {
"call_id": "call_ab12cd34ef56gh78",
"transcript_status": "processing",
"customer_number": "xxxxx9999999",
"agent_number": "xxxxx8888888",
"duration": "52",
"recording": "https://signed-recording-url",
"transcript": null
}
}

Callback Payload: Transcript Ready

{
"status_code": 200,
"message": "Transcript ready",
"error": null,
"event": "transcript.ready",
"data": {
"call_id": "call_ab12cd34ef56gh78",
"transcript_status": "ready",
"customer_number": "xxxxx9999999",
"agent_number": "xxxxx8888888",
"duration": "52",
"recording": "https://signed-recording-url",
"transcript": {
"transcript_id": "67c7....",
"summary": "Call summary text",
"call_outcome": "Interested",
"history": [],
"createdAt": "2026-03-05T10:00:00Z",
"updatedAt": "2026-03-05T10:01:00Z"
}
}
}

Callback Payload: Transcript Failed

{
"status_code": 500,
"message": "Transcript processing failed",
"error": "Client error '401 Unauthorized' for url 'https://voice.induslabs.io/v1/audio/transcribe/diarize'",
"event": "transcript.failed",
"data": {
"call_id": "call_ab12cd34ef56gh78",
"transcript_status": "failed",
"customer_number": "xxxxx9999999",
"agent_number": "xxxxx8888888",
"duration": "52",
"recording": "https://signed-recording-url",
"transcript": null,
"error": "Client error '401 Unauthorized' for url 'https://voice.induslabs.io/v1/audio/transcribe/diarize'"
}
}

Recommended Usage Flow

  1. Login to get access_token, and refresh it with POST /api/refresh_token rather than logging in again on every run.
  2. Call GET /api/mobile-numbers once to discover the C2C DIDs assigned to you and their channel ceilings.
  3. Set your concurrency plan with PUT /api/mobile-numbers/channel-allocation, then retune individual DIDs later with PATCH.
  4. 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.
  5. Store call_id from the create response, and check data.warning for capacity pressure.
  6. Use callbacks as the primary async notification path when callback_url is configured.
  7. Use GET /api/calls/recent to list and filter many calls efficiently.
  8. 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.
  9. For inbound traffic, register a webhook per DID with POST /api/calls/inbound-callback-url.
  10. 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.