Reports
Two flavours:
- Summary, fast synchronous read. KPI roll-up + daily series + in-range broadcast table. Use it for dashboards.
- Export, asynchronous. Queues a CSV or XLSX file that the export worker uploads to storage and emails you a signed download link. Files retain for 7 days.
Every endpoint is scoped by the caller's bots JWT claim. A viewer
or operator sees only their assigned bots. Passing bot_id="*" (or
omitting it) means "all bots the caller can act on".
GET /api/v1/rcs/reports/summary
POST /api/v1/rcs/reports/export
GET /api/v1/rcs/reports/exports
GET /api/v1/rcs/reports/exports/{export_id}GET /rcs/reports/summary
Auth: Authorization: Bearer <jwt> (any role).
Query params:
Maximum window: 90 days (400 invalid_date_range beyond).
Success:
{
"success": true,
"from": "2026-06-15",
"to": "2026-07-14",
"scope": ["*"],
"kpis": {
"total_broadcasts": 12,
"total_recipients": 184320,
"sent": 175430,
"delivered": 168112,
"read": 94207,
"clicked": 14208,
"failed": 8890,
"ctr": 8.45,
"delivery_rate": 95.83,
"cost": 1247.85
},
"series": [
{ "date": "2026-06-15", "sent": 0, "delivered": 0, "failed": 0 },
{ "date": "2026-06-16", "sent": 2105, "delivered": 2001, "failed": 12 },
{ "date": "2026-07-14", "sent": 18324, "delivered": 17501, "failed": 92 }
],
"broadcasts": [
{
"broadcast_id": "b1b2…",
"name": "May launch",
"bot_id": "e8bb61e0-…",
"bot_name": "acme_promotions",
"status": "completed",
"total_count": 18324,
"sent_count": 18200,
"delivered_count": 17501,
"read_count": 9820,
"clicked_count": 1247,
"failed_count": 124,
"started_at": "2026-07-14T08:00:01Z",
"completed_at": "2026-07-14T08:22:44Z"
}
]
}Errors:
400{"success": false, "code": "invalid_date_range", "message": "to must be on or after from"}400{"success": false, "code": "invalid_date_range", "message": "Range too wide. The maximum is 90 days."}403{"success": false, "code": "bot_not_assigned", "message": "You don't have access to this bot."}
POST /rcs/reports/export
Queue an async export. The worker picks it up within 15 s (average), uploads the file to storage, and emails a signed download link.
Auth: Authorization: Bearer <jwt> (any role, data is scoped).
Request body:
Example:
{
"bot_id": "e8bb61e0-8500-11ef-9c9a-4bc60d7a1e2f",
"from": "2026-06-15",
"to": "2026-07-14",
"format": "xlsx",
"scope": "targets"
}Success:
{
"success": true,
"export_id": "c1c2c3…",
"status": "queued",
"expires_at": "2026-07-21T12:03:44Z"
}Errors:
400{"success": false, "message": "format must be csv or xlsx"}400{"success": false, "message": "scope must be 'broadcasts' or 'targets'"}400{"success": false, "code": "invalid_date_range", "message": "…"}403{"success": false, "code": "bot_not_assigned", …}429{"success": false, "code": "too_many_exports_queued", "message": "You already have too many exports queued. Wait for one to finish then try again."}
GET /rcs/reports/exports
List recent exports (last 30 days), newest first.
Auth: Authorization: Bearer <jwt>.
Query params:
Success:
{
"success": true,
"exports": [
{
"export_id": "c1c2c3…",
"requested_by": "[email protected]",
"requested_by_role": "owner",
"bot_id_scope": "e8bb61e0-…",
"from_ts": "2026-06-15T00:00:00Z",
"to_ts": "2026-07-14T00:00:00Z",
"format": "xlsx",
"scope": "targets",
"status": "ready",
"download_url": "https://folder.splashifypro.com/rcs-exports/aaaa…/c1c2c3….xlsx",
"file_size_bytes": 482114,
"error_message": "",
"requested_at": "2026-07-14T12:03:44Z",
"completed_at": "2026-07-14T12:04:07Z",
"expires_at": "2026-07-21T12:03:44Z"
}
]
}GET /rcs/reports/exports/{export_id}
Fetch one export's current state (poll for queued → running →
ready / failed).
Success: same shape as one row of GET /reports/exports.
Errors:
400{"success": false, "message": "invalid export_id"}404{"success": false, "code": "export_not_found", "message": "Export not found. It may have expired."}
Export lifecycle
queued → running → ready (download_url populated, email sent)
↘
failed (error_message populated)queued, created; worker hasn't started yet.running, worker is streaming data + uploading.ready, file uploaded.download_urlis a public link, valid untilexpires_at(7 days afterrequested_at).failed, seeerror_message.
The "Your RCS report is ready" email fires once when status
transitions to ready. No email fires for failed, the panel
surfaces the error inline.
Retention: after expires_at both the downloadable file and its
record are deleted. Polling that export_id afterwards returns
404 export_not_found.
CSV column reference
scope=broadcasts
One row per broadcast in the window.
Sample row:
b1b2c3d4-…, "May launch", acme_promotions, completed, 18324, 18200, 17501, 9820, 1247, 124, 17290.00, 2026-07-14T08:00:01Z, 2026-07-14T08:22:44Zscope=targets
One row per recipient across every broadcast in the window.
Sample row:
b1b2c3d4-…, "May launch", acme_promotions, +919876543210, delivered, 2026-07-14T08:00:02Z, 2026-07-14T08:00:03Z, 2026-07-14T08:07:41Z, ,XLSX
Same columns as CSV, one sheet (Broadcasts or Targets), header
row frozen, columns auto-sized best-effort. Content type
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.