Avyukta Intellicall

Version 1.0

API Reference

Click-to-call telephony for your CRM. There are two integrations — pick the one that matches how your agents talk to customers. Base URL:

https://api.clouddialer.in

New here? Read the Integration Guide for the architecture and call flow first.

Choose your integration

The only question that matters: where does the agent talk?

Browser

Agent talks in the browser

A softphone runs in the agent's browser tab.
  • No phone, no software to install
  • Your CRM just opens a URL — no backend work at all
  • Conferences are a URL too — no call_id to track
  • Uses your Launch Key
Server

Agent talks on a real phone

We ring the agent's phone, then the customer.
  • Nothing runs in the browser
  • Your backend calls our REST API
  • Conferences by agent — still no call_id needed
  • Uses your API Key (different key!)
 BrowserServer
Agent deviceBrowser tab (softphone)Any real phone
Your CRM doesOpens a URLCalls the REST API from its backend
CredentialLaunch Key (in the URL)API Key (Authorization header)
Backend neededNoYes
Ring orderBoth dialed together (softphone auto-answers)Agent's phone first — customer is only dialed once the agent answers
Result webhookSame for both

You can run both — e.g. office agents in the browser, field agents on their mobiles.

Authentication — two keys

We issue you two different keys. They are not interchangeable, and each works with exactly one integration.

KeyUsed bySent asSafe in a browser?
Launch Key Browser integration api_key=… in the URL Yes — it is designed for this
API Key Server integration Authorization: Bearer … No — backend only, never in browser code
⚠ Most common integration error. Sending the Launch Key to a server endpoint (or vice-versa) returns 401 {"error_code":"UNAUTHORIZED","message":"Invalid or missing API key"}. If you get that, check you're using the key that matches the integration — not that the key is wrong.

Server integration header

Authorization: Bearer <your-api-key>
Browser integration

Browser integration

Your CRM never calls our API. It only opens URLs. Everything else — registering the softphone, placing the call, live status, hanging up — is handled by the page we serve.

Start a call

OPEN URL/CRM_API/webphone/launch

Wire this to your Dial button. Open it in a new tab/window.

https://api.clouddialer.in/CRM_API/webphone/launch
  ?crm_username=<your agent id>
  &api_key=<your launch key>
  &to_number=<customer number>
  &from_number=<your caller-ID>
ParameterDescription
crm_username requiredYour own agent identifier. Stable and unique per agent.
api_key requiredYour Launch Key.
to_number requiredCustomer's number (10-digit or +91).
from_number requiredCaller-ID to present, from the numbers assigned to your account.
uuid optionalYour own call id, echoed back in the result push. Must be unique per call.
💡  The agent must allow microphone access the first time. A brand-new agent takes a few extra seconds on their very first call while their softphone is provisioned.

Conference / add a participant

OPEN URL/CRM_API/webphone/launch?action=conference

Wire this to your Conference button. It adds the third party into the agent's existing call — everyone stays on together. It does not start a new call, and it does not open a second call window.

https://api.clouddialer.in/CRM_API/webphone/launch
  ?action=conference
  &to_number=<number to add>

No call_id, no API key, no backend call. The agent's open call tab picks it up and merges the party in.

Server integration

Server integration

Your backend calls these. All requests and responses are JSON. Every request needs Authorization: Bearer <your-api-key>.

Start a call

POST/calls

We ring the agent's phone first. Only once the agent answers do we dial the customer, and bridge them together. If the agent never picks up, the customer is never called.

Body

FieldTypeDescription
from_number requiredstringCaller-ID to present, from the numbers assigned to your account.
to_number requiredstringCustomer's number (10-digit or +91).
agent_number requiredstringThe agent's phone number — this is what rings first.
uuid requiredstringYour own call id, echoed back in the result push. Must be unique per call.
crm_usernamestringStrongly recommended. Your agent id. Does not affect routing — but it appears in the result push and lets you conference without tracking a call_id.
recording optionalbooleanDefault true.
max_ring_time optionalintegerSeconds to ring. Default 30.
💡  Sending crm_username instead of agent_number routes the call to that agent's browser softphone — that is the Browser integration, and requires their call window to be open.

Example

POST /calls
Authorization: Bearer <your-api-key>
Content-Type: application/json

{
  "from_number":   "<your caller-ID>",
  "to_number":     "9876543210",
  "agent_number":  "9812345678",
  "crm_username":  "agent.017",
  "uuid":          "550e8400-e29b-41d4-a716-446655440000",
  "recording":     true
}
200 OK
{ "status": "success", "call_id": "5deb67aa-…", "message": "Call initiated" }
StatusMeaning
200Call accepted and queued
400Validation error, unrecognised caller-ID, or unknown agent
400INVALID_JSON — malformed body (check for smart/curly quotes)
401Wrong or missing key — see Authentication
409DUPLICATE_UUID — that uuid was already used
502Telephony platform could not queue the call

