Skip to main content

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

FieldTypeRequiredDescription
_id / idstringyesMongoDB ObjectId
organizationIdstringyesOwning org
workspaceIdstring | nullnonull for org-wide
namestringyesOperator-readable; max 120 chars
frequencyenumyesdaily, weekly, monthly
recipients[]string[]yes1-20 email addresses
includeMetricsbooleannoKPI summary card; default true
includeTopAgentsbooleannoTop-5 agents; default true
includeAlertsbooleannoRecent flagged / escalated traces; default true
includeLowConfbooleannoTraces below confidence threshold; default false
isActivebooleannoDefault true; inactive reports are not sent
lastSentAtstring (ISO 8601)noSet on each successful send
nextSendAtstring (ISO 8601)noComputed from frequency
sentCountnumbernoCumulative successful-send count
createdBy, createdByEmailstringyesSnapshotted at create time
createdAt / updatedAtstring (ISO 8601)noStandard 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
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. /compliance is 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-now does not skip the schedule. A manual send leaves the recurring cadence intact. If you want to "reset" the schedule after a manual send, PATCH nextSendAt directly.
  • Idempotency. The Idempotency-Key middleware is wired only on POST /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