Dialer docsHTTP API
Home Progressive Predictive Callback Disposition API Socket Dashboard

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.

Bearer / X-API-Key JSON, 1 MB max POST leads · callbacks · dispositions

Auth

Write endpoints require WS_SECRET. Send it as any one of:

HeaderValue
AuthorizationBearer YOUR_WS_SECRET
X-API-KeyYOUR_WS_SECRET
X-Access-KeyYOUR_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

MethodPathAuthWhat it does
POST/api/leadsYesUpsert one lead or a batch by student_id
POST/api/callbacksYesUpsert callback-only leads (always priority_id 1)
POST/api/dispositionsYesCRM wrap-up: dispose (NI/Invalid/DND/Language Barrier) or next retry (Call Not Connected); writes fic_dialer_disposition_history
GET/api/answered-callsYesThis agent’s answered Received attempts for the agent panel redial list
GET/healthzNoAMI + DB liveness. No internals
GET/healthYesDetailed AMI / metrics health
GET/api/healthYesSame 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:

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.

ColumnPOST /api/leadsPOST /api/callbacks
dial_statusPending, or Callback when a callback time is sent on a callback priorityCallback
priority_idFrom the bodyAlways 1
last_dial_resultNULLNULL
hangup_byNULLNULL
hangup_causeNULLNULL
last_attempt_atNULLNULL
first_attempt_atNULLNULL
next_retry_atNext eligible originate time, or the callback datetime when a callback time is sentSame as callback_scheduled_at
callback_scheduled_atNew time, or NULL if omittedRequired from the body
call_count00
isReserved00

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

FieldRequiredRules
student_idYesString, 1–100 chars. Unique. Same id upserts.
phoneYes10–15 digits after stripping non-digits. A leading 0 is kept; a 10-digit number gets a leading 0.
student_nameYesString, 1–150 chars
course_codeYesString, 1–100 chars. Used as skill match when the lead is unassigned.
priority_idYesActive row in fic_dialer_priority.id. Final Disposal is rejected.
statusNo0 or 1. Defaults to 1.
callback_scheduled_atNoOnly on callback priorities (fic_dialer_priority.is_callback = 1, seeded as id 1). Datetime with time; past values are allowed. Requires agent_id.
agent_idNofic_dialer_employee.id (not the SIP extension). If set, only that agent is called. Required when callback_scheduled_at is sent.
reg_dateNoCRM 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

FieldRequiredRules
student_idYesSame as leads
phoneYesSame as leads
student_nameYesSame as leads
course_codeYesSame as leads
callback_scheduled_atYesDatetime with time; past values are allowed (due immediately). Date-only is rejected.
agent_idYesfic_dialer_employee.id. The lead is pinned to this agent.
statusNo0 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:

iditemis_callbackDialable via push
1Callback RequestYesYes. Required for a callback time on /api/leads. Forced by /api/callbacks.
2Fresh AssignNoYes
3ChurnNoYes
4Final DisposalNoNo — 400 priority_id is not dialable

Errors

StatusWhen
400Invalid JSON, failed field validation, unknown / non-dialable priority_id, or callback time on a non-callback priority
401Missing or wrong key
404Unknown /api/... path
413Body larger than 1 MB
429More than 120 /api/callbacks writes from this IP in 60 seconds
500Unexpected 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