Conference / add a participant

POST/calls/conference

Merge a third party into the agent's current call. Identify the agent exactly as you did when dialing — we resolve their live call ourselves, so you never need to store our call_id.

FieldTypeDescription
crm_username or agent_number requiredstringWhich agent's call to add the participant to.
number requiredstringThe third party to add (10-digit or +91).
max_ring_time optionalintegerDefault 30.
POST /calls/conference
Authorization: Bearer <your-api-key>

{ "crm_username": "agent.017", "number": "9811111111" }

200 OK
{ "status": "success", "message": "Conference leg queued",
  "call_id": "5deb67aa-…", "leg_id": 3 }
StatusMeaning
200Third party is being dialed; joins when they answer
404AGENT_NOT_ON_CALL — that agent has no call in progress
400INVALID_NUMBER / MISSING_FIELD

If the added party doesn't answer, the existing call is unaffected.

Live agents

GET/calls/live

Every call in progress for your account — who is on a call, with whom, and each party's state. Useful for a real-time supervisor view.

200 OK
{
  "status": "success",
  "count": 1,
  "calls": [{
    "call_id": "5deb67aa-…",
    "uuid": "your-own-call-id",
    "crm_username": "agent.017",
    "agent_number": "9812345678",
    "customer_number": "9876543210",
    "call_status": "ANSWER",
    "call_type": "server",
    "started_at": "2026-07-03T10:00:00Z",
    "legs": [
      { "role": "agent",       "number": "9812345678", "status": "JOINED" },
      { "role": "destination", "number": "9876543210", "status": "JOINED" }
    ]
  }]
}

Call status

GET/calls/{call_id}

Poll one call's status, duration, recording URL and per-party detail. Optional — the result push covers the normal flow.

call_status is one of INITIATED, ANSWER, NO_ANSWER, BUSY, FAILED, CANCELED. Each leg is DIALING, JOINED, LEFT, or FAILED.

Add a participant by call_id

POST/calls/{call_id}/add-leg

Same as /calls/conference, but identified by our call_id instead of the agent. Use this only if you already store the call_id; otherwise prefer /calls/conference.

{ "number": "9811111111" }

Both integrations

These behave identically whichever integration you use.

Call result push

When a call ends we POST the full result to the callback URL you give us during onboarding. You don't poll. Up to 3 retries; de-duplicate on uuid. Full field list in the Integration Guide.

POST <your callback URL>
Content-Type: application/json

{
  "call_id": "5deb67aa-…", "uuid": "your-own-call-id",
  "status": "ANSWER", "duration": 150,
  "customer_number": "9876543210", "crm_username": "agent.017",
  "call_type": "server", "disposition": "CRM_CALL",
  "recording_url": "https://api.clouddialer.in/recordings/crm_….wav",
  "start_time": "2026-07-03T10:00:00Z", "end_time": "2026-07-03T10:02:30Z"
}

call_type tells you which integration produced the call: browser or server.

This event waits until the recording has been verified on disk, so it lands roughly ten seconds after the call ends. recording_url is optional and absent when there is no verified recording — that means "no recording exists", not "retry later". For something that fires the instant the call ends, see below.

Instant hangup event

Sent the moment either side hangs up — before any recording work, so nothing delays it. This is what a post-call form should open on.

POST <your callback URL>
Content-Type: application/json

{
  "event": "call_hangup",
  "call_id": "5deb67aa-…", "uuid": "your-own-call-id",
  "status": "ANSWER", "duration": 150,
  "customer_number": "9876543210", "agent_number": "9876543211",
  "from_number": "8047587340", "crm_username": "agent.017",
  "direction": "outbound",
  "start_time": "2026-07-03T10:00:00Z", "end_time": "2026-07-03T10:02:30Z"
}

It carries no recording_url — that is exactly why it is immediate. The verified recording still arrives in the call result push above, so a call you have both enabled for produces two events. Ordering between them is not guaranteed; de-duplicate on uuid and handle whichever lands first.

failure_reason is added when a call failed for a known reason — currently agent_unreachable, meaning the agent never answered and the customer was never dialled.

Live call events (SSE)

Your page holds one connection open and we push every state change to it as it happens — ringing, answered, the running call timer, and the hangup. There is no endpoint for you to host and nothing to poll.

Which stream to open

EndpointKeyed onUse when
/crm-agent-streamThe logged-in agentStart here. Needs nothing but the agent’s username, which your page already knows. No change to how you build call links.
/crm-uuid-streamYour own call idYou want to follow one specific call, or an agent can have two calls in flight at once.
/calls/{call_id}/streamOur call idServer integrations that already hold a call_id from the dial response.
💡  A browser page will not have a call_id to hand. Use the agent stream, or supply your own uuid when you start the call.

Agent stream

GET/crm-agent-stream?key={launch_key}&crm_username={agent}

Reports whatever call that agent is on. Every state change arrives as one event name, call_update; read live and answered to tell which transition it is.

