HTTP API
Push leads into master. The engine does the rest.
CRM and other services use this API. It does not originate calls. Push upserts by unique oli_id. Progressive or Predictive then claims matching rows from the same master table.
Auth
Lead write endpoints require WS_SECRET. Send it as any one of:
| Header | Value |
|---|---|
Authorization | Bearer YOUR_WS_SECRET |
X-API-Key | YOUR_WS_SECRET |
X-Access-Key | YOUR_WS_SECRET |
Wrong or missing key returns 401 with {"ok":false,"error":"unauthorized"}. All write endpoints use this key: POST /api/leads, POST /api/master, POST /api/send-callback-request, POST /api/pending-callbacks, and POST /api/pending-dispositions. GET /api/missed-activities uses it too. /healthz and GET /api/pause-reasons are public. Detailed /health uses the same key.
Endpoints
| Method | Path | Auth | What it does |
|---|---|---|---|
| POST | /api/leads | Yes | Upsert one lead or a batch by oli_id |
| POST | /api/master | Yes | Same handler as /api/leads |
| POST | /api/send-callback-request | Yes | Set callback time on an existing master oliId |
| POST | /api/pending-callbacks | Yes | CRM after-call callback. Held until the live call settles, then applied |
| POST | /api/pending-dispositions | Yes | CRM final disposition. Held until settle, then sets Disposed |
| GET | /api/missed-activities | Yes | One agent's open missed activities and outbound-blocked flag |
| GET | /api/pause-reasons | No | Agent pause-reason catalog for the picker and reports |
| GET | /healthz | No | AMI + DB liveness. No internals |
| GET | /health | Yes | Detailed AMI / metrics health |
| GET | /api/health | Yes | Same as /health |
There is no GET/PATCH/DELETE for leads. Push with POST /api/leads (or /api/master) using one of the three priorities: 1 Callback Schedule, 2 Fresh Assign (3 Final Disposal is set by the dialer and rejected on push). The dialer still claims and settles live rows itself. Floor-wide new calls stop when sales_dialer_config.isMaintainance = 1 (maintenance banner); live talks are not hung.
Push leads
Body is JSON. One object upserts one row keyed by oli_id (unique). {"leads":[...]} upserts 1–500 rows in one request. Extra fields are rejected.
New oli_id inserts with dial-state defaults. An existing oli_id updates contact fields and resets dial state unless the row is currently Calling and reserved. In that case the API returns skipped_in_flight and leaves the live call alone. HTTP status is always 201 for a valid body. Each row in the response is oli_id, status (inserted, updated, or skipped_in_flight), data_id, and the raw callback_scheduled_at that was sent (null if omitted). The dialer always mints data_id (UUID). Sending it on the body is a 400. Insert and update get a new id; skipped_in_flight returns the existing one.
Single Pending lead
curl -sS -X POST https://YOUR_HOST/api/leads \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"oli_id": "OLI123",
"phone_number": "9876543210",
"client_name": "Test Client",
"form_type": "GST",
"priority_id": 2
}'
201 response
{
"ok": true,
"leads": [
{ "oli_id": "OLI123", "status": "inserted", "data_id": "11111111-1111-4111-8111-111111111111", "callback_scheduled_at": null }
]
}
Batch
curl -sS -X POST https://YOUR_HOST/api/leads \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"leads": [
{
"oli_id": "OLI123",
"phone_number": "9876543210",
"client_name": "A",
"form_type": "GST",
"priority_id": 2
},
{
"oli_id": "OLI124",
"phone_number": "9876543211",
"client_name": "B",
"form_type": "GST",
"priority_id": 1,
"callback_scheduled_at": "2026-08-20 15:30:00"
}
]
}'
Dial-state reset
Every successful insert, update, or send-callback-request writes these master columns back to a fresh cycle. A live Calling + reserved row is never touched.
| Column | Push insert / update | Send callback request |
|---|---|---|
data_id | New UUID | New UUID |
dial_status | Pending, or Callback when a callback time is sent on Callback Schedule | Callback |
disposeRemark | NULL | NULL |
last_dial_result | NULL | NULL |
hangup_by | NULL | NULL |
hangup_cause | NULL | NULL |
last_attempt_at | NULL | NULL |
first_attempt_at | NULL | NULL |
next_retry_at | NULL inside working hours; otherwise next work_start. Callbacks stay NULL and wait on callback_scheduled_at | NULL |
callback_scheduled_at | New time, or NULL if omitted | From scheduledAt |
call_count | 0 | 0 |
isReserved | 0 | 0 |
Auto-dial max-tries settle (not this table) sets disposeRemark to Dialer system: max tries reached. Socket redial and inbound claim also set disposeRemark to NULL when they leave Disposed.
Fields
| Field | Required | Rules |
|---|---|---|
oli_id | Yes | String, 1–100 chars. Unique. Same id upserts. |
data_id | Generated | UUID minted by the dialer on insert/update. Not accepted on the body (extra field → 400). |
phone_number | Yes | 10–15 digits after stripping non-digits |
client_name | Yes | String, 1–150 chars |
form_type | Yes | Must match an agent skill to be dialed. Claim is skill-only. |
priority_id | Yes | 1 Callback Schedule or 2 Fresh Assign. 3 Final Disposal is rejected (the dialer sets it at max tries). |
callback_scheduled_at | No | Only on Callback Schedule (priority_id 1, the only sales_dialer_priority.is_callback = 1 row). Datetime with time; past values are allowed (due immediately). |
priority_id must exist and be active or the API returns 400 unknown priority_id. Stored dial_status is Callback when the priority is a callback priority and a callback time is sent; otherwise Pending. Connected agent is recorded on sales_dialer_call_attempts at settle, not on master.
Send callback request
Same WS_SECRET. Body is only oliId and scheduledAt (alias ScheduledAt). Extra keys are rejected. The oliId must already exist in sales_dialer_master_data or the API returns 404. Any priority_id is allowed. A live Calling + reserved row returns 409.
Sets dial_status = Callback and callback_scheduled_at, keeps priority_id, and applies the dial-state reset.
curl -sS -X POST https://YOUR_HOST/api/send-callback-request \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"oliId": "OLI123",
"scheduledAt": "2026-08-21 16:30:00"
}'
200 response
{ "ok": true, "lead": {
"oli_id": "OLI123",
"data_id": "11111111-1111-4111-8111-111111111111",
"dial_status": "Callback",
"callback_scheduled_at": "2026-08-21 16:30:00"
} }
Callback time
Accepted: 2026-08-20 15:30:00 or ISO-8601, including times already in the past (the lead is due immediately). Rejected: date-only (2026-08-20), empty invalid strings, and any time on a non-callback priority. Extra keys such as agent_id are rejected.
curl -sS -X POST https://YOUR_HOST/api/master \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"oli_id": "OLI123",
"phone_number": "9876543210",
"client_name": "Test Client",
"form_type": "GST",
"priority_id": 1,
"callback_scheduled_at": "2026-08-20 15:30:00"
}'
On POST /api/leads, the engine waits on this time for Callback Schedule (priority_id 1). A future callback_scheduled_at on any row (including one set by send-callback-request) is skipped until due. When due, any idle agent whose skills include form_type and whose priority_ids allow the lead’s priority_id (empty = all) may claim it. Full calling behaviour is in the Callback Request guide.
Pending callbacks
For callbacks scheduled from the CRM page during or right after a call. send-callback-request would return 409 while the lead is live, and the hangup settle would overwrite it anyway. This endpoint accepts at any time: it stores a pending row in sales_dialer_pending_callbacks (older pending rows for the same OLI become superseded, latest wins) and applies it as soon as the lead is not on a live call.
Body is camelCase. Extra keys are rejected. oliId must exist in master (404 unknown oliId). scheduledAt is a wall clock in DB_TIMEZONE (2026-10-01 15:00[:00]) or ISO-8601 with offset, and must not be more than 5 minutes in the past. Optional audit fields: agentId, extension, callbackType (today / future_day), workStatus, subDisposition, remarks (max 500).
curl -sS -X POST https://YOUR_HOST/api/pending-callbacks \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"oliId": "OLI124",
"scheduledAt": "2026-10-01T15:00:00+05:30",
"agentId": 42,
"extension": "1030",
"callbackType": "future_day"
}'
200 when applied now, 202 when held until the live call settles:
{ "ok": true, "pending": {
"id": 17,
"oli_id": "OLI124",
"scheduled_at": "2026-10-01 15:00:00",
"status": "applied"
} }
Applying sets dial_status = Callback, callback_scheduled_at, priority_id = 1 (Callback Schedule), clears next_retry_at, call_count, attempt timestamps, last_dial_result, hangup_by, hangup_cause, disposeRemark and the reserve, and mints a new data_id. Storing the row also clears any missed activity for that OLI.
Pending dispositions
Final disposition from the CRM page (Not Intrested, Invalid Lead, Language Barrier, DND). Same table (action = dispose) and the same latest-wins / apply-after-settle flow, so a callback followed by a disposition (or the reverse) ends with whichever was submitted last.
Body: oliId (required, existing), reason (required, 1–200 chars). Optional audit fields: agentId, extension, workStatus, subDisposition, remarks.
curl -sS -X POST https://YOUR_HOST/api/pending-dispositions \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{ "oliId": "OLI124", "reason": "Not Intrested - Price too high", "agentId": 42, "workStatus": 4 }'
{ "ok": true, "pending": {
"id": 18,
"oli_id": "OLI124",
"action": "dispose",
"reason": "Not Intrested - Price too high",
"status": "pending"
} }
Applying sets dial_status = Disposed, disposeRemark = CRM: <reason>, priority_id = disposed_priority_id (Final Disposal), clears callback_scheduled_at, next_retry_at and the reserve, and mints a new data_id. The engines stop claiming the lead. Storing the row also clears any missed activity for that OLI.
Missed activities
Every bridged outgoing or redial call opens a missed activity for that agent and OLI. If no pending callback or disposition is stored within MISSED_ACTIVITY_GRACE_MINUTES (default 10) after hangup, the agent's outbound is paused (inbound still rings). While paused, they can still redial their own missed OLIs. The CRM sales-dialer page uses this endpoint for its Missed activities panel.
Query one of agentId (sales_dialer_employee.id) or extension (3–16 digits). An extension not in the roster returns 404 unknown_extension. Items are every uncleared row (max 100, oldest first): overdue ones, ones still inside the grace window (overdue: false), and a live call (ended_at and due_at are null).
curl -sS -H "Authorization: Bearer YOUR_WS_SECRET" \ "https://YOUR_HOST/api/missed-activities?extension=1030"
{
"ok": true,
"agent_id": 42,
"blocked": true,
"grace_minutes": 10,
"items": [
{
"id": 5,
"oli_id": "OLI124",
"lead_id": 991,
"call_type": "outgoing",
"extension": "1030",
"connected_at": "2026-09-28T05:10:00.000Z",
"ended_at": "2026-09-28T05:14:00.000Z",
"due_at": "2026-09-28T05:24:00.000Z",
"overdue": true
}
]
}
Live changes come over the socket as dialer.outbound_blocked / dialer.missed_activities (see the Socket guide). Rules and UI are in Settle, retry & more.
Pause reasons
Public. No WS_SECRET. CRM and agent.html fetch this once on connect to populate the pause picker. Codes are stored in sales_dialer_agent_pauses.reason (VARCHAR(32)). Agent pause on the socket must use a picker code; system codes are never in this list. If the request fails, clients fall back to the same 15 codes. other requires a short note (max 12 words / 80 characters) stored in reason_note.
curl -sS https://YOUR_HOST/api/pause-reasons
{
"ok": true,
"note_max_words": 12,
"note_max_chars": 80,
"reasons": [
{ "code": "bio", "label": "Bio break" },
{ "code": "ondemand", "label": "On-demand call" },
{ "code": "other", "label": "Other", "note": true }
],
"system": [
{ "code": "left", "label": "Left / disconnect" },
{ "code": "toggle", "label": "Unspecified" },
{ "code": "day_reset", "label": "Day reset" }
]
}
Picker codes: bio, tea, lunch, meeting, training, backoffice, acw, escalation, process_query, system, logout, personal, poa, ondemand, other. Socket pause with a missing or system-only code emits server_error invalid_pause_reason. Wire details are in the Socket guide.
Errors
| Status | When |
|---|---|
400 | Invalid JSON, failed field validation, unknown extra keys (including agent_id), unknown priority_id, callback time on a non-callback priority, or priority_id 3 (Final Disposal) |
401 | Missing or wrong key |
404 | Unknown /api/... path, unknown oliId on send-callback-request / pending-callbacks / pending-dispositions, or unknown_extension on missed-activities |
409 | send-callback-request while the lead is Calling and reserved (pending-callbacks accepts it with 202 instead) |
413 | Body larger than 1 MB |
429 | rate_limited: more than 120 requests per IP in 60 seconds on send-callback-request or pending-callbacks / pending-dispositions (shared), or 240 on missed-activities. Lead push is not rate-limited |
500 | Unexpected server error |
Validation shape:
{
"ok": false,
"error": "validation_failed",
"details": [
{ "field": "phone_number", "message": "phone_number must be 10-15 digits" }
]
}
Health
/healthz is public and returns {"ok":true,"ami":true,"db":true} (booleans only). Detailed /health and /api/health require the same key as lead writes and include AMI status, logs, DB host, and metrics. 200 when healthy, otherwise 503.
curl -sS https://YOUR_HOST/healthz curl -sS -H "Authorization: Bearer YOUR_WS_SECRET" https://YOUR_HOST/api/health