Endpoints: Metrics & Audit
Metrics
Section titled “Metrics”All metrics endpoints require admin role, and are scoped to the caller’s own organization. Three span types are recorded automatically:
| Type | Recorded when | Key fields in data |
|---|---|---|
request | Every HTTP request | method, path, status_code, auth_type (api_key/session/anonymous) |
ingest | Every ingest — POST .../ingest and POST .../analyze (see below) | namespace, analyzer, resource_count |
analysis | Successful analysis submission | resource_type, resource_address, finding_count, severity_counts |
Every span carries key_id (the API-key principal, if any) and user_id (the session
principal, if any) as separate top-level fields — exactly one is non-null on a given
span, mirroring AuthContext’s own split. These used to be merged into one key_id
field; they’re independent now specifically so a span can be filtered or attributed to
a genuine human actor without conflating them with a machine one.
GET /metrics/summary
Section titled “GET /metrics/summary”Aggregated statistics for the last N days.
| Parameter | Type | Default | Description |
|---|---|---|---|
days | int | 7 | Aggregation window (1–365) |
Response 200
{ "period_days": 7, "requests": { "total": 1240, "by_status": {"200": 900, "201": 180, "401": 60, "404": 100}, "by_method": {"GET": 980, "POST": 180, "PUT": 80}, "error_count": 160, "error_rate_pct": 12.9, "avg_duration_ms": 38.4 }, "ingest": {"total": 42, "total_resources": 310}, "analysis": { "total": 310, "by_severity": {"CRITICAL": 12, "HIGH": 88, "MEDIUM": 130, "LOW": 45, "INFO": 35}, "avg_findings_per_resource": 2.9 }}GET /metrics/spans
Section titled “GET /metrics/spans”Paginated raw span log, newest first.
| Parameter | Type | Default | Description |
|---|---|---|---|
type | string | — | Filter: request, ingest, or analysis |
user_id | string | — | Filter to spans from this human (session) actor |
key_id | string | — | Filter to spans from this machine (API-key) actor |
limit | int | 100 | Number of results (1–1000) |
offset | int | 0 | Pagination offset |
Response 200
[ { "span_id": "3f4a...", "trace_id": "x-request-id-value", "type": "ingest", "timestamp": "2026-01-01T12:00:00Z", "duration_ms": 45.2, "key_id": "config", "user_id": null, "data": { "namespace": "prod-deploy-42", "analyzer": "terraform-plan", "resource_count": 7 } }]GET /metrics/usage
Section titled “GET /metrics/usage”Per-key request counts, sorted descending.
| Parameter | Type | Default | Description |
|---|---|---|---|
days | int | 7 | Aggregation window (1–365) |
Response 200
[ {"key_id": "config", "request_count": 540}, {"key_id": "3f4a5b6c-...", "request_count": 700}]key_id is _anon when auth is disabled. Only counts api_key-authenticated requests
(see the Metrics section’s note above) — a session-authenticated dashboard user never
appears here bucketed under their user_id as if it were a key.
Distinct from the spans above: spans describe HTTP shape (method/path/status/ duration) and coarse ingest/analysis counts; audit events describe privileged actions — who changed what, on which target, with before/after values. All audit endpoints require admin role and are scoped to the caller’s own organization (see Platform admin below for the cross-org equivalent).
Instrumented actions (first cut — see the note at the end of this section for what’s deliberately not yet covered):
| Action | Target type | Fired by |
|---|---|---|
settings.update | platform_settings | PATCH /admin/settings |
org.lock | organization | PATCH /admin/organizations/{org_id}/lock |
org.plan_update | organization | PATCH /admin/organizations/{org_id}/plan |
org.2fa_policy_update | organization | PATCH /admin/organizations/{org_id}/2fa-policy |
superadmin.grant | user | POST /admin/users/{user_id}/superadmin |
member.role_update | membership | PATCH /orgs/{org_id}/members/{user_id} (org-admin) or its superadmin mirror PATCH /admin/organizations/{org_id}/members/{user_id} |
member.remove | membership | DELETE /orgs/{org_id}/members/{user_id} or its superadmin mirror |
member.password_reset | user | POST /orgs/{org_id}/members/{user_id}/reset-password or its superadmin mirror |
member.2fa_reset | user | POST /orgs/{org_id}/members/{user_id}/reset-2fa or its superadmin mirror |
user.account_update | user | PATCH /admin/users/{user_id} (account-wide, not org-scoped) |
user.password_reset | user | POST /admin/users/{user_id}/reset-password (account-wide) |
user.2fa_reset | user | POST /admin/users/{user_id}/2fa-reset (account-wide) |
api_key.create / api_key.revoke | api_key | POST /auth/keys / DELETE /auth/keys/{key_id} |
sso.update | sso_connection | PATCH /orgs/{org_id}/sso |
finding.status_update | finding | PATCH /findings/{resource_hash}/{rule_id} |
team.create / team.update / team.delete | team | POST/PATCH/DELETE /orgs/{org_id}/teams[/{team_id}] |
team.member_add / team.member_remove | team_member | POST/DELETE /orgs/{org_id}/teams/{team_id}/members[/{user_id}] |
rule.create / rule.update / rule.delete | custom_rule | POST/PATCH/DELETE /orgs/{org_id}/rules[/{rule_id}], or their superadmin cross-tenant mirror under /admin/custom-rules |
report_template.create / report_template.update / report_template.delete | report_template | POST/PATCH/DELETE /orgs/{org_id}/report-templates[/{template_id}] |
invite.create / invite.revoke | org_invite | POST /orgs/{org_id}/invites / DELETE /orgs/{org_id}/invites/{invite_id} |
scim.config.create / scim.config.update / scim.config.delete | scim_config | POST/PATCH/DELETE /orgs/{org_id}/scim |
scim.token.rotate | scim_config | POST /orgs/{org_id}/scim/rotate-token |
scim.user.provisioned / scim.user.deactivated / scim.user.reactivated | user | /scim/v2/{org_id}/Users[/{id}] create/deactivate/reactivate/delete |
scim.group.created / scim.group.renamed / scim.group.deleted | team | /scim/v2/{org_id}/Groups[/{id}] create/rename/delete |
scim.group.membership_synced | team | PATCH/PUT /scim/v2/{org_id}/Groups/{id} (member add/remove) |
GET /metrics/audit
Section titled “GET /metrics/audit”Paginated audit events for the caller’s organization, newest first.
| Parameter | Type | Default | Description |
|---|---|---|---|
action | string | — | Filter by action, e.g. settings.update |
actor_id | string | — | Matches either actor_user_id or actor_key_id |
target_type | string | — | Filter by target type, e.g. api_key |
since / until | string (ISO-8601) | — | Inclusive time-range bounds |
limit | int | 100 | Number of results (1–1000) |
offset | int | 0 | Pagination offset |
Response 200
{ "events": [ { "event_id": "3f4a...", "org_id": "org-id", "action": "api_key.create", "target_type": "api_key", "target_id": "key-id", "actor_user_id": "user-id", "actor_key_id": null, "actor_is_superadmin": false, "ip": "203.0.113.5", "before": null, "after": {"role": "member", "name": "ci-bot", "ip_allowlist": null}, "request_id": "x-request-id-value", "timestamp": "2026-01-01T12:00:00Z" } ], "total_returned": 1}GET /metrics/audit/export
Section titled “GET /metrics/audit/export”Download the same filtered query as CSV or JSON, up to 10,000 rows.
| Parameter | Type | Default | Description |
|---|---|---|---|
format | string | json | csv or json |
action, actor_id, target_type, since, until | — | — | Same filters as GET /metrics/audit |
limit | int | 1000 | Number of results (1–10000) |
Returns a file download (Content-Disposition: attachment) rather than an inline
JSON body — before/after are JSON-encoded into their own CSV cells, since the
field set varies per action.