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.inNew 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?
Agent talks in the browser
- No phone, no software to install
- Your CRM just opens a URL — no backend work at all
- Conferences are a URL too — no
call_idto track - Uses your Launch Key
Agent talks on a real phone
- Nothing runs in the browser
- Your backend calls our REST API
- Conferences by agent — still no
call_idneeded - Uses your API Key (different key!)
| Browser | Server | |
|---|---|---|
| Agent device | Browser tab (softphone) | Any real phone |
| Your CRM does | Opens a URL | Calls the REST API from its backend |
| Credential | Launch Key (in the URL) | API Key (Authorization header) |
| Backend needed | No | Yes |
| Ring order | Both dialed together (softphone auto-answers) | Agent's phone first — customer is only dialed once the agent answers |
| Result webhook | Same 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.
| Key | Used by | Sent as | Safe 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 |
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
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
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>| Parameter | Description |
|---|---|
crm_username required | Your own agent identifier. Stable and unique per agent. |
api_key required | Your Launch Key. |
to_number required | Customer's number (10-digit or +91). |
from_number required | Caller-ID to present, from the numbers assigned to your account. |
uuid optional | Your own call id, echoed back in the result push. Must be unique per call. |
Conference / add a participant
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
Your backend calls these. All requests and responses are JSON. Every request needs Authorization: Bearer <your-api-key>.
Start a call
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
| Field | Type | Description |
|---|---|---|
from_number required | string | Caller-ID to present, from the numbers assigned to your account. |
to_number required | string | Customer's number (10-digit or +91). |
agent_number required | string | The agent's phone number — this is what rings first. |
uuid required | string | Your own call id, echoed back in the result push. Must be unique per call. |
crm_username | string | Strongly 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 optional | boolean | Default true. |
max_ring_time optional | integer | Seconds to ring. Default 30. |
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" }| Status | Meaning |
|---|---|
| 200 | Call accepted and queued |
| 400 | Validation error, unrecognised caller-ID, or unknown agent |
| 400 | INVALID_JSON — malformed body (check for smart/curly quotes) |
| 401 | Wrong or missing key — see Authentication |
| 409 | DUPLICATE_UUID — that uuid was already used |
| 502 | Telephony platform could not queue the call |
Conference / add a participant
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.
| Field | Type | Description |
|---|---|---|
crm_username or agent_number required | string | Which agent's call to add the participant to. |
number required | string | The third party to add (10-digit or +91). |
max_ring_time optional | integer | Default 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 }| Status | Meaning |
|---|---|
| 200 | Third party is being dialed; joins when they answer |
| 404 | AGENT_NOT_ON_CALL — that agent has no call in progress |
| 400 | INVALID_NUMBER / MISSING_FIELD |
If the added party doesn't answer, the existing call is unaffected.
Live agents
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
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
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
Enabled per account. 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 stream (server-sent events)
Enabled per account. The alternative to a callback for browser-based CRMs: your page holds the connection open and we push the hangup straight to it, with no endpoint for you to host. The stream closes when the call ends.
const es = new EventSource(
`https://api.clouddialer.in/calls/${call_id}/stream?key=${LAUNCH_KEY}`
);
// once, on connect — current status and legs
es.addEventListener('call_state', e => console.log(JSON.parse(e.data)));
// same body as the hangup callback; server closes the stream after it
es.addEventListener('call_hangup', e => openPostCallForm(JSON.parse(e.data)));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.
If the call already ended before you connect, the call_hangup is replayed immediately with "replayed": true. A keepalive comment is sent every 20 seconds so proxies don't close an idle connection; EventSource never surfaces those. Returns 403 SSE_DISABLED if streaming is not enabled for your account.
Download a recording
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
Unauthenticated liveness check for your monitoring.
200 OK
{ "status": "success" }