API reference
21 operations in reports. Raw spec: openapi.json.
GET
/api/v1/report-catalogGET /api/report-catalog — the fixed field catalog for the R6 builder (ADR 0001): per-view projected columns + filterable inventory with value classes. Static data served from the same Rust constants the compiler validates against, so the UI can never offer an identifier the backend would reject.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
GET
/api/v1/report-definitionsGET /api/report-definitions — visible definitions, deterministically ordered `(name, id)`.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
POST
/api/v1/report-definitionsPOST /api/report-definitions — create a stored definition through the instant-first write path (idempotency key honored; response carries `mutation` metadata).
Request body
CreateReportDefinitionRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | missing reporting:create |
409 | name conflict or idempotency key reused |
422 | spec failed registry validation |
Security: bearer
GET
/api/v1/report-definitions/{id}GET /api/report-definitions/{id} — one visible definition; anything not visible (deleted, other org, private of another user) is a bare 404.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
404 | not visible in scope |
Security: bearer
PUT
/api/v1/report-definitions/{id}PUT /api/report-definitions/{id} — full-replace update through the instant-first write path (version gate enforced; a miss inside a mutation is the typed `rls_sparse` reason, not `not_found`).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Request body
UpdateReportDefinitionRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | definition outside the caller's writable scope |
404 | rls_sparse: target not visible |
409 | version conflict or idempotency key reused |
422 | spec failed registry validation |
Security: bearer
DELETE
/api/v1/report-definitions/{id}DELETE /api/report-definitions/{id} — removal through the same gated path; the write trail keeps the deleted snapshot (undo substrate).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
403 | definition outside the caller's writable scope |
404 | rls_sparse: target not visible |
409 | version conflict |
Security: bearer
GET
/api/v1/report-definitions/{id}/runGET /api/report-definitions/{id}/run — compile-on-demand execution of a visible stored definition's spec (R6 builder runs). The spec re-validates through the registry, compiles via `ReportSpec::compile_windowed` (identifiers stay enum-matched catalog constants; values bind as `$N` parameters), and executes against the reporting pool inside an org-bound transaction. Non-admins are structurally self-scoped exactly like the prebuilt kinds.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | |
from | query | — | No | Inclusive window start (UTC day, YYYY-MM-DD). Default: 30 trailing days. |
to | query | — | No | Inclusive window end (UTC day). Max span with `from`: 366 days. |
limit | query | — | No | Page size, clamped to [1, 500]. |
format | query | — | No | `"csv"` streams text/csv instead of the JSON envelope. |
Responses
| Status | Description |
|---|---|
200 | Report page (JSON envelope or CSV stream) |
404 | not visible in scope |
422 | spec failed registry validation or invalid window |
Security: bearer
GET
/api/v1/report-schedulesGET /api/report-schedules — cursor-paged listing (admin), ordered `(name, id)`, each row carrying its next-run preview.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
cursor | query | — | No | Opaque keyset cursor from a previous page's `next_cursor`. |
limit | query | — | No | Page size, clamped to [1, 200]. |
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
POST
/api/v1/report-schedulesPOST /api/report-schedules — create a schedule through the instant-first write path; the next-run preview computes from now.
Request body
CreateReportScheduleRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | admin-only surface |
409 | name conflict or idempotency key reused |
422 | validation failed (shape, recipients, definition) |
Security: bearer
GET
/api/v1/report-schedules/{id}GET /api/report-schedules/{id} — one schedule; anything not in this org is a bare 404.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
404 | not in scope |
Security: bearer
PUT
/api/v1/report-schedules/{id}PUT /api/report-schedules/{id} — full-replace update; the next-run preview recomputes from now (cadence changes cannot strand a slot in the past; the worker's claim sweep catches up enabled schedules anyway, so this only affects the preview's freshness).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Request body
UpdateReportScheduleRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | admin-only surface |
404 | rls_sparse: target not visible |
409 | name conflict or version conflict |
422 | validation failed |
Security: bearer
DELETE
/api/v1/report-schedules/{id}DELETE /api/report-schedules/{id} — removal through the same gated path; ledger rows cascade and the write trail keeps the snapshot.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
403 | admin-only surface |
404 | rls_sparse: target not visible |
409 | version conflict |
Security: bearer
GET
/api/v1/report-schedules/{id}/deliveriesGET /api/report-schedules/{id}/deliveries — the delivery ledger for one schedule (R4 history surface). Worker-written data: rows are append-only run records, never mutated, newest slot first. A missing schedule is a bare 404.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes | |
limit | query | — | No | Page size, clamped to [1, 200]. |
offset | query | — | No | Row offset for paging. |
Responses
| Status | Description |
|---|---|
200 | |
404 | not in scope |
Security: bearer
POST
/api/v1/reports/run-specPOST /api/reports/run-spec — execute an INLINE spec (R6 builder preview). Same compile/execute/RBAC seam as running a stored definition; the spec never touches storage and identifiers re-validate against the static catalog before compilation.
Request body
RunSpecRequest
Responses
| Status | Description |
|---|---|
200 | |
422 | spec failed registry validation or invalid window |
Security: bearer
GET
/api/v1/reports/{kind}GET /api/reports/{kind} — prebuilt operational report over the read-model views. Kinds: time_by_user | time_by_client | time_by_ticket | sla_attainment | sla_breaches | ticket_throughput | ticket_aging | first_response.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | — | No | Inclusive window start (UTC day, YYYY-MM-DD). Default: 30 trailing days. |
to | query | — | No | Inclusive window end (UTC day). Max span with `from`: 366 days. |
cursor | query | — | No | Opaque keyset cursor from a previous page's `next_cursor`. |
limit | query | integer | No | Page size, clamped to [1, 500]. |
format | query | — | No | `"csv"` streams text/csv instead of the JSON envelope. |
user_id | query | — | No | Manager-only narrowing onto one staff member; non-managers are ALWAYS forced onto themselves regardless of this parameter. |
policy_id | query | — | No | SLA kinds: restrict to one governing policy. |
billable | query | — | No | Time rollups: filter billable/non-billable entries. |
as_of | query | — | No | ticket_aging only: snapshot instant for ages (default: now). |
kind | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | Report page (JSON envelope or CSV stream) |
Security: bearer
GET
/api/v1/scorecardsGET /api/scorecards?from&to — every active staff member's scorecard in one org-wide read (owner/admin only; R5 private-by-default means a plain tech never sees anyone else's card here, including via query tricks — the scope is derived from the principal, never the request).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | — | No | Inclusive window start (UTC day, YYYY-MM-DD). Default: 30 trailing days. |
to | query | — | No | Inclusive window end (UTC day). Max span with `from`: 366 days. |
Responses
| Status | Description |
|---|---|
200 | |
403 | team scorecards are owner/admin only |
422 | invalid window |
Security: bearer
GET
/api/v1/scorecards/clientsGET /api/scorecards/clients?from&to — per-client CSAT rollups over the window (owner/admin only). `available: false` until the org records its first survey, matching the per-tech section's honesty gate.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | — | No | Inclusive window start (UTC day, YYYY-MM-DD). Default: 30 trailing days. |
to | query | — | No | Inclusive window end (UTC day). Max span with `from`: 366 days. |
Responses
| Status | Description |
|---|---|
200 | |
403 | missing reporting:view |
422 | invalid window |
Security: bearer
GET
/api/v1/scorecards/meGET /api/scorecards/me?from&to — the caller's own scorecard.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | — | No | Inclusive window start (UTC day, YYYY-MM-DD). Default: 30 trailing days. |
to | query | — | No | Inclusive window end (UTC day). Max span with `from`: 366 days. |
Responses
| Status | Description |
|---|---|
200 | |
403 | missing reporting:view |
422 | invalid window |
Security: bearer
GET
/api/v1/scorecards/me/settingsGET /api/scorecards/me/settings — the caller's scorecard settings (absent row reads as the private-by-defaults).
Responses
| Status | Description |
|---|---|
200 | |
403 | missing reporting:view |
Security: bearer
PATCH
/api/v1/scorecards/me/settingsPATCH /api/scorecards/me/settings — flip the R5 opt-in gamification toggle. Only ever touches the CALLER's row (no user_id in the body by design); emits `scorecard.settings_updated` in the same transaction.
Request body
UpdateScorecardSettingsRequest
Responses
| Status | Description |
|---|---|
200 | |
403 | missing reporting:view |
Security: bearer
GET
/api/v1/scorecards/{user_id}GET /api/scorecards/{user_id}?from&to — one member's scorecard; a member may read their own, owner/admin may read anyone's.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | — | No | Inclusive window start (UTC day, YYYY-MM-DD). Default: 30 trailing days. |
to | query | — | No | Inclusive window end (UTC day). Max span with `from`: 366 days. |
user_id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
403 | other members' scorecards are owner/admin only |
404 | member not in scope (rls_sparse) |
422 | invalid window |
Security: bearer