HTTP API
Push students into master. The engine does the rest.
CRM and other services use this API. It does not originate calls. Push upserts by unique student_id into fic_dialer_master_data. Progressive or Predictive then claims matching rows from the same table.
Auth
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"}. Write endpoints use this key: POST /api/leads, POST /api/callbacks, and POST /api/dispositions. The agent panel also uses it for GET /api/answered-calls and GET /api/priorities. /healthz is 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 student_id |
| POST | /api/callbacks | Yes | Upsert callback-only leads (always priority_id 1) |
| POST | /api/dispositions | Yes | CRM wrap-up: dispose (NI/Invalid/DND/Language Barrier) or next retry (Call Not Connected); writes fic_dialer_disposition_history |
| GET | /api/answered-calls | Yes | This agent’s answered Received attempts for the agent panel redial list |
| 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. There is no /api/master, escalated-ticket, or send-callback-request route. The dialer claims and settles live rows itself. GET /api/answered-calls reads fic_dialer_call_attempts for the agent panel.
Answered calls
The agent page lists this desk’s connected calls and a Redial button. Data is GET /api/answered-calls?agent_id=&extension= (either query is enough). It returns the newest Received attempts for that agent or SIP extension, max 100. Socket redial uses student_id from each row; if the agent is paused the client sends ondemand: true.
Outbound talk already writes Received on settle. Answered inbound hangups now write the same attempt row (last_dial_result = INBOUND) so they show up in the list. The payload has student_name, course_code, tried_at, duration_seconds, inbound, and phone_tail (last 4). Full mobile is not returned.
curl -sS -H "Authorization: Bearer YOUR_WS_SECRET" \ "https://YOUR_HOST/api/answered-calls?agent_id=42&extension=1059"
Push leads
Body is JSON. One object upserts one row keyed by student_id (unique). {"leads":[...]} upserts 1–500 rows in one request. Extra fields are rejected.
New student_id inserts with dial-state defaults. An existing student_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 student_id, status (inserted, updated, or skipped_in_flight), and the raw callback_scheduled_at that was sent (null if omitted).
Single Pending lead
curl -sS -X POST https://YOUR_HOST/api/leads \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"student_id": "STU-1001",
"phone": "9876543210",
"student_name": "Ada Lovelace",
"course_code": "GST",
"priority_id": 2,
"status": 1,
"reg_date": "2026-09-20 10:30:00"
}'
201 response
{
"ok": true,
"leads": [
{ "student_id": "STU-1001", "status": "inserted", "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": [
{
"student_id": "STU-1001",
"phone": "9876543210",
"student_name": "Ada Lovelace",
"course_code": "GST",
"priority_id": 2
},
{
"student_id": "STU-1002",
"phone": "9876543211",
"student_name": "Ada Lovelace",
"course_code": "GST",
"priority_id": 1,
"callback_scheduled_at": "2026-08-20 15:30:00",
"agent_id": 42
}
]
}'
Callback time on this route is only allowed when priority_id is a callback priority (seeded as id 1) and requires agent_id. Stored dial_status is Callback in that case; otherwise Pending.
Push callbacks
Same WS_SECRET. Body is callback-only student data. Do not send priority_id — the API always stores priority_id = 1 (Callback Request) and dial_status = Callback. Extra fields are rejected.
One object upserts one row by student_id. {"callbacks":[...]} upserts 1–500 rows. Same insert / update / skipped_in_flight rules as push leads, including the dial-state reset. HTTP status is 201. Rate limit: 120 requests per IP per 60 seconds.
Single callback
curl -sS -X POST https://YOUR_HOST/api/callbacks \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"student_id": "STU-2001",
"phone": "9876543210",
"student_name": "Ada Lovelace",
"course_code": "GST",
"callback_scheduled_at": "2026-08-20 15:30:00",
"agent_id": 42
}'
201 response
{
"ok": true,
"leads": [
{ "student_id": "STU-2001", "status": "inserted", "callback_scheduled_at": "2026-08-20 15:30:00" }
]
}
Batch
curl -sS -X POST https://YOUR_HOST/api/callbacks \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"callbacks": [
{
"student_id": "STU-2001",
"phone": "9876543210",
"student_name": "Ada Lovelace",
"course_code": "GST",
"callback_scheduled_at": "2026-08-20 15:30:00",
"agent_id": 42
},
{
"student_id": "STU-2002",
"phone": "9876543211",
"student_name": "Grace Hopper",
"course_code": "GST",
"callback_scheduled_at": "2026-08-21 16:00:00",
"agent_id": 7
}
]
}'
Accepted times: 2026-08-20 15:30:00 or ISO-8601, including past datetimes (due immediately). Rejected: date-only. The row is pinned to agent_id and is not claimed until callback_scheduled_at is due. next_retry_at is stored as the same datetime.
Agent dispositions
Full product guide: Disposition docs. CRM Auto Dialer calls this after ChangeWorkStatus. Always inserts fic_dialer_disposition_history. If the lead exists in master:
crm_lead_status4/7/8/9→dial_status=Disposed, Final Disposal priority, clear retry/callbackcrm_lead_status6(Call Not Connected) →dial_status=Not Connected+next_retry_atfrom the same retry policy as AMI NC (or dispose at max tries)
curl -sS -X POST https://YOUR_HOST/api/dispositions \
-H "Authorization: Bearer YOUR_WS_SECRET" \
-H "Content-Type: application/json" \
-d '{
"student_id": "FIC123",
"crm_std_id": 456,
"crm_lead_status": 4,
"disposition_status": 12,
"remark": "Not interested in course",
"tag_from": "fresh_tag",
"agent_id": 42,
"source": "crm"
}'
Response includes action, master_updated, lead_found, history_id, next_retry_at. Unsupported statuses return 400.
Dial-state reset
Every successful insert or update writes these master columns back to a fresh cycle. A live Calling + reserved row is never touched.
| Column | POST /api/leads | POST /api/callbacks |
|---|---|---|
dial_status | Pending, or Callback when a callback time is sent on a callback priority | Callback |
priority_id | From the body | Always 1 |
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 | Next eligible originate time, or the callback datetime when a callback time is sent | Same as callback_scheduled_at |
callback_scheduled_at | New time, or NULL if omitted | Required from the body |
call_count | 0 | 0 |
isReserved | 0 | 0 |
Both routes also refresh phone, student_name, course_code, agent_id, push_date, and status (defaults to 1 if omitted). POST /api/leads also writes reg_date when that field is present (including null). Omitting it leaves the stored registration date unchanged. POST /api/callbacks does not change reg_date.
Fields
POST /api/leads
| Field | Required | Rules |
|---|---|---|
student_id | Yes | String, 1–100 chars. Unique. Same id upserts. |
phone | Yes | 10–15 digits after stripping non-digits. A leading 0 is kept; a 10-digit number gets a leading 0. |
student_name | Yes | String, 1–150 chars |
course_code | Yes | String, 1–100 chars. Used as skill match when the lead is unassigned. |
priority_id | Yes | Active row in fic_dialer_priority.id. Final Disposal is rejected. |
status | No | 0 or 1. Defaults to 1. |
callback_scheduled_at | No | Only on callback priorities (fic_dialer_priority.is_callback = 1, seeded as id 1). Datetime with time; past values are allowed. Requires agent_id. |
agent_id | No | fic_dialer_employee.id (not the SIP extension). If set, only that agent is called. Required when callback_scheduled_at is sent. |
reg_date | No | CRM registration datetime. 2026-09-20 10:30:00 or ISO-8601. Date-only is rejected. null or "" stores NULL. Claim order is newest reg_date first; null is dialed only after every dated lead. Values before CLAIM_MIN_PUSH_DATE are not claimed. |
POST /api/callbacks
| Field | Required | Rules |
|---|---|---|
student_id | Yes | Same as leads |
phone | Yes | Same as leads |
student_name | Yes | Same as leads |
course_code | Yes | Same as leads |
callback_scheduled_at | Yes | Datetime with time; past values are allowed (due immediately). Date-only is rejected. |
agent_id | Yes | fic_dialer_employee.id. The lead is pinned to this agent. |
status | No | 0 or 1. Defaults to 1. |
priority_id | — | Not accepted. Always stored as 1. |
Priorities
priority_id must exist and be active in fic_dialer_priority or the API returns 400 unknown priority_id. Seeded values:
| id | item | is_callback | Dialable via push |
|---|---|---|---|
1 | Callback Request | Yes | Yes. Required for a callback time on /api/leads. Forced by /api/callbacks. |
2 | Fresh Assign | No | Yes |
3 | Churn | No | Yes |
4 | Final Disposal | No | No — 400 priority_id is not dialable |
Errors
| Status | When |
|---|---|
400 | Invalid JSON, failed field validation, unknown / non-dialable priority_id, or callback time on a non-callback priority |
401 | Missing or wrong key |
404 | Unknown /api/... path |
413 | Body larger than 1 MB |
429 | More than 120 /api/callbacks writes from this IP in 60 seconds |
500 | Unexpected server error |
Validation shape:
{
"ok": false,
"error": "validation_failed",
"details": [
{ "field": "phone", "message": "phone must be 10-15 digits (leading 0 is kept)" }
]
}
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