-
Notifications
You must be signed in to change notification settings - Fork 1
Admin Audit 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).
| 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.
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.
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
doneAn 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.
Lists system_audit_entries — one row per successful /admin/* write.
| 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.
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 |
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}'Returns a single system_audit_entries row by its UUID.
-
404if 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.
Lists audit_entries across all threat models — investigator view with no fixed threat_model_id constraint.
| 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 |
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}'Returns a single audit_entries row by UUID. The entry already carries threat_model_id, so no parent path segment is needed.
-
404if the entry does not exist;400for an invalid UUID format.
| 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 |
- Authentication#privilege-restrictions -- Why CCG tokens are denied on all /admin/* routes
- Maintenance-Tasks#audit-retention-and-immutability -- Retention and immutability of the underlying tables
- Webhook-Integration#operator-pinned-alert-sink -- Real-time alert sink for system audit events
- REST-API-Reference -- Full OpenAPI-generated endpoint reference
- Using TMI for Threat Modeling
- Accessing TMI
- Authentication
- Identity Linking
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Timmy AI Assistant
- Metadata and Extensions
- Planning Your Deployment
- Terraform Deployment (AWS, OCI, GCP, Azure)
- Deploying TMI Server
- OCI Container Deployment
- Certificate Automation
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Bootstrapping Production
- Component Integration
- Post-Deployment
- Branding and Customization
- Monitoring and Health
- Cloud Logging
- Configuring Local Development
- Managing Operational Settings
- Content Extractors - Limits and Overrides
- Database Operations
- Database Security Strategies
- Transaction Isolation
- Oracle Content Feedback FK Cleanup
- Security Operations
- Performance and Scaling
- Maintenance Tasks
- Getting Started with Development
- Local Development Cluster
- Architecture and Design
- API Integration
- Testing
- Contributing
- Extending TMI
- Dependency Upgrade Plans
- DFD Graphing Library Reference
- Migration Instructions
- Issue Tracker Integration
- Webhook Integration
- Addon System
- MCP Integration
- Delegated Content Providers
- Setting Up Google Content Providers
- API Clients
- API Client Maintenance
- Database Tool Reference
- TMI Terraform Analyzer
- TMI Promtail Logger
- WebSocket Test Harness