Skip to main content

Reports API

Quick example

# Run a catalog report and download as CSV
curl "$TRINITITE_BASE/v1/reports/run/ciso.alerts_by_severity?format=csv&period=30d" \
-H "Authorization: Bearer $TRINITITE_API_KEY" \
-o alerts.csv

See Enterprise Reporting for the architecture.


Overview

A horizontal BI surface that sits beside the persona dashboards in the rest of the platform. It provides:

  • A catalogue of 60+ parameterised reports curated across eight personas.
  • A semantic layer of whitelisted sources (tables and views) with typed dimensions, metrics, filters, and time-grain support.
  • A safe query-builder that lets you compose your own saved-report definitions without touching SQL.
  • Multi-format rendering: JSON, CSV, plain PDF, branded PDF, XLSX.
  • Scheduled delivery (cron) over email, webhook, SFTP, and S3.
  • Signed artifact downloads with row-level organisation isolation enforced at compile time.

Authentication: Authorization: Bearer <session_token | api_key> with analytics:read (and analytics:create / analytics:update / analytics:delete for the saved reports and schedules surfaces).

Entitlement-gated

Most of this surface requires the enterprise_reporting entitlement. Branded PDF needs enterprise_reporting.branded_pdf; the query builder needs enterprise_reporting.builder; schedules need enterprise_reporting.schedules. Insufficient entitlement returns 403 entitlement_denied.


Endpoints

Catalogue

MethodPathPermission
GET/v1/reports/cataloganalytics:read
GET/v1/reports/catalog/{reportId}analytics:read
GET/v1/reports/semantic-sourcesanalytics:read

Runs & artifacts

MethodPathPermission
POST/v1/reports/runsanalytics:read
GET/v1/reports/runsanalytics:read
GET/v1/reports/runs/{runId}analytics:read
GET/v1/reports/runs/{runId}/artifacts/{format}analytics:read
POST/v1/reports/runs/{runId}/artifacts/{format}/signanalytics:read
GET/v1/reports/artifacts/local/{relPath}(signed URL only)

Saved reports

MethodPathPermission
GET/v1/reports/savedanalytics:read
GET/v1/reports/saved/{savedReportId}analytics:read
POST/v1/reports/savedanalytics:create
PUT/v1/reports/saved/{savedReportId}analytics:update
DELETE/v1/reports/saved/{savedReportId}analytics:delete

Schedules

MethodPathPermission
POST/v1/reports/schedulesanalytics:create
GET/v1/reports/schedulesanalytics:read
GET/v1/reports/schedules/{scheduleId}analytics:read
PUT/v1/reports/schedules/{scheduleId}analytics:update
DELETE/v1/reports/schedules/{scheduleId}analytics:delete

Personas & catalogue

PersonaExamples
ceoceo.executive_summary, ceo.roi_quarterly
cfocfo.cost_by_model, cfo.tool_cost_by_type
cisociso.alerts_by_severity, ciso.threat_source_breakdown
crocro.var_trend, cro.sir_utilization
gcgc.failed_audit_events, gc.correction_trend_monthly
auditorauditor.evidence_pack, auditor.governance_coverage
opsops.hourly_activity, ops.tool_block_rate

Semantic layer

GET /v1/reports/semantic-sources enumerates every available source with its full schema (typed dimensions, metrics, filters, supported time grains, and the mandatory organisation column). The semantic layer is the atomic unit of the query builder — you can only reference dimensions / metrics / filters that the source declares.

Available sources include governance_daily (unified daily outcomes), tool_usage_hourly (MCP tool invocations), proxy_logs (row-level LLM calls), audit_logs (actor / action / resource), liability_daily, nhi_daily, tool_cost_hourly, mcp_alert_events, mcp_config_audit, correction_patterns, roi_snapshots.


Report definition shape

interface ReportDefinition {
id: string;
version: number;
persona: 'ceo' | 'cfo' | 'ciso' | 'cro' | 'gc' | 'auditor' | 'ops';
title: string;
description: string;
source_id: string; // whitelisted source
dimensions: string[]; // whitelisted dimension keys
metrics: string[]; // whitelisted metric keys (>= 1)
time_grain?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
default_filters: ReportFilterBinding[];
sort?: ReportSortSpec[];
limit?: number; // hard cap 10,000
params?: ReportParamSpec[];
chart_hints?: ChartHint[];
redact?: RedactionRule[];
entitlement_key?: string;
tags?: string[];
}

All whitelisting is enforced eagerly when a definition is registered, and re-checked at execution time inside the SQL compiler.


Run a report

POST /v1/reports/runs — synchronous; returns the run record, a 100-row preview, and downloadable artifacts.

{
"report_id": "ceo.executive_summary",
"formats": ["pdf_branded", "json"],
"params": {
"period_start": "2026-04-01T00:00:00Z",
"period_end": "2026-04-30T23:59:59Z"
},
"render_options": {
"title": "Acme Corp — April 2026 Executive Summary",
"subtitle": "Confidential — Board Distribution Only",
"organization_name": "Acme Corp",
"logo_url": "https://assets.acme.example.com/logo.svg"
}
}
FieldTypeDescription
report_idstringOne-of: catalogue report ID
saved_report_idstringOne-of: tenant-authored saved report ID
formatsstring[]Any of json, csv, pdf, pdf_branded, xlsx
paramsobjectParameter overrides declared by the definition's params
render_optionsobjectBranded-PDF parameters

Response — 201 Created:

