Welcome to the API documentation for the FreePBX Middleware system. This API facilitates data ingestion from FreePBX and data consumption for PowerBI.
This API acts as a secure middleware and requires a Bearer Token for all requests. This ensures that only authorized services (FreePBX, PowerBI) can access the data.
You must include the Authorization header in every HTTP request with your secret token.
Authorization: Bearer YOUR_SECURE_TOKEN
Note: The token is generated securely by the system administrator and is not publicly available. If you lose your token, please contact the administrator to generate a new one from the server environment.
To ensure system stability, the API enforces the following limits:
429 Too Many Requests
response.limit together with cursor to fetch
data in chunks (e.g., 1000 records at a time) rather than requesting the entire database at once. Start without a
cursor, then pass the nextCursor value from each response into the next request until it is
null.offset values: offset is still supported, but the database
must walk and discard every skipped row, so each page gets progressively slower over a large range. Paging with
cursor costs the same for page 1 and page 500.created_after to fetch only new records since your last
refresh. This is significantly faster and reduces load.429 errors by waiting a few minutes
before retrying.GET /api/v1/data
Fetches flattened call data suitable for PowerBI ingestion.
limit: Number of records to return (default: 500)cursor: Opaque cursor marking where the next page resumes. Take it from
meta.pagination.nextCursor of the previous response. Recommended for paging through large
ranges. When supplied, offset is ignored.offset: Pagination offset (default: 0). Still supported for backwards compatibility, but slower
on deep pages — prefer cursor.created_after: Filter by event date (ISO 8601, e.g., 2026-01-27T00:00:00Z)created_before: Filter by event date (ISO 8601)cid_num: Filter by specific CID Num (exact match)eventtype: Filter by specific Event Type (exact match, e.g., "CHAN_START")Request the first page without a cursor, then follow nextCursor until it comes back
null. Records are ordered newest first (eventtime descending).
GET /api/v1/data?limit=1000&created_after=2026-07-19T22:00:00Z
-> meta.pagination.nextCursor = "eyJldmVudHRpbWUi..."
GET /api/v1/data?limit=1000&created_after=2026-07-19T22:00:00Z&cursor=eyJldmVudHRpbWUi...
-> meta.pagination.nextCursor = "eyJldmVudHRpbWUi..." (repeat)
...until nextCursor is null, which means the last page has been reached.
Important: keep limit and all filters identical across every request in the same
walk. The cursor encodes a position within one specific ordered result set, so changing a filter mid-walk
produces meaningless results. An unreadable cursor returns 400 Bad Request.
{
"data": [
{
"id": 1025,
"eventtype": "CHAN_START",
"eventtime": "2026-03-28T08:30:05.000Z",
"cid_name": "John Doe",
"cid_num": "101",
"cid_ani": "101",
"cid_rdnis": "",
"cid_dnid": "900",
"exten": "s",
"context": "from-internal",
"channame": "PJSIP/101-000000a1",
"appname": "",
"appdata": "",
"amaflags": 3,
"accountcode": "",
"uniqueid": "1711611005.161",
"linkedid": "1711611005.161",
"peer": "",
"userdeftype": "",
"extra": "{\"header\":\"value\"}"
}
],
"meta": {
"pagination": {
"total": 1,
"limit": 500,
"offset": 0,
"hasNextPage": false,
"hasPrevPage": false,
"nextOffset": null,
"prevOffset": null,
"nextCursor": null
}
}
}
total: Total number of records matching the filters (the whole result set, not just this
page).nextCursor: Pass as cursor to fetch the next page. null on the last
page.hasNextPage / hasPrevPage: Whether further pages exist in each direction.nextOffset / prevOffset: Offset-based equivalents. These are null when
paging with a cursor.