Reports
The Reports API covers two related-but-separate surfaces. The
ad-hoc compliance PDF is a synchronous render of the org's
current compliance posture — available on every plan,
rate-limited at 10 per user per hour because PDF generation is
expensive. The scheduled reports are recurring analytics
digests delivered by email on a daily / weekly / monthly cadence,
plan-gated by scheduledReports (Governance and above) and
managed via a small CRUD surface plus a manual trigger. Tested
against API version v1. Mounted at /api/v1/reports. JWT auth
on every endpoint; scheduled-report mutations require admin or
owner.
Endpoints
GET /api/v1/reports/compliance ── synchronous PDF (any plan)
GET /api/v1/reports/schedule
POST /api/v1/reports/schedule
PATCH /api/v1/reports/schedule/:id
DELETE /api/v1/reports/schedule/:id
POST /api/v1/reports/send-now/:id
Download the compliance PDF
GET /api/v1/reports/compliance
Returns a binary PDF (Content-Type: application/pdf) summarising
the org's compliance posture: trace volume by status, average
confidence, policy coverage, hash-chain verification result,
incident-clock state, and the regulator-mapping table. The
endpoint is available on every plan because the artefact is what
operators hand to a regulator during an inspection — it is
not optional infrastructure.
curl -o adjudon-compliance.pdf \
-H "Authorization: Bearer $ADJUDON_JWT" \
https://api.adjudon.com/api/v1/reports/compliance
The render is rate-limited at 10 requests per user per hour
via complianceReportRateLimit (backend/middleware/cpiRateLimit.js:50).
Past that, the limiter returns
429 RATE_LIMITED with a hint to use scheduled reports for higher
volume. PDF generation is bounded but not budgeted under the
trace-ingestion latency contract; expect tens of seconds on a
large org.
Errors: 401, 429 RATE_LIMITED, 500.
The ScheduledReport object
| Field | Type | Required | Description |
|---|---|---|---|
_id / id | string | yes | MongoDB ObjectId |
organizationId | string | yes | Owning org |
workspaceId | string | null | no | null for org-wide |
name | string | yes | Operator-readable; max 120 chars |
frequency | enum | yes | daily, weekly, monthly |
recipients[] | string[] | yes | 1-20 email addresses |
includeMetrics | boolean | no | KPI summary card; default true |
includeTopAgents | boolean | no | Top-5 agents; default true |
includeAlerts | boolean | no | Recent flagged / escalated traces; default true |
includeLowConf | boolean | no | Traces below confidence threshold; default false |
isActive | boolean | no | Default true; inactive reports are not sent |
lastSentAt | string (ISO 8601) | no | Set on each successful send |
nextSendAt | string (ISO 8601) | no | Computed from frequency |
sentCount | number | no | Cumulative successful-send count |
createdBy, createdByEmail | string | yes | Snapshotted at create time |
createdAt / updatedAt | string (ISO 8601) | no | Standard timestamps |
The createdByEmail snapshot survives user deletion — an
audit reading the report history a year later can still attribute
the configuration without joining against the live User
collection.
List scheduled reports
GET /api/v1/reports/schedule
Returns every active scheduled report for the caller's org. Errors:
401, 403 UPGRADE_REQUIRED (plan), 403 FORBIDDEN (role), 500.
Create a scheduled report
POST /api/v1/reports/schedule
curl -X POST https://api.adjudon.com/api/v1/reports/schedule \
-H "Authorization: Bearer $ADJUDON_JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekly Risk digest",
"frequency": "weekly",
"recipients": ["[email protected]", "[email protected]"],
"includeMetrics": true,
"includeTopAgents": true,
"includeAlerts": true,
"includeLowConf": true
}'
The first send fires on the next scheduler tick after creation;
the nextSendAt field is computed server-side from frequency.
The scheduler runs as a background process (reportScheduler
started in backend/server.js:63); it reads every
isActive: true report and sends those whose nextSendAt <= now.
A failed send leaves lastSentAt unchanged and the next tick
retries.
Errors: 400 VALIDATION_ERROR (missing fields, recipients out of
1-20 range), 401, 403, 500.
Update / delete
PATCH /api/v1/reports/schedule/:id
DELETE /api/v1/reports/schedule/:id
PATCH accepts partial updates; flip isActive: false to pause
without losing the configuration. DELETE hard-deletes the row.
Trigger a send now
POST /api/v1/reports/send-now/:id
Renders and sends the report immediately, regardless of the
schedule. Useful for testing recipient delivery, validating PDF
content after a configuration change, or sending an off-cycle
digest after a notable incident. Increments sentCount and
updates lastSentAt but does not advance nextSendAt
— the recurring schedule continues uninterrupted.
Common gotchas
- Two surfaces, two access patterns.
/complianceis available on every plan because the artefact is non-optional for regulator inspection;/schedule/*is Governance+ because the recurring digest is a workflow optimisation. - PDF generation is bounded, not budgeted. A large org's compliance render takes tens of seconds. The 10/hour limit is the brake; for higher volume use Scheduled Reports.
- Recipients are external emails. The 1-20 cap exists to defend against accidental mass distribution. Internal users who want the digest must subscribe by email; the report does not surface in-app via the Notifications API.
send-nowdoes not skip the schedule. A manual send leaves the recurring cadence intact. If you want to "reset" the schedule after a manual send, PATCHnextSendAtdirectly.- Idempotency. The
Idempotency-Keymiddleware is wired only onPOST /traces; report mutations do not auto-receive replay protection. The(organizationId, name)lookup is not enforced as unique, so two reports with the same name are permitted — the dashboard surfaces the duplication warning client-side.
See also
- Analytics API — the live read-only aggregation surface this report renders against
- Notifications API — the in-app inbox alternative for org-wide updates
- Plans & Features —
the
scheduledReportsfeature gate - Sub-Processors — Resend (EU) is the transactional-email path for digest delivery
- Error Codes — the broader error taxonomy