FabrikFabrik
FabrikAPI Reference

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:

ParamRequiredNotes
saved_query_idyesThe saved query whose snapshots you're scanning.
dnyesThe exact DN string (e.g. uni/tn-prod/BD-web).
limitnoMax snapshots to consider (default 20, capped at 100).
from_datenoISO datetime. Only snapshots captured at or after this are scanned.
to_datenoISO 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:

ParamNotes
qSubstring filter (case-insensitive). Empty = return everything.
limitMax 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_strategydn | 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.