Admin client reports

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.

Endpoints

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

GET /admin/reports/clients

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

GET /admin/reports/clients/export

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.


GET /admin/reports/clients/retention

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

GET /admin/reports/clients/retention/export

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).