{
"run": {
"run_id": "rrun_01JF8RRRN1A2B3C4D5E6F7G8H9I",
"organization_id": "org_01JF8RORG1A2B3C4D5E6F7G8H9I",
"report_id": "ceo.executive_summary",
"status": "ready",
"started_at": "2026-05-01T22:14:00Z",
"finished_at": "2026-05-01T22:14:08Z",
"row_count": 1
},
"result_preview": {
"columns": ["period", "ars_score", "liability_shielded_usd", "peer_percentile"],
"rows": [["2026-04", 0.86, 1284000, 89]],
"preview_truncated": false
},
"artifacts": [
{
"format": "json",
"url": "/v1/reports/runs/rrun_01JF8RRRN1A2B3C4D5E6F7G8H9I/artifacts/json",
"size_bytes": 1842
},
{
"format": "pdf_branded",
"url": "/v1/reports/runs/rrun_01JF8RRRN1A2B3C4D5E6F7G8H9I/artifacts/pdf_branded",
"size_bytes": 248792
}
]
}

result_preview.rows is truncated to 100 rows; fetch a format artifact for the full payload. The hard row cap on every report is 10,000.

Sign an artifact for download

POST /v1/reports/runs/{runId}/artifacts/{format}/sign returns a short-lived signed URL the caller can hand off without exposing platform credentials:

{ "ttl_seconds": 3600 }
{
"url": "https://api.trinitite.ai/v1/reports/artifacts/local/...?expires=1746148800&sig=...",
"expires_at": "2026-05-01T23:14:00Z"
}

Verifiable by re-computing the HMAC over (rel_path, expires) with the platform's signing key — the algorithm is documented in your enterprise contract.


Saved reports (query builder)

A saved report is a tenant-authored ReportDefinition derived from a catalogue base.

POST /v1/reports/saved

{
"base_report_id": "ops.hourly_activity",
"title": "Acme Ops — APAC Region Hourly",
"description": "Hourly activity filtered to APAC.",
"default_filters": [
{ "field": "region", "op": "eq", "value": "apac" }
],
"tags": ["acme", "apac", "ops"]
}

The platform validates that all referenced fields exist on the base report's source before persisting.

PUT /v1/reports/saved/{savedReportId}

Replace the saved definition. Returns the updated record.

DELETE /v1/reports/saved/{savedReportId}

204 No Content. Schedules pointing at this saved report stop firing on the next tick (and are flagged with disabled_reason: "saved_report_deleted" until you remove or re-target them).


Schedules

Cron-driven recurring delivery of a report over one or more channels.

POST /v1/reports/schedules

{
"saved_report_id": "rsav_01JF8RRSV1A2B3C4D5E6F7G8H9I",
"cron": "0 8 * * MON",
"timezone": "America/Los_Angeles",
"formats": ["pdf_branded"],
"channels": [
{ "type": "email", "to": ["board@acme.example.com"], "subject": "Acme weekly executive summary" },
{ "type": "s3", "bucket": "acme-reports", "prefix": "trinitite/weekly/", "region": "us-west-2" }
],
"enabled": true
}
FieldTypeDescription
report_id / saved_report_idstringOne-of
cronstringStandard 5-field cron expression
timezoneIANA TZTimezone for cron evaluation
formatsstring[]Same enum as runs
channelsarrayOne or more of email, webhook, sftp, s3
enabledbooleanSoft-disable without deleting

Channel shapes

// email
{ "type": "email", "to": ["..."], "cc": [], "bcc": [], "subject": "..." }

// webhook
{ "type": "webhook", "url": "https://...", "headers": { "X-API-Key": "..." }, "secret": "..." }
// Webhooks are signed with X-Trinitite-Signature (HMAC-SHA256 over the body using `secret`)

// sftp
{ "type": "sftp", "host": "...", "port": 22, "username": "...", "remote_path": "/incoming/", "auth": { "type": "password" | "key", "value": "..." } }

// s3
{ "type": "s3", "bucket": "...", "prefix": "...", "region": "us-west-2", "kms_key_id": "..." }

List / get / update / delete

GET /v1/reports/schedules (cursor pagination), GET /v1/reports/schedules/{id}, PUT /v1/reports/schedules/{id} (replace), DELETE /v1/reports/schedules/{id} (204 No Content).


Webhook signature verification

Webhook deliveries include the header X-Trinitite-Signature: t=<unix>,v1=<hmac_sha256_hex>. To verify:

import hmac, hashlib

signature_header = request.headers["X-Trinitite-Signature"]
parts = dict(p.split("=") for p in signature_header.split(","))
ts, sig = parts["t"], parts["v1"]

body = request.body # raw bytes
expected = hmac.new(
secret.encode("utf-8"),
f"{ts}.".encode("utf-8") + body,
hashlib.sha256,
).hexdigest()

assert hmac.compare_digest(expected, sig)

Reject any signature where t is more than 5 minutes off from your clock.


Errors

HTTPerror.codeCause
400validation_errorBody or query failed schema validation
400bad_requestDefinition references unknown dimension / metric
401unauthenticatedMissing or invalid credential
403forbiddenCaller lacks the required analytics:* permission
403entitlement_deniedCaller's organisation lacks the required entitlement
404not_foundCatalogue report, saved report, run, artifact, or schedule not found
409conflictCron expression collides with an existing schedule on the same definition
410resource_goneRun has expired (artifacts garbage-collected)
422unprocessable_entityRow cap (10,000) would be exceeded
429rate_limitedPer-organization rate limit exceeded

Best practices

  • Use saved reports for stable views. A saved report freezes the parameters; a catalogue run with params is good for one-offs.
  • Schedule signed downloads, not raw PDFs. Webhook + signed URL avoids large binary payloads in your SIEM.
  • Cap the row count. Most BI dashboards do not need 10,000 rows. Lower limit in your saved report to keep PDFs sane.
  • Branded PDFs aren't free. They re-render every run; prefer plain PDF or XLSX where the design isn't needed.

Next steps