const es = new EventSource(
  `https://api.clouddialer.in/crm-agent-stream?key=${LAUNCH_KEY}&crm_username=${AGENT}`
);

es.addEventListener('call_update', e => {
  const call = JSON.parse(e.data);
  if (!call.live)      showEnded(call.status, call.duration, call.recording_url);
  else if (call.answered) showConnected(call.customer_number);
  else                 showRinging(call.customer_number);
});

// live timer — once a second while the call is up
es.addEventListener('call_tick', e => showDuration(JSON.parse(e.data).elapsed));

Calls that started before you connected are ignored, so open the stream when the page loads rather than on click.

Call-id stream

GET/crm-uuid-stream?key={launch_key}&uuid={your_call_id}

You choose the uuid and pass it when you start the call, so you can subscribe before the call exists and miss nothing. Each transition gets its own event name.

const es = new EventSource(
  `https://api.clouddialer.in/crm-uuid-stream?key=${LAUNCH_KEY}&uuid=${myCallId}`
);

es.addEventListener('call_ringing',  e => showRinging(JSON.parse(e.data)));
es.addEventListener('call_answered', e => showConnected(JSON.parse(e.data)));
es.addEventListener('call_hangup',   e => showEnded(JSON.parse(e.data)));
es.addEventListener('call_tick',     e => showDuration(JSON.parse(e.data).elapsed));

Reconnecting is safe. A page refresh, a dropped connection or a browser tab restored later can simply reopen the stream with the same uuid, and we replay where the call currently stands:

State when you reconnectWhat you receive
Still ringingcall_ringing with "resumed": true, then the call continues normally.
On the callcall_answered with "resumed": true, then a call_tick within a second carrying the correct elapsed — the timer picks up where it should be.
Already endedcall_hangup with "replayed": true, then the stream closes.
💡  This only works if you can reconnect with the same uuid, so store it somewhere that survives a reload rather than in a page variable alone.
⚠ A uuid may be used for exactly one call. Reusing one is rejected with 409 {"error_code":"DUPLICATE_UUID"}, so a bare record id fails the second time that record is dialed. Append something per-click — a timestamp works — and keep it under 64 characters.

Per-call stream

GET/calls/{call_id}/stream?key={launch_key}

Emits call_state once on connect, then call_hangup, after which the server closes the stream. Useful only where you already hold the call_id.

Events

EventSent byMeaning
call_updateAgent streamAny state change. Distinguish with live and answered.
call_ringinguuid streamThe customer’s phone is ringing. answered false, start_time null.
call_answereduuid streamThe customer picked up. start_time is set.
call_hangupuuid & per-callThe call ended. live false and duration final.
call_tickAgent & uuidOnce per second while the call is up. Carries elapsed seconds.

Event fields

FieldMeaning
liveThe one to branch on. True while the call is up, false on the final event.
answeredTrue once the customer picks up. Stays true after hangup.
start_timeThe answer instant, ISO 8601. Null while ringing.
end_timeWhen the call ended. Null while live.
durationTalk seconds. Zero on live events — final only when live is false.
elapsedcall_tick only. Whole seconds since answer — show this as the live timer.
statusINITIATED, ANSWER, NO_ANSWER, BUSY or FAILED.
call_id / uuidOur id and yours. Use either to de-duplicate.
customer_number / agent_numberThe number dialed and the agent’s extension.
failure_reasonSet when the call could not be placed.
recording_urlAppears once the audio is ready. Null means “not yet”, not “none”.
⚠ Never count the call timer with setInterval. Opening the softphone gives focus to a new tab, so your CRM page runs in the background for the whole call, and browsers throttle timers in background tabs — a one-second interval can fire far less often than that. Use call_tick instead: it arrives over the network, which is not throttled. Display elapsed, do not count locally.
⚠ Do not read duration during the call. It is 0 on every live event because talk time is not known until hangup. Use call_tick.elapsed while the call is up, and duration only on the event where live is false.
💡  Make your handler idempotent and key it on call_id. An event may arrive more than once; when it does, take the latest values rather than discarding the repeat.

No delivery guarantee. A closed tab, a sleeping laptop or a dropped connection means the event is missed, and unlike the callback there is nowhere to retry to. Anything that must not be lost belongs on the callback.

Authentication is by query parameter because the browser EventSource API cannot set an Authorization header. Pass your launch key — never the vendor API key, which is a backend credential and would end up in browser history and access logs. A keepalive comment is sent periodically so proxies do not close an idle connection; EventSource never surfaces those. If streaming is not yet active on your account the endpoint returns 403 SSE_DISABLED — contact us and we will switch it on.

Download a recording

GET/recordings/{filename}

Direct download using the exact URL from the result push. The unguessable per-call filename is the access control — treat recording URLs as private. Returns the audio file, or 404.

Health check

GET/health

Unauthenticated liveness check for your monitoring.

200 OK
{ "status": "success" }