Back-office client reporting: a detailed per-client report and a per-period
activity/retention report. Each has a CSV export. Paths are relative to the
/api/v1 prefix; these are back-office endpoints that require an admin token.
Requires an admin bearer token (see Admin auth). Admin only. Results are scoped to the admin's assigned cities.
See Common errors.
All reports are read-only. All money is reported in USD. A client is activo when it has
an order within the inactivity window (default 90 days), otherwise inactivo.
The detailed list is page-based; see
Pagination → page-based. Every .../export
endpoint accepts ?format=csv|xlsx (default csv); xlsx returns
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.
Each report accepts sort (a column key from its allowed list below) and direction
(asc|desc, default desc). An unknown sort key or direction value returns 422.
The detailed list sorts the full dataset before paginating; .../export uses the same
sort as its list endpoint.
| Method | URI | Access | Description |
|---|---|---|---|
GET |
/admin/reports/clients |
Admin | Detailed clients, one row per client |
GET |
/admin/reports/clients/export |
Admin | CSV export of the detailed report |
GET |
/admin/reports/clients/retention |
Admin | Activity & retention, one row per period |
GET |
/admin/reports/clients/retention/export |
Admin | CSV export of the retention report |
Detailed report with one row per registered client (clients with no orders appear with zero metrics).
Query
| Field | Type | Required | Rules |
|---|---|---|---|
status |
string | no | activo or inactivo |
min_spent |
number | no | USD, ≥ 0 (on delivered spend) |
max_spent |
number | no | USD, ≥ 0 |
min_orders |
integer | no | ≥ 0 (total orders) |
max_orders |
integer | no | ≥ 0 |
cancellation_rate_min |
number | no | 0–100 (%) |
payment_method |
string | no | payment method value (matches the client's preferred method) |
registered_from |
date | no | registered on/after this date |
registered_to |
date | no | registered on/before this date; >= registered_from |
sort |
string | no | one of the sortable keys below; default newest first |
direction |
string | no | asc or desc (default desc) |
limit |
integer | no | 1–100, default 25 |
page |
integer | no | ≥ 1, default 1 |
Sortable sort keys: name, registered_at, total_orders, delivered, cancelled,
cancellation_rate, liters, spent_usd, avg_ticket_usd, solidary_usd,
rating_given, last_order_at, addresses.
Response 200
{
"items": [
{
"client_id": 1,
"name": "Ana Martínez",
"phone": "+58412...",
"registered_at": "2026-01-10T09:00:00+00:00",
"status": "activo",
"total_orders": 12,
"delivered": 10,
"cancelled": 1,
"cancellation_rate": 8.3,
"liters": 10000,
"spent_usd": 101.0,
"avg_ticket_usd": 10.1,
"preferred_method": "CASH_USD",
"preferred_method_label": "Cash USD",
"solidary_usd": 1.0,
"rating_given": 4.6,
"last_order_at": "2026-07-18T12:40:00+00:00",
"days_since_last_order": 2,
"addresses": 3
}
],
"meta": { "total": 240, "per_page": 25, "current_page": 1, "last_page": 10 },
"totals": {
"clientes": 240,
"activos": 180,
"inactivos": 60,
"gasto_total_usd": 24200.0,
"ticket_promedio_general_usd": 10.2,
"aporte_solidario_total_usd": 242.0
}
}
Errors
| Status | Body | When |
|---|---|---|
422 |
{ "error": "ValidationError", … } |
Invalid filter value |
Streams the filtered detailed report as a CSV download (text/csv); there is no JSON body
on success. Accepts the same filters (no pagination).
Response 200 — a text/csv stream (reporte_clientes_<timestamp>.csv) with the
columns Cliente, Teléfono, Fecha registro, Estado, Total pedidos, Entregados, Cancelados, Tasa cancelación (%), Litros totales, Gasto total (USD), Ticket promedio (USD), Método preferido, Aporte solidario (USD), Calif. que otorga, Último pedido, Días sin pedir, Direcciones.
Activity & retention with one row per period. The default range is the last 6 months.
Query
| Field | Type | Required | Rules |
|---|---|---|---|
date_from |
date | no | start of the range; span ≤ 366 days |
date_to |
date | no | end of the range; >= date_from |
granularity |
string | no | week or month (default month) |
sort |
string | no | one of the sortable keys below; default chronological |
direction |
string | no | asc or desc (default desc) |
Sortable sort keys: period, new, active, recurring, reactivated, lost,
cumulative_base, retention_rate, churn_rate, orders_per_active,
spend_per_active_usd. Cohorts are always computed chronologically; sort only reorders
the returned items.
Response 200
{
"granularity": "month",
"items": [
{
"period": "2026-03",
"new": 40,
"active": 120,
"recurring": 70,
"reactivated": 10,
"lost": 8,
"cumulative_base": 500,
"retention_rate": 63.6,
"churn_rate": 7.3,
"orders_per_active": 1.8,
"spend_per_active_usd": 18.4
}
],
"totals": { "new": 250, "reactivated": 60, "lost": 45, "periods": 6 }
}
retention_rate and churn_rate are null for the first period (no previous period to
compare).
Errors
| Status | Body | When |
|---|---|---|
422 |
{ "error": "ValidationError", "details": { "fieldErrors": { "date_to": ["…"] } } } |
Range wider than 366 days, or invalid filter |
Streams the filtered retention report as a CSV download (text/csv); no JSON body on
success. Same filters.
Response 200 — a text/csv stream (reporte_retencion_clientes_<timestamp>.csv)
with the columns Período, Nuevos, Activos, Recurrentes, Reactivados, Perdidos, Base acumulada, Tasa de retención (%), Churn (%), Pedidos por activo, Gasto por activo (USD).