Time-travel with snapshots and compare¶
Tutorial · 2 of 5
Every fact in Telha is stamped with three clocks. In this tutorial you record a fact, correct it, and then ask the same question at two different atTxTime coordinates to see how Telha reconstructs what you believed in the past, even after the belief has changed. You finish with /v1/snapshot and /v1/compare.
You will need
- A running server and an API key (quickstart, steps 2-3)
-
curl - Five minutes with the tritemporal model concept page open in another tab, this tutorial mirrors its worked example
Scenario¶
You track a supplier risk and who owns it. On an early date, an out-of-date org chart says the owner is Sarah. Weeks later, a correction arrives: the owner was actually Michael the whole time. You want to ask "who owned this risk?" two ways: as best known today, and as it was believed before the correction landed.
1. Record the initial belief¶
Create a RISK record with an owner property. We will use POST /v1/records (not /v1/ingest) here because we want a single addressable record we can version with PUT.
curl -sS -X POST http://127.0.0.1:7625/v1/records \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d '{
"records": [
{ "labels": ["RISK"],
"properties": { "title": "Cold-chain excursion risk", "severity": 6, "owner": "Sarah" },
"validTime": { "start": 1746057600000000 }
}
]
}'
{
"records": [
{ "id": "d41d...", "labels": ["RISK"],
"properties": { "title": "Cold-chain excursion risk", "severity": 6, "owner": "Sarah" },
"validTime": { "start": 1746057600000000 },
"txTime": { "start": 1749513783000000 },
"tombstone": false, "relationships": [] }
]
}
validTime.start above is 2025-05-01T00:00:00Z in microseconds, this record asserts the risk has been true in the world since May 1. txTime.start is stamped automatically at write time, when Telha recorded the belief (Feb 3, in this scenario, if you set your system clock or just treat it as "now" for the exercise).
Checkpoint
GET /v1/records/$RISK_ID returns the record with owner: "Sarah".
2. Correct the fact¶
A correction arrives later: the owner was actually Michael, effective the same May 1 valid-time start. Write a new full-snapshot version with PUT:
curl -sS -X PUT http://127.0.0.1:7625/v1/records/$RISK_ID \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d '{
"labels": ["RISK"],
"properties": { "title": "Cold-chain excursion risk", "severity": 6, "owner": "Michael" },
"validTime": { "start": 1746057600000000 }
}'
{
"id": "d41d...", "labels": ["RISK"],
"properties": { "title": "Cold-chain excursion risk", "severity": 6, "owner": "Michael" },
"validTime": { "start": 1746057600000000 },
"txTime": { "start": 1752048600000000 },
"tombstone": false
}
Nothing was overwritten. The old version (owner: "Sarah") is still there, reconstructable at its original transaction time, this is the append-only discipline the tritemporal model is built on.
Checkpoint
versions should have two entries: the original (owner: "Sarah") and the correction (owner: "Michael"), newest first. 3. Ask the same question two ways¶
Query the risk's owner as of world-time May 1, with no atTxTime, current belief:
curl -sS -X POST http://127.0.0.1:7625/v1/query \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d '{"find": "RISK", "where": {"title": {"$eq": "Cold-chain excursion risk"}}, "atValidTime": "2026-05-01T00:00:00Z"}'
{ "records": [ { "id": "d41d...", "properties": { "owner": "Michael", "severity": 6 }, "...": "..." } ], "...": "..." }
Now pin atTxTime to a moment before the correction, when only the Sarah belief existed:
curl -sS -X POST http://127.0.0.1:7625/v1/query \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d '{"find": "RISK", "where": {"title": {"$eq": "Cold-chain excursion risk"}}, "atValidTime": "2026-05-01T00:00:00Z", "atTxTime": "2026-06-01T00:00:00Z"}'
{ "records": [ { "id": "d41d...", "properties": { "owner": "Sarah", "severity": 6 }, "...": "..." } ], "...": "..." }
Match the txTime to your own clock
The exact atTxTime cutoff you need depends on when you actually ran step 2 on your machine, pick any RFC3339 timestamp between your POST /v1/records (step 1) and your PUT (step 2) writes. That is the whole trick: atTxTime reconstructs whatever the winning version was as of that recording date, regardless of what has been corrected since.
Read the two responses side by side:
| Query | atValidTime | atTxTime | Owner |
|---|---|---|---|
| "As we know it today" | May 1 | (omitted, = now) | Michael |
| "As we believed it before the correction" | May 1 | before the PUT | Sarah |
This is exactly the worked example in The tritemporal model: a plain last-write-wins store cannot answer the second question at all, the old belief would simply be gone. Telha keeps every version, so "what did we believe before the amendment arrived" stays answerable, forever.
4. Pin a moment with /v1/snapshot¶
/v1/snapshot is query sugar: it pins atValidTime (required) and optionally atTxTime, then re-dispatches through the same executor as /v1/query.
curl -fsS -X POST http://127.0.0.1:7625/v1/snapshot \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d '{"find": "RISK", "atValidTime": "2026-05-01T00:00:00Z"}'
The response shape is identical to /v1/query's, records, related, nextCursor, stats, just with the temporal coordinate baked into the request instead of threaded through every call site.
5. Diff two moments with /v1/compare¶
/v1/compare computes what changed between two bitemporal coordinates: added, removed, and modified, with per-property old and new values.
curl -sS -X POST http://127.0.0.1:7625/v1/compare \
-H "x-api-key: $KEY" -H "Content-Type: application/json" \
-d '{
"baseline": { "validTime": "2026-05-01T00:00:00Z", "txTime": "2026-06-01T00:00:00Z" },
"comparison": { "validTime": "2026-05-01T00:00:00Z" },
"find": "RISK"
}'
{
"added": [],
"removed": [],
"modified": [
{
"kind": "node", "id": "d41d...", "labels": ["RISK"],
"versions": {
"baseline": { "validTimeStart": 1746057600000000, "txTimeStart": 1749513783000000 },
"comparison": { "validTimeStart": 1746057600000000, "txTimeStart": 1752048600000000 }
},
"props": { "added": {}, "removed": {}, "changed": [{ "key": "owner", "old": "Sarah", "new": "Michael" }] },
"labelsAdded": [], "labelsRemoved": [], "touch": false
}
],
"stats": { "added": 0, "removed": 0, "modified": 1, "unchanged": 0 },
"nextCursor": null
}
Checkpoint
modified[0].props.changed contains exactly one entry: owner went from "Sarah" to "Michael". This is the delta report a governance or audit workflow would consume, in one call, without you having to diff two query responses by hand.
What you learned¶
- How to write a fact, then correct it, with
POST /v1/recordsandPUT /v1/records/:id - Why corrections never overwrite: the superseded version stays reconstructable
- How
atValidTimealone answers "what is true, as best we know now" andatValidTime+atTxTimeanswers "what did we believe, as of that recording date, about that world-date" - How
/v1/snapshotpins a temporal coordinate as sugar over/v1/query - How
/v1/comparereports added/removed/modified between two bitemporal coordinates, down to the per-property old and new value
Related¶
- Concepts › The tritemporal model - the three clocks, the winner rule, and this exact worked example
- Query language -
atValidTime/atTxTimegrammar in full - REST API -
/v1/records,/v1/snapshot,/v1/compareendpoint contracts - Ingest and ask a verified question - the previous tutorial
- Hybrid semantic and temporal search - the next tutorial