Reporting API
The reporting API lets your own systems read the same numbers as the reports, and lets Studio flows and bots add milestones to IVR journeys:
| Endpoint | Purpose | Scope |
|---|---|---|
GET /analytics/catalog | Metrics, dimensions, range presets and time buckets | reports:read |
GET /analytics/dimension-values | The values a dimension takes, for filters | reports:read |
POST /analytics/query | Run a query and get the result as JSON | reports:read |
POST /analytics/export | The same query, or a saved report, as a CSV or Excel file | reports:read |
GET /analytics/reports | Saved reports you can see, to export by ID | reports:read |
GET /analytics/exports/:day | The nightly open data files for a day, with download links | reports:read |
POST /analytics/journey-events/:token | Record an IVR milestone from a Studio flow | token in the URL |
POST /analytics/journey-events | Record a milestone from a bot or service | journeys:write |
POST /analytics/mcp | The MCP server for AI assistants, see Ask AI and MCP | mcp |
Everything else (report settings, saved report editing, email schedules, the backfill) is done in the Reports view.
Basics
- Base URL:
https://<your-floor-walker-host> - Authentication: an API key in the
X-Api-Keyheader, with the scope for what it does:reports:readfor the catalog, dimension values, queries, exports, saved reports and the open data files;journeys:writeforPOST /analytics/journey-events;mcpfor the MCP server, pluscontacts:readfor its contact-level tools. Ask us for one key per integration, with only the scopes it needs. API keys are issued by us and see every row; keep them on the server side. A signed-in Flex user's token (Authorization: Bearer <Flex token>) works too, with that user's permissions: agents get their own rows only, and the open data files need a supervisor. - Format: JSON. Times are ISO 8601 with an offset. Durations are in seconds, percentages 0–100.
- Errors return an HTTP status and
{ "error": "<code>", "details": … }, for example400 validation_error(with the problems indetails),400 unknown_metric,400 unknown_dimension,400 dimension_not_supported(a dimension that does not apply to a metric's source),400 invalid_range,400 too_many_buckets,401 unauthorized,403 forbidden(the key's scopes do not cover this endpoint;details.requiredScopeslists the scopes that would) and404 export_not_found.
Catalog
GET /analytics/catalog lists everything a query can use:
metrics:id,source, English and Hebrewlabel,description,formatandhigherIsBetter. The metrics are described in the Metrics catalog.dimensions:id,labeland thesourcesthey apply to, including your custom report fields ascustom.<key>.sources: the data sets metrics come from (for examplesegmentsfor queue and agent legs,contacts,queue_load,agent_time,journeys).rangePresets,bucketsand the defaulttimezone.
GET /analytics/dimension-values?dimension=queue&source=segments&from=<ISO>&to=<ISO> returns { "values": [...] }: the values a dimension takes in that period, for building filters.
Query
POST /analytics/query
{
"metrics": ["offered", "service_level", "asa"],
"dimensions": ["queue"],
"preset": "last_week",
"timezone": "Europe/London"
}
| Field | Meaning |
|---|---|
metrics | 1 to 20 metric IDs. Metrics from different sources may be combined; each is computed on its own data set and joined on the dimensions |
dimensions | Up to 4 dimension IDs to group by |
filters | Dimension ID to a list of allowed values, for example { "queue": ["WQ…"], "channel": ["voice"] } |
preset, or from + to | A relative period (today, yesterday, last_7_days, last_30_days, last_90_days, this_week, last_week, this_month, last_month) or ISO times |
bucket | Time breakdown: none (default), 15m, 30m, 1h, 1d, 1w, 1mo |
timezone | IANA time zone for days and buckets; defaults to your environment's |
sort | { "by": "<column id>", "dir": "asc" | "desc" } |
limit | Rows, up to 10,000 (default 1,000) |
totals | Totals over the whole period (default true) |
Response:
{
"columns": [
{ "id": "queue", "kind": "dimension", "label": { "en": "Queue", "he": "תור" } },
{ "id": "offered", "kind": "metric", "label": { "en": "Offered", "he": "…" }, "format": "count" },
{ "id": "service_level", "kind": "metric", "label": { "en": "Service level", "he": "…" }, "format": "percent", "higherIsBetter": true }
],
"rows": [{ "queue": "WQ0123…", "offered": 1840, "service_level": 82.4, "asa": 14.2 }],
"labels": { "queue": { "WQ0123…": "Billing" } },
"totals": { "offered": 5210, "service_level": 79.8, "asa": 17.9 },
"meta": { "from": "…", "to": "…", "bucket": "none", "timezone": "Europe/London", "truncated": false }
}
Queues and agents appear in rows by their Twilio SID; labels gives their names. With a bucket, each row also has a t column with the bucket's start time. meta.truncated is true when the limit cut the result.
Counting follows the reports exactly: contacts count in the period in which they started, in the query's time zone. See How contacts are counted.
Export
POST /analytics/export returns a file instead of JSON:
{ "query": { "metrics": ["handled", "aht"], "dimensions": ["agent"], "preset": "yesterday" }, "format": "xlsx", "language": "en", "name": "Agents yesterday" }
| Field | Meaning |
|---|---|
query | A query as above, or |
reportId | A saved report, optionally with from and to to run it over other dates |
format | csv (default) or xlsx |
language | en (default) or he, for column names and the sheet direction |
name | The file name |
The file is described in Exports. GET /analytics/reports returns { "reports": [...] } with the id, name and definition of every saved report you can see (your own and shared ones).
Open data files
GET /analytics/exports/2026-11-01 returns the manifest of that day's open data files and a download link for each file:
{
"manifest": { "day": "2026-11-01", "timezone": "Asia/Jerusalem", "tables": [{ "table": "…", "rows": 5210, "files": ["…/date=2026-11-01/….parquet", "…/date=2026-11-01/….csv.gz"], "columns": [...] }] },
"links": { "…/date=2026-11-01/….parquet": "https://…" }
}
Links are valid for one hour. A day that has not been written returns 404 export_not_found.
IVR milestones
Milestones attach business events to a contact's IVR or bot journey. See Milestones for what they are used for.
From a Studio flow
POST /analytics/journey-events/<token>, form-encoded or JSON, with no other authentication. The token is a secret we give you for your Studio flows: either the installation's journey token or an API key with only the journeys:write scope (one per flow if you like, so it can be revoked on its own). A wrong or revoked token returns 404.
POST https://<your-floor-walker-host>/analytics/journey-events/<token>
Content-Type: application/x-www-form-urlencoded
event=self_service_resolved&CallSid={{trigger.call.CallSid}}&topic=balance
From a bot or service
POST /analytics/journey-events with an API key that has the journeys:write scope:
curl -X POST https://<your-floor-walker-host>/analytics/journey-events \
-H "X-Api-Key: <your-api-key>" -H "Content-Type: application/json" \
-d '{"event":"intent_detected","conversation_sid":"CHxxxxxxxx","attributes":{"intent":"billing"}}'
| Field | Meaning |
|---|---|
event | 1 to 64 letters, digits, _, . or -. self_service_resolved marks the journey as contained |
CallSid / call_sid, ConversationSid / conversation_sid, or ExecutionSid / execution_sid | The contact the milestone belongs to; one is required |
at | Optional time of the milestone |
attributes, or any other field | Stored with the milestone, up to 8,000 characters as JSON |
Both return 201 with { "ok": true, "id": "…" } and the journey or contact the milestone was attached to. Errors: 400 invalid_event, 400 missing_contact, 413 attributes_too_large.