Time Machine
Capture, browse, compare, and annotate query result snapshots. Manage retention cleanup — endpoints under /api/time-machine/.
Time Machine captures the full row-set output of a saved query at a point in time. Snapshots are the source of truth for drift detection, change auditing, and historical comparisons. All endpoints under /api/time-machine/.
Snapshot IDs are UUIDs accepted as strings (uppercase or lowercase hex) — the older Django URL converters were strict about case, so the routes use <str:> instead of <uuid:>.
Capture
POST /api/time-machine/capture/
Execute a saved query and store the result as a snapshot. Synchronous — returns after the APIC query completes and the snapshot is persisted.
Request:
{
"saved_query": "<id>",
"apic_connection": "<id>", // optional override
"retention_policy": "30d", // 7d | 30d | 90d | 1y | forever
"annotation": "Pre-change baseline"
}Response 201:
{
"id": "…",
"saved_query": "<id>",
"apic_connection": "<id>",
"captured_at": "…",
"captured_by": "alice",
"row_count": 142,
"version_hash": "sha256:…",
"size_bytes": 2840192,
"retention_policy": "30d",
"expires_at": "2026-05-22T…",
"annotation": "Pre-change baseline"
}Browse
GET /api/time-machine/queries/
List saved queries that have at least one snapshot. Useful for the Time Machine landing page — which queries do we have history for?
Response 200:
{
"queries": [
{ "id": "…", "name": "Tenant BD health", "snapshot_count": 42, "latest_snapshot_at": "…" },
…
]
}GET /api/time-machine/snapshots/?saved_query=<id>
List snapshots for a specific query, most recent first. Paginated.
Resource shape (list):
{
"id": "…",
"captured_at": "…",
"captured_by": "alice",
"row_count": 142,
"version_hash": "sha256:…",
"retention_policy": "30d",
"annotation": null
}Row data isn't returned in list mode — fetch the detail endpoint for the full payload.
GET /api/time-machine/snapshots/<id>/
Full snapshot. Returns the entire captured result set (can be large; clients should stream the response).
POST /api/time-machine/snapshots/<id>/annotate/
Add or replace an annotation.
Request: { "annotation": "After migration" }.
Compare
POST /api/time-machine/compare/
Diff two snapshots. Returns added, removed, and changed rows.
Request:
{
"snapshot_a": "<id>",
"snapshot_b": "<id>",
"key_fields": ["dn"] // columns used to match rows across snapshots
}Response 200:
{
"summary": { "added": 3, "removed": 1, "changed": 12, "unchanged": 126 },
"added": [ /* rows only in B */ ],
"removed": [ /* rows only in A */ ],
"changed": [
{
"key": { "dn": "uni/tn-prod/BD-web" },
"fields": { "status": { "before": "formed", "after": "not-applied" } }
}
]
}key_fields lets you override the default matching strategy. Default is to match on dn for ACI MOs, then fall back to a hash of all columns. If your query produces rows without DNs, pass the fields that uniquely identify a row.
Visualization
GET /api/time-machine/heatmap/?saved_query=<id>&window=30d
Change intensity per day across a window. Returns a calendar-style matrix suitable for rendering a heatmap.
Response 200:
{
"window": "30d",
"days": [
{ "date": "2026-04-01", "snapshot_count": 24, "change_score": 0.02 },
{ "date": "2026-04-02", "snapshot_count": 24, "change_score": 0.47 },
…
]
}GET /api/time-machine/timeline/
Powers the Track DN panel. Returns the evolution of every attribute on a single Distinguished Name across the snapshots of a saved query.
Query parameters:
| Param | Required | Notes |
|---|---|---|
saved_query_id | yes | The saved query whose snapshots you're scanning. |
dn | yes | The exact DN string (e.g. uni/tn-prod/BD-web). |
limit | no | Max snapshots to consider (default 20, capped at 100). |
from_date | no | ISO datetime. Only snapshots captured at or after this are scanned. |
to_date | no | ISO datetime. Only snapshots up to and including this are scanned. |
Response 200:
{
"dn": "uni/tn-prod/BD-web",
"saved_query_id": 202,
"snapshot_count": 12,
"points": [
{ "snapshot_id": "…", "executed_at": "…", "present": true,
"attributes": { "name": "web", "scope": "private", … } },
{ "snapshot_id": "…", "executed_at": "…", "present": false, "attributes": {} }
],
"attribute_evolution": [
{
"attribute": "scope",
"change_count": 1,
"is_stable": false,
"distinct_values": ["private", "shared"],
"values": [
{ "executed_at": "…", "snapshot_id": "…", "value": "private", "changed": false },
{ "executed_at": "…", "snapshot_id": "…", "value": "shared", "changed": true }
]
}
],
"tracked_attributes": ["arpFlood", "name", "scope", … ]
}points is in chronological order (oldest first); present: false entries
mean the DN was missing from that snapshot. The frontend Lifecycle bar uses
those gaps to render created / deleted markers.
GET /api/time-machine/saved-queries/<saved_query_id>/dns/
Distinct DNs present in the latest snapshot of a saved query. Powers the autocomplete in the Track DN picker.
Query parameters:
| Param | Notes |
|---|---|
q | Substring filter (case-insensitive). Empty = return everything. |
limit | Max rows returned (default 50, capped at 200). |
Response 200:
{
"dns": [
{ "dn": "uni/tn-prod", "className": "fvTenant" },
{ "dn": "uni/tn-mgmt", "className": "fvTenant" }
],
"count": 2
}This endpoint is intentionally exempt from the global rate-limiter — it bursts naturally as a user types into the autocomplete.
Settings
GET / PATCH /api/time-machine/settings/
Per-user preferences.
Fields:
default_retention_policy— which policy to preselect in capture dialogs.auto_capture_enabled— whether scheduled tasks also create snapshots by default.diff_key_strategy—dn|auto|hash.
Cleanup
GET /api/time-machine/cleanup/preview/
Preview the next cleanup run. Returns counts of snapshots that will be deleted, grouped by policy. Doesn't delete anything.
Response 200:
{
"will_delete": {
"7d": 342,
"30d": 88,
"90d": 12,
"1y": 0,
"forever": 0
},
"total_bytes_reclaimed": 482932841
}POST /api/time-machine/cleanup/execute/
Run cleanup now. Normally scheduled by Celery Beat at 03:30 server time daily — this endpoint is for admin-triggered manual runs.
Response 200: Same shape as preview/, but counts are actual deletions.
Cleanup is destructive — deleted snapshots are gone. Always hit /preview/ first, then /execute/. The action is logged in the audit trail either way.
MIM
Class hierarchy lookups, universal search, favorites, and the MIM registry install flow — endpoints under /api/mim/ and /api/mim-registry/.
Audit and notifications
Read the audit log, export records, manage notification preferences, and query dashboard stats — endpoints under /api/audit/, /api/notifications/, and /api/dashboard/.