Revisions and Conflicts
Every persisted scene in zindex has an immutable revision history. Understanding revisions is essential for safe multi-step agent workflows.
For the full agent behavioural guide, fetch the canonical agent front door with
Accept: text/markdownfor the ready-to-paste system prompt.
How revisions work
- Creating a scene produces revision 1.
- Each successful
applyOpscall increments the revision by 1. - Revisions are immutable - earlier revisions are never modified.
- You can retrieve any previous revision with
GET /v1/scenes/{id}?revision=N. - You can list all revisions with
GET /v1/scenes/{id}/revisions.
The baseRevision field
When applying operations, you must include baseRevision in the request. You can also include an optional message describing what this batch does (like a git commit message):
{
"schemaVersion": "0.1",
"sceneId": "my-scene",
"baseRevision": 3,
"ops": [ ... ],
"message": "Added cache layer between API and database"
}
baseRevision declares which revision your ops were planned against. The server checks that baseRevision matches the scene’s current revision before applying.
Conflict detection (409)
If another writer updated the scene between your read and your write, baseRevision won’t match. The server returns 409 Conflict:
{ "error": "Revision conflict", "currentRevision": 5 }
This prevents stale writes from silently overwriting concurrent changes.
Conflict recovery pattern
- Read the latest scene:
GET /v1/scenes/{id} - Compare your intended change to the current state
- Re-plan a fresh minimal patch against the new revision
- Apply with the new
baseRevision
Do not:
- Blindly replay the same ops - the scene state may have changed
- Retry without re-reading - you’ll get 409 again
- Force-overwrite - there is no mechanism for this (by design)
Revision list with metadata
GET /v1/scenes/:id/revisions returns rich metadata per revision:
{
"sceneId": "my-diagram",
"currentRevision": 3,
"revisions": [
{ "revision": 3, "createdAt": "2026-04-01T12:00:00Z", "message": "Added cache layer", "createdByKind": "agent", "createdVia": "mcp", "proposalId": "prop_1c2d...", "approvedByUserId": "user_9f...", "summary": { "added": 1, "removed": 0, "modified": 1 } },
{ "revision": 2, "createdAt": "2026-04-01T11:30:00Z", "message": "Initial structure", "createdByKind": "human", "createdVia": "api", "proposalId": null, "approvedByUserId": null, "summary": { "added": 5, "removed": 0, "modified": 0 } },
{ "revision": 1, "createdAt": "2026-04-01T11:00:00Z", "message": null, "createdByKind": null, "createdVia": null, "proposalId": null, "approvedByUserId": null, "summary": null }
]
}
Each revision includes:
- createdAt - ISO 8601 timestamp
- message - the revision message (from
applyOpsrequest), or null - summary - change counts (added/removed/modified elements) compared to the previous revision. Null for revision 1.
- createdByKind - who authored the change:
agent(API-key caller),human(session caller), orderive(the deterministic schema-sync writer). Null on revisions written before provenance recording landed. - createdVia - the transport the author used:
mcp,api, orcli. Null on pre-provenance revisions. - proposalId - when the revision landed through the proposal review flow, the proposal that produced it; null for direct writes.
- approvedByUserId - the user who approved that proposal; null for direct writes.
- deriveFormat + deriveSourceSha256 - source lineage for derive-created revisions: the snapshot dialect (
prismaorsql) and the SHA-256 of the exact source text the server derived from. Computed by the API at derive time, never accepted from the caller. Null on non-derive revisions. This is how an evidence pack ties a diagram revision back to the schema that produced it.
Point-in-time reads (“as of”)
Because every revision is timestamped, any instant resolves to a revision. GET /v1/scenes/:id?asOf=2026-06-30 returns the scene as it stood at the END of that date (UTC); a full ISO timestamp is used exactly. The same parameter works on the evidence export, and GET /v1/workspaces/audit?asOf= lists every owned scene with its as-of revision - the data behind the dashboard’s Audit tab. A scene that did not exist yet returns 404 with code NO_REVISION_AS_OF. asOf and revision are mutually exclusive.
Revision watermark on rendered output
When rendering a persisted scene, the output includes metadata in the bottom corners:
zindex.ai my-diagram | Rev 5 - 2026-04-01
- Bottom-left:
zindex.aibranding - Bottom-right: scene ID, revision number, and date
The watermark is binary - full metadata or nothing:
- Default ON for persisted renders (
POST /v1/scenes/:id/render, requires API key) - stamps both parts of the watermark - Never shown for stateless renders (
POST /v1/scenes/render, public) - stateless output is always clean, no watermark at all - Suppressible on persisted renders with
"showRevision": false- turns the full watermark off entirely (there is no middle state)
The watermark color adapts to the canvas background:
- Dark backgrounds: subtle light text (
rgba(255,255,255,0.25)) - Light backgrounds: subtle dark text (
rgba(0,0,0,0.2))
The scene ID in the watermark makes every rendered artifact self-identifying. An agent seeing a diagram can read the scene ID and call GET /v1/scenes/:id or dsp_get_scene to retrieve and edit it.
Diffing revisions
Compare any two revisions to see exactly what changed:
GET /v1/scenes/:id/diff?from=1&to=3
The to parameter is optional - defaults to the current (latest) revision.
Response:
{
"sceneId": "my-diagram",
"fromRevision": 1,
"toRevision": 3,
"summary": { "added": 2, "removed": 1, "modified": 1 },
"added": ["node-x", "edge-y"],
"removed": ["node-old"],
"modified": ["node-a"],
"changedConstraintIds": []
}
- added - elements present in
toRevisionbut not infromRevision - removed - elements present in
fromRevisionbut not intoRevision - modified - elements present in both but with different content
- summary - quick counts for each category
Use diff to:
- Show architectural changes in PR reviews
- Build incident timelines (what changed between healthy and broken states)
- Track how a diagram evolved over time
- Verify that an agent’s edits had the intended effect
Visual diff rendering
Render a color-coded diagram showing changes between revisions. Pass diff.fromRevision on the render request:
POST /v1/scenes/my-diagram/render
{ "format": "svg", "diff": { "fromRevision": 1 } }
Added elements render in green, removed as red dashed ghosts, modified in amber, unchanged muted. A color legend is included. See Rendering: Visual diff for details.
Best practices
- Always
GETthe scene before editing it - Always use the revision from the most recent read as
baseRevision - Keep op batches small so conflicts are less likely and easier to re-plan
- Treat 409 as a normal control flow signal, not an error - re-fetch and retry
Retrieving previous revisions
GET /v1/scenes/my-scene?revision=1
Returns the scene as it was at revision 1. Useful for:
- Inspecting what changed between revisions
- Debugging unexpected state
- Showing revision history to users