Skip to content

Admin Audit API

Eric Fitzgerald edited this page Jun 12, 2026 · 1 revision

Admin Audit Query API

TMI exposes four read-only endpoints under /admin/audit/ for investigators to query the two audit streams: the per-resource audit_entries trail (threat-model object changes) and the system_audit_entries trail (admin-write evidence). Both require the admin role; service-account tokens are categorically denied (see Authentication#privilege-restrictions).

Overview

Operation Path Description
GET /admin/audit/system listSystemAuditEntries List admin-write events with filters and cursor pagination
GET /admin/audit/system/{entry_id} getSystemAuditEntry Get a single system audit entry
GET /admin/audit/threat_models listAdminThreatModelAuditEntries Cross-TM audit trail with filters and cursor pagination
GET /admin/audit/threat_models/{entry_id} getAdminThreatModelAuditEntry Get a single threat-model audit entry

All four endpoints require an interactive JWT with the admin role. No step-up re-authentication is required on reads.

Pagination

Both list endpoints use keyset cursor pagination rather than limit/offset. Audit tables are append-only streams; offset paging suffers from page-drift duplicates on active tables and degrades at depth. The cursor encodes the (created_at, id) of the last row returned and is opaque to callers.

Response envelope:

{
  "entries": [...],
  "total": 1284,
  "limit": 50,
  "next_cursor": "eyJ0IjoiMjAyNi0wNi0xMVQxNDozMDowMFoiLCJpIjoiN2ZhODVmNjQtNTcxNy00NTYyLWIzZmMtMmM5NjNmNjZhZmE2In0"
}
  • total: count of all filter-matching rows (computed per request).
  • next_cursor: present only when a full page was returned; absent when the last page is reached.
  • limit: 1–100, default 50.

Iterating all pages

TOKEN="<your-admin-jwt>"
CURSOR=""

while true; do
  if [ -z "$CURSOR" ]; then
    RESULT=$(curl -s "https://api.tmi.dev/admin/audit/system?limit=100" \
      -H "Authorization: Bearer $TOKEN")
  else
    RESULT=$(curl -s "https://api.tmi.dev/admin/audit/system?limit=100&cursor=$CURSOR" \
      -H "Authorization: Bearer $TOKEN")
  fi

  echo "$RESULT" | jq '.entries[]'

  CURSOR=$(echo "$RESULT" | jq -r '.next_cursor // empty')
  [ -z "$CURSOR" ] && break
done

An invalid cursor value returns 400 Bad Request. An empty result set (all filters match nothing) returns an empty entries array with total: 0 and no next_cursor.

GET /admin/audit/system

Lists system_audit_entries — one row per successful /admin/* write.

Query parameters

Parameter Type Description
actor_email string Filter by actor email address
actor_provider string Filter by identity provider (google, github, tmi, …)
created_after ISO8601 Return entries created after this timestamp
created_before ISO8601 Return entries created before this timestamp
http_method string Filter by HTTP method (PUT, POST, DELETE, PATCH)
path_prefix string http_path LIKE '{value}%' — unindexed, use alongside other filters
field_path string Exact match on the field path (e.g., auth.step_up_window_seconds)
limit int Page size 1–100, default 50
cursor string Opaque continuation cursor from previous response

All filters are optional and AND-combined.

Response fields

Each entry in the entries array:

Field Description
id UUID of the system audit entry
actor.email Actor's email address
actor.provider Actor's identity provider
actor.provider_id Actor's provider-scoped user ID
actor.display_name Actor's display name at time of write
http_method HTTP method used
http_path Full request path
field_path Setting key or field path changed (if applicable)
old_value_redacted Previous value, redacted to safe summary
new_value_redacted New value, redacted to safe summary
change_summary Human-readable description of the change
created_at RFC3339Nano timestamp

Example: find all admin writes by a specific actor in the last 7 days

curl "https://api.tmi.dev/admin/audit/system?actor_email=charlie%40example.com&created_after=2026-06-04T00:00:00Z" \
  -H "Authorization: Bearer $TOKEN" | jq '.entries[] | {path: .http_path, method: .http_method, when: .created_at}'

GET /admin/audit/system/{entry_id}

Returns a single system_audit_entries row by its UUID.

  • 404 if the entry does not exist (or the UUID is not a valid format → 400).

The entry_id in system_audit.admin_write webhook payloads links directly to this endpoint.

GET /admin/audit/threat_models

Lists audit_entries across all threat models — investigator view with no fixed threat_model_id constraint.

Query parameters

Parameter Type Description
actor_email string Filter by actor email
actor_provider string Filter by identity provider
created_after ISO8601 Return entries after this timestamp
created_before ISO8601 Return entries before this timestamp
change_type enum One of: created, updated, patched, deleted, restored, rolled_back
object_type enum One of: threat_model, diagram, threat, asset, document, note, repository
threat_model_id UUID Restrict to a single threat model
limit int Page size 1–100, default 50
cursor string Opaque continuation cursor

Example: find all deletions this week

curl "https://api.tmi.dev/admin/audit/threat_models?change_type=deleted&created_after=2026-06-08T00:00:00Z" \
  -H "Authorization: Bearer $TOKEN" | jq '.entries[] | {who: .actor_email, what: .object_type, id: .object_id, when: .created_at}'

GET /admin/audit/threat_models/{entry_id}

Returns a single audit_entries row by UUID. The entry already carries threat_model_id, so no parent path segment is needed.

  • 404 if the entry does not exist; 400 for an invalid UUID format.

Error responses

Status Condition
400 Invalid cursor, invalid UUID format, or invalid enum value
401 Missing or expired JWT
403 Non-admin JWT, or service-account (CCG) token
404 Single-entry lookup: entry not found
429 Rate limit exceeded

Related pages

Home

Releases


Getting Started

Deployment

Operation

Troubleshooting

Development

Integrations

Tools

API Reference

Reference

Clone this wiki locally