Skip to content

Call Logs API

Enterprise call history API that reads directly from Asterisk's CDR table in MariaDB.

Endpoint

GET /api/v1/calls?limit=50&offset=0
Authorization: Bearer <JWT>

Query parameters

Param Type Description
limit int (1-200) Records per page (default 50)
offset int Pagination offset
direction string inbound / outbound / internal / all
disposition string ANSWERED / NO ANSWER / BUSY / FAILED
from string Filter by caller number (partial match)
to string Filter by destination (partial match)
date_from date YYYY-MM-DD start
date_to date YYYY-MM-DD end
search string Searches src, dst, caller ID

Response

{
  "data": [{
    "id": 10,
    "calldate": "2026-04-12T12:07:13Z",
    "src": "919944421125",
    "dst": "918065978001",
    "disposition": "NO ANSWER",
    "duration": 10,
    "billsec": 0,
    "direction": "inbound",
    "rang_extension": "1001",
    "answered_by": null,
    "answered_type": null,
    "wait_time": 10,
    "disconnected_by": "caller",
    "hangup_cause": "16",
    "hangup_reason": "Normal Clearing",
    "hangup_source": "PJSIP/tata_gateway-...",
    "queue_name": null,
    "queue_wait_time": null,
    "answered_agent": null,
    "recording_url": "/api/v1/calls/10/recording",
    "...": "all raw CDR fields also included"
  }],
  "pagination": { "total": 100, "limit": 50, "offset": 0, "has_more": true }
}

Enriched fields (derived from raw CDR)

Field Source Description
rang_extension Extracted from dstchannel Which extension the call rang (e.g., 1001)
answered_by Same, only when ANSWERED Who picked up
answered_type dstchannel presence check human / prompt / queue / null
wait_time duration - billsec Ring time in seconds
disconnected_by CHANNEL(hangupsource) via userfield caller / callee / timeout / busy / system
hangup_cause SIP cause code from userfield 16 = Normal, 17 = Busy, 18 = No Response
hangup_reason Mapped from cause code Human-readable string
queue_name From CDR queue_name column Queue the call entered
queue_wait_time From CDR column Seconds caller waited in queue
answered_agent From CDR column Agent endpoint that answered from queue

CDR pipeline

Call happens → Asterisk writes to asterisk_cdr (ODBC)
    → CDR poller (every 30s) picks up new rows
    → Classifies direction, finds org_id
    → POSTs to LogsUpdate /auto-ticket (for ticket creation)
    → GET /api/v1/calls reads directly from asterisk_cdr

accountcode

Every CDR row should have accountcode = org_id. Set by dialplan in:

  • Internal context (user extensions) ✓
  • Incoming DID subroutine ✓
  • Outbound context ✓
  • Queue context ✓

Without accountcode, the call history query falls back to channel-name heuristics which can miss some call types.

Recordings

GET /api/v1/calls/:id/recording?token=<JWT>

Looks for the recording file in order: 1. Local disk: /var/spool/asterisk/monitor/{filename} 2. Firebase Storage via rclone (hourly cron moves files there)

Returns audio/wav stream. Only available when billsec > 0 and recording file exists.

DELETE /api/v1/calls/:id/recording

Right-to-erasure: deletes from local disk + GCS, clears CDR reference, writes audit log.