The immutable, bitemporal time-travel database · open source

Every change kept. Every version queryable.

Query any past state of your JSON and XML documents — as it was recorded, or as it was true — see exactly which fields changed, and let AI agents edit real data you can always undo. Every change is an immutable revision. Open source and embeddable.

Five revisions stacked like the layers of the Sirix logo Revision 1 writes every page of the grid. Each later revision writes only one changed fragment, shown in orange, plus the few index pages on its path to the new revision root, and shares the other pages, shown as dashed outlines. A query then opens revision 2 directly. jn:doc('shop','products', 2) opens revision 2 directly · no replay
Watch

SirixDB in 90 seconds.

Every revision kept and queryable, field-level identity and diffs, both kinds of time and versioned indexes — with real sirix-shell output. Captions are available from the player's CC control.

A 90-second tour; nothing plays until you press play. Download the MP4 or the captions.
See it in action

Four commits. Then travel back in time.

A real sirix-shell session: build a catalog in four revisions, then read the past, follow one field through history and diff two revisions. The output is what SirixDB 1.0.0-beta7 printed.

sirix-shell
(: Revision 1: store a product catalog :)
jn:store('shop','products','[{"name":"Laptop","price":999},{"name":"Phone","price":699}]')
Query result
(: Revision 2: the laptop gets cheaper :)
let $doc := jn:doc('shop','products')
return replace json value of $doc[0].price with 899
Query result
(: Revision 3: add a product :)
let $doc := jn:doc('shop','products')
return append json {"name":"Tablet","price":449} into $doc
Query result
(: Revision 4: discontinue the phone :)
let $doc := jn:doc('shop','products')
return delete json $doc[1]
Query result
(: Time travel: the catalog as it was in revision 1 :)
jn:doc('shop','products', 1)
Query result
[{"name": "Laptop", "price": 999}, {"name": "Phone", "price": 699}]
(: How did the laptop's price evolve? :)
let $price := jn:doc('shop','products')[0].price
for $v in sdb:item-history($price)
return {"rev": sdb:revision($v), "price": $v}
Query result
{"rev": 1, "price": 999}
{"rev": 2, "price": 899}
(: How many products did each revision have? :)
for $v in jn:all-times(jn:doc('shop','products'))
return {"rev": sdb:revision($v), "products": count($v[])}
Query result
{"rev": 1, "products": 2}
{"rev": 2, "products": 2}
{"rev": 3, "products": 3}
{"rev": 4, "products": 2}
(: What changed between revision 1 and revision 4? :)
jn:diff('shop','products', 1, 4)
Query result
{
  "database": "shop",
  "resource": "products",
  "old-revision": 1,
  "new-revision": 4,
  "diffs": [
    {
      "update": {
        "nodeKey": 4,
        "path": "/[0]/price",
        "type": "number",
        "value": 899
      }
    },
    {
      "insert": {
        "nodeKey": 8,
        "insertPositionNodeKey": 2,
        "insertPosition": "asRightSibling",
        "path": "/[1]",
        "type": "jsonFragment",
        "data": "{\"name\":\"Tablet\",\"price\":449}"
      }
    },
    {
      "delete": {
        "nodeKey": 5,
        "path": "/[1]"
      }
    }
  ]
}

Multi-line queries run on an empty line in sirix-shell. JSON results are re-indented for reading; values are unchanged.

See it in the browser

Explore every revision visually.

The web interface ships with open-source SirixDB: browse documents as trees, step through revision history, compare any two revisions and see the shape of your data.

~18 µsto open and read revision 1, 5,000 or 10,000 (warm)
~570 commits/sdurable commits, flat at 10,000 revisions deep (small document)
0.2–0.3 msnode-level diff between two revisions (warm)

Core-API numbers from BENCHMARKS.md (10,000-commit history, one machine, small document) and the README. Time-travel windows elsewhere: BigQuery 2–7 d (7 d default); Snowflake 1 d default, up to 90 d for permanent objects on Enterprise or higher. Lakehouse queries depend on retained snapshots, logs and files and engine-enforced query retention limits; configuration and defaults vary. See Iceberg maintenance and Databricks time-travel limits.

The problem

An update should not erase the past.

When history isn't built in, teams add audit tables, change feeds or event logs to preserve it alongside current state.

History tables & triggers

History you maintain

Application-managed history needs capture logic and an as-of query model. Extensions can automate parts of this work.

PostgreSQL temporal tables · periods extension

Event sourcing

Events need a state model

Past state comes from replay, often accelerated with snapshots, or from maintained projections.

Fowler · Event Sourcing

Warehouse time travel

Know your retention policy

Lakehouse time travel needs retained snapshots, logs and files, and must also satisfy engine-enforced query retention limits. Cleanup is not the only boundary; settings and defaults vary.

Iceberg maintenance · Databricks query limits

Retention limits: Snowflake, BigQuery, Databricks Delta.

What makes Sirix different

History is the data model, not a feature.

Six properties of Sirix's approach to history: immutable revisions, shared unchanged data, and queries that can open the past directly.

At a glance: evolution of state across four revisions Revision 1 writes all five pages. Each edit creates a new revision that writes only its changed page fragments, shown in orange, plus the few index pages on the path up to its new root, marked as an orange tab on each snapshot, and points to the unchanged pages of the previous revision, shown in light blue with dashed pointers.
At a glance · Every edit is a new snapshot: it writes only what changed, plus the short path of index pages up to its new root (the orange tab), and shares everything else. Everything below follows from this.
01 · Time travel

Any revision reads like the latest.

Open the past by revision number or timestamp, and query it with the full language — directly, not by replaying a log.

Every commit is an immutable revision with its own root. Opening one is O(1) by number and O(log R) by timestamp; each page read combines at most N fragments, where N is a configurable window, so revision 1 costs the same as revision 10,000.

JSONiqtime travel
(: the catalog exactly as it was at revision 1 :)
jn:doc('shop','products', 1)

(: or as it was at 3 a.m. last Tuesday :)
jn:open('shop','products', xs:dateTime('2026-11-10T03:00:00Z'))

Source: README · Page Versioning Strategies, BENCHMARKS.md · Benchmark 2

jn:doc('shop','products', 4)latest revision
changed in this revisioninserted in this revisiondeleted in this revision (no longer in the document)
Diagram 1 · Drag or tap a revision. It plays forward on its own, then jumps back — the old state is answered from its own pages, never reconstructed by replay.
02 · Complete history

Keep everything. Pay only for what changed.

A new revision shares every unchanged page with the one before. By default it writes only the changed records, plus their path to the root.

Versioning goes below the page: a commit writes a page fragment for each changed data page. Because the page tree is copy-on-write, it also writes new copies of the small index pages on the path from each changed leaf up to a new revision root — a handful of pages, logarithmic in the data size.

What a fragment holds is a per-resource choice of versioning type, trading write cost against read cost:

Versioning types and what each writes per changed page
TypeA changed page writesFragments per read
Sliding snapshot defaultthe changed records, plus any record about to fall out of the N-fragment window (N configurable per resource)≤ N
Incrementalthe changed records; a full page every N revisions≤ N
Differentialevery record changed since the last full page2
Fullthe whole page1

With the default, storage per revision follows the change, not the document, and reads stay bounded without the write spikes of periodic full snapshots.

Don't need everything forever? SirixDB Enterprise adds retention policies — keep the last N revisions, everything newer than a date, or a chosen set — and compaction that physically reclaims the space (in development).

Javaconfigure per resource
ResourceConfiguration.newBuilder("orders")
    .versioningApproach(VersioningType.SLIDING_SNAPSHOT) // default
    .build();

Source: README · The Solution, Page Versioning Strategies, WHY-SIRIX §3

Three revisions sharing pages Revision 1 writes four full pages. Revision 2 changes one record in page 2 and writes only a small fragment holding that record, plus the index pages on its path to the new revision root; pages 1, 3 and 4 are shared with revision 1. Revision 3 changes one record in page 4 and writes only that fragment; the rest is shared. The bars on the right show bytes written per revision: one large bar, then two tiny ones. page 1 page 2 page 3 page 4 bytes written Page 2 at rev 3: 2 fragments (at most N, configurable)
page writtenchanged record → new fragmentshared, not rewrittenindex pages on the leaf-to-root path
Diagram 2 · Shown for the default sliding snapshot, simplified: real pages hold many records, and a fragment can also carry records rescued from the edge of the window. The orange stubs above each fragment are the index pages on its leaf-to-root path, rewritten by copy-on-write. The proportion is the point — one changed record costs a fragment plus that short path, not a copy of the page or the document.
03 · Node-level identity

Every field has an identity. Every diff is exact.

Each JSON object, array, field and value keeps a stable key across revisions, so Sirix can tell you exactly which nodes changed.

Diffs compare trees, not text: the result is a list of node-level inserts, updates and deletes. With hashes enabled, unchanged subtrees are skipped. The same identity lets you follow one field through its whole history with jn:all-times, jn:previous and jn:next.

RESTdiff two revisions
curl "localhost:9443/shop/products/diff?first-revision=1&second-revision=4"

Source: README · Diff, OpenAPI · Diff schema, WHY-SIRIX §2

Node-level diff between revision 1 and revision 4 Two trees side by side. Node 4, the laptop price, exists in both revisions and changes from 999 to 899: an update. The tablet object, node 8, is new in revision 4: an insert. The phone object, node 5, exists only in revision 1: a delete. Dotted lines connect the same node key across revisions. rev 1Mon rev 4Thu
Response (abridged, real run)/diff
{"update": {"nodeKey": 4, "path": "/[0]/price", "type": "number", "value": 899}}{"insert": {"nodeKey": 8, "path": "/[1]", "type": "jsonFragment", …}}{"delete": {"nodeKey": 5, "path": "/[1]"}}
Diagram 3 · Node keys and the response are from a real run of this catalog (abridged). The dotted lines are the point: node #4 is the same node in both revisions.
04 · Native valid time

What did you know — and when was it true?

Two time axes: transaction time (when Sirix recorded it, every commit) and valid time (when it was true in the world, from your documents).

Alice moves from Berlin to Munich on Mar 1, but only tells you on Mar 10. You record the move on Mar 10, effective Mar 1. Ask what your records said on Mar 5 and Sirix answers Berlin — that is what you knew when you mailed her invoice. Ask today and it answers Munich for Mar 5, and still Berlin for Feb 20.

JSONiqboth axes at once
(: recorded Mar 5, valid Mar 5 → Berlin :)
jn:open-bitemporal('crm','customers',
  xs:dateTime('2026-03-05T00:00:00Z'),  (: transaction time :)
  xs:dateTime('2026-03-05T00:00:00Z'))  (: valid time       :)
(: recorded today, valid Mar 5  → Munich :)
(: recorded today, valid Feb 20 → Berlin :)

Valid time is two fields you name per resource (validFrom/validTo), indexed by a versioned interval index. Recording the move is one ordinary commit: it ends the Berlin record at Mar 1 and adds Munich from Mar 1. Sirix has no FOR PORTION OF-style automatic split yet.

Source: README · Bitemporal, Bitemporal Queries (half-open validity in jn:open-bitemporal)

Bitemporal plane: transaction time across, valid time up Until Mar 10 in transaction time, the database records Alice in Berlin for all valid time: one blue region. From Mar 10 on, after the late-reported move, it records Berlin for valid time before Mar 1, blue, and Munich from Mar 1, orange. A crosshair moves from recorded Mar 5 and valid Mar 5, answer Berlin, to recorded Mar 15 and valid Mar 5, answer Munich, then down to valid Feb 20, answer Berlin. transaction time — when Sirix recorded it → valid time → Revision 1: Berlin for all valid time Berlin rev 1 · move not reported yet Revision 2: Munich from Mar 1 Munich rev 2 · from Mar 1 Revision 2: Berlin until Mar 1 Berlin rev 2 · until Mar 1 Mar 5 Mar 5
Recorded on Mar 5, valid Mar 5:Berlinrev 1 · the move was not reported yet
Diagram 4 · The vertical line is when the database is asked (transaction time); the horizontal line is the date the question is about (valid time). Moving right past Mar 10 changes the answer for dates after Mar 1 only; moving down below Mar 1 brings back Berlin.
05 · Embeddable, with analytics built in

A library first. A server when you want one.

Add one dependency and you have a versioned database in a directory — or run the same engine as a REST server, CLI, MCP server for AI agents, or GraalVM native binary.

Every index is versioned too: path, value (CAS), name, valid-time and columnar projection indexes all open "as of" any revision. Projection indexes feed SIMD kernels for group-bys and aggregates over a past state — and fall back to the regular pipeline whenever they can't answer exactly.

Gradleembed
implementation("io.sirix:sirix-core:1.0.0-beta7")

Source: README · Embedded, Indexes, Projection indexes (experimental)

One engine, several ways to run it Inside your JVM process, your code calls sirix-core, which writes append-only files in a directory. The same core also ships as a REST server, a CLI, an MCP server for AI agents and a native binary. your JVM process your application Java · Kotlin sirix-core versioned store · JSONiq · indexes /data/mydb/ append-only files or run the same core as REST serverDocker · fat JAR CLI & JSONiq shellsirix-cli MCP serverfor AI agents Native binaryGraalVM

query any revision through the same core

Single process, no sidecar. Single writer per resource with snapshot-isolated readers.
06 · Immutable revisions · AI agents

Let AI agents edit real data — and undo them.

Every revision is immutable. An agent's edit is just a new revision you can inspect, diff and revert — and the bad state stays in history as evidence.

The built-in MCP server lets agents (Claude, Cursor or any MCP client) read any revision, inspect structural diffs and optionally revert. It is read-only by default, with access control, output sanitization and an audit log. A revert appends a new revision that restores the whole resource, guarded by the head you expect: if someone committed in between, it returns 409 and changes nothing.

Shellregister with your MCP client
# read-only by default
sirix-mcp --database-path /data/sirix
# allow edits (and reverts)
sirix-mcp --database-path /data/sirix --read-write

Source: README · MCP server, AGENT_QUICKSTART, QUICKSTART · revert

An agent edit, inspected and reverted Revision 41 is the current state. An agent edits through the MCP server, which creates revision 42 and leaves revision 41 untouched. A structural diff from 41 to 42 shows one update, one insert and one delete. A revert to 41 is appended as revision 43 with the content of revision 41, while revision 42 stays in history.
Diagram 6 · Nothing is overwritten at any step: the agent's revision 42 remains readable and diffable after the revert.
What you can do

Ask questions about the past in one line.

JSONiq with temporal functions, a REST API, and the same operations from Java or the CLI. Every example below uses functions documented in the Sirix README.

Read a resource as it was — over REST, or inside any query.

REST
curl 'localhost:9443/mydb/products?revision=1'
# -> [{"id":1,"name":"keyboard","price":49.9},{"id":2,"name":"mouse","price":19.9}]
JSONiq
count(jn:doc('mydb','products', 1)[])   (: -> 2 :)
count(jn:doc('mydb','products')[])      (: -> 3, the latest :)
Measured, not claimed

History depth doesn't slow it down.

Numbers from Sirix's own published benchmarks, with their methodology and their losses. Single machine, so treat them as shape, not as a promise for your workload.

~18 µs

Open and read revision 1, 5,000 or 10,000 of a 10,000-revision resource — flat across the history.

BENCHMARKS · Bench 2

0.18 ms

Session open at 10,000 revisions, flat (was 4.64 ms and linear before the fix).

BENCHMARKS · post-fix

~570 commits/s

Durable single-field commits, flat at 10,000 revisions deep. Tiny document, core API.

BENCHMARKS · post-fix

18,361 req/s

REST reads at 16 concurrent clients on a 1,901-revision resource; p99 1.84 ms.

BENCHMARKS · REST re-run

15–37 s → 1.4–3.2 s

Group-bys, sums, averages and count-distinct over 1M records: interpreted pipeline vs vectorized SIMD execution, cold. Results verified byte-identical.

WHY-SIRIX §5

3 of 9

Analytical query shapes where the profile-guided native binary is ahead of DuckDB 1.5.2 at 100M records; the rest within 1.1–4.5×. Minimum of three timed runs after warmup, with the projection already built; build cost excluded.

COMPARISON_DUCKDB · setup and results

Where it loses, today

  • Keeping history consumes additional disk space; measure storage and peak memory on your workload.
  • Single node, single writer per resource; no replication yet.
  • No SQL — JSONiq/XQuery only. Still 1.0.0-beta.

See KNOWN_LIMITATIONS.md and WHY-SIRIX · where Sirix is the wrong choice. The PostgreSQL comparison is withdrawn as unsound and quoted nowhere on this page.

Honest comparison

What Sirix does that the alternatives don't — and the reverse.

Checked against each project's own documentation in November 2026. Rows marked “Sirix behind” are real gaps.

Capability comparison of SirixDB, XTDB 2, Datomic, Dolt, PostgreSQL 18/19 and lakehouse table formats, with sources
CapabilitySirixDBXTDB 2DatomicDoltPostgreSQL 18/19Iceberg / DeltaSources
Transaction-time history Yes, every commit; no automatic age cutoffYes, system-time history; subject to ERASEYes, as-of / since; noHistory and excision can remove historyYes, explicit, retained commitsNo native system-time history; triggers / extensionsSnapshots; retained metadata / files and engine query limits SirixXTDB timeXTDB ERASEDatomic filtersDatomic noHistoryDoltPGIcebergDatabricks
Native valid time Yes, configured paths + versioned interval indexYes, bitemporal tablesApplication-modeled effective datesCommit history; effective dates are application-modeledRange / period constraints; history via application / extensionsApplication / engine-modeled effective dates SirixXTDBDatomic modelDolt historyPG temporal tablesIceberg modelDelta model
Identity & diff granularity Node — every field and value inside the JSONRow / document, identified by _idDatom (entity / attribute / value fact)Row / cell diffs; three-way mergeApplication / extension-defined row historyRow-level changes via Delta change data feed or Iceberg changelog views (engine / configuration dependent) SirixXTDBDatomicDoltPG extension exampleDelta change data feedIceberg changelogs
Storage per revision Page fragments + root paths (versioning type dependent)Versioned rows in columnar blocksAssertions / retractions (datoms) + indexesChanged Prolly-tree chunks with structural sharingApplication / extension-defined history storageData files + metadata; Delta deletion vectors and Iceberg delete files can avoid data-file rewrites for row deletes (engine / configuration dependent) Sirix versioningXTDB storageDatomic modelDolt storagePG extension exampleDelta deletion vectorsIceberg row deletes
Embeddable in your process Yes, JVM library; also RESTJVM node API; also server deploymentPeer library + transactor (Pro); Local supports embedded applicationsGo embedded driver; also CLI / SQL serverClient/server architectureLibraries / engine integrations over table formats SirixXTDB node APIDatomic ProDatomic LocalDolt embedded driverPG architectureIceberg libraryDelta Kernel
Licence BSD-3-ClauseMPL-2.0Apache-2.0 binaries; source not publishedApache-2.0PostgreSQLApache-2.0 (Iceberg and Delta projects) Sirix licenceXTDB licenceDatomic binariesDolt licencePG licenceIceberg licenceDelta licence
SQL access No — JSONiq / XQueryYes, PostgreSQL wire protocolDatalog; SQL analytics via Trino connectorYes, MySQL-compatible DoltYesYes, through compatible query engines SirixXTDB SQLDatomic AnalyticsDolt SQLPG SQLIceberg SQLDelta SQL
History removal No prune / erase command yet
Enterprise (in dev.): prune whole revisions + compaction
ERASE, for all valid and system timeExcision (Pro)filter-branch, remove old references, then gc --fullDelete application history; storage reclamation is separateDelete, purge/rewrite soft-deleted files, then expire snapshots / VACUUM; see removal details SirixXTDB ERASEDatomic excisionDolt filter-branchDolt full GCPG VACUUMDelta physical deletionIceberg cleanup
Branch & merge No — linear history + revertNo native Git-style merge workflowNo native Git-style merge workflowYes, version-control branches and mergesNo native Git-style merge workflowIceberg branches + fast-forward; engine / catalog dependent Sirix historyXTDB database modelDatomic database modelDolt branches / mergePG architectureIceberg branches
Scale-out / replicas Single node
Enterprise (in dev.): S3 + Kafka read replicas
One leader per database + followers (2.2+)Pro: transactor + peers; Cloud: compute groupsRead replicas and standby replicationReplication and read standbysCompute and replicas depend on engine / catalog SirixXTDB nodesDatomic ProDatomic CloudDolt replicationPG replicationIceberg integrationsDelta engines

Scroll sideways on small screens. Sources describe the named projects; extension behavior and engine integrations are identified separately. PostgreSQL 19 links refer to development documentation. The Dolt column covers MySQL-compatible Dolt; Doltgres is a separate product. Native valid time means database-managed effective intervals, not merely storing date columns. Branch/merge means a built-in Git-style workflow, not a snapshot, clone or speculative transaction alone.

Queryable history: retaining files is necessary but may not be sufficient. Databricks Runtime 18+ blocks versions older than the configured deleted-file retention threshold (7 days by default), even if files remain; Unity Catalog managed tables enforce this on earlier runtimes too. Iceberg snapshot expiration and branch/tag retention govern which snapshots remain available.

Removal: logical deletion alone is not physical erasure. Dolt history filtering must cover the relevant references, followed by full garbage collection (gc --full), including old-generation data. For deletion-vector data, Delta documents REORG TABLE ... APPLY (PURGE), then VACUUM. Iceberg delete files can leave rows in existing data files, so those files must be rewritten and become unreferenced before cleanup removes them. These cells describe database mechanisms; independent backups and copies need separate handling.

Open core

Embedded today. Retention and scale-out when you need them.

The open-source engine is single-node by design. SirixDB Enterprise, a commercial edition in development, adds what the comparison above says is missing, using storage providers that plug into the same engine and on-disk model.

  • Retention policies and compactionin developmentDecide how long history lives: keep the last N revisions, everything newer than a date, or a chosen set. Compaction then physically reclaims the space of pruned revisions; each keeps its number and timestamp as a tombstone, and opening it is refused.
  • Web consolein developmentA dashboard across your databases plus audit reports: a cross-resource activity timeline with author filtering, and compliance exports of the audit trail.
  • Object storage (S3 and compatibles)in developmentEach commit becomes an immutable segment object plus a small manifest, read through an in-memory page cache. Immutable revisions are a natural fit for object storage.
  • Read replicas over Kafkain developmentA leader publishes one small event per commit; followers reload the leader's manifest from shared storage and serve the same revisions. Still one writer per resource.
Leader and follower replication through S3 and Kafka The leader commits a revision and uploads an immutable segment and the manifest to a shared S3 bucket. It then publishes a small commit event to a Kafka topic. The follower consumes the event in order, reloads the manifest from S3, and serves the new revision. Leader one writer Follower read replica Shared S3 bucket segments/segment-<id>.dat manifest (latest revision) Kafka topic 1 event / commit rev 41 rev 41
steady state: leader and follower both at rev 41
Enterprise, in development · Kafka carries notifications only; the page data lives once, in object storage. Followers apply events in order and idempotently.
Who needs this

When "what did it say then?" is a requirement.

The requirement is often written into regulation. Sirix gives you the mechanism; it does not certify compliance.

Regulated record-keeping

Broker-dealer rules accept a system that permits the recreation of an original record if it is modified or deleted; MiFID II wants the contents of the records prior to such corrections.

Sirixevery revision kept and openable by time.

SEC 17a-4 · MiFID II Art. 72

Insurance & effective-dated policies

Policies change retroactively; out-of-sequence endorsements must not destroy what was believed when a claim was priced.

Sirixvalid time + transaction time in one query.

Guidewire: out-of-sequence

Trading data & backtests

Corrections arrive days later. Restated data leaks the future into backtests unless you can read the uncorrected past.

Sirixjn:open-bitemporal as of the decision time.

OneTick · Reladomo

Healthcare & life-science records

FDA Part 11: changes shall not obscure previously recorded information. FHIR resources support JSON and XML; server-side history capabilities vary.

Sirixdocument-shaped history, node-level diffs.

21 CFR 11.10 · FHIR R4

AI agents that edit real data

Agents need to change things safely: inspect what they did, diff it, roll it back — with an audit trail.

SirixMCP server, structural diff, guarded revert.

Sirix MCP · LangGraph time travel

ML features & AI-Act lineage

Feature retrieval should use values available at the decision time; later corrections can otherwise leak into training data.

Sirixas-of reads of any feature document.

Feast · EU AI Act Art. 12

Config, content & catalogs

Content-history retention varies by product and plan; config mistakes need "what changed, and when?"

Sirixunbounded history, field-level history queries.

Sanity · Notion

Debugging production state

Bisect data the way you bisect code: what did the state tree look like when the bug fired?

Sirixopen by timestamp, diff neighbours.

WHY-SIRIX · use cases

Event-sourced systems

Keep the log, but query state at any point without replay or hand-built projections.

Sirixeach event a commit; any state one open away.

Fowler · Kiehl

Getting started

From zero to time travel in three commands.

A disposable local server with authentication off, for evaluation on Linux x86-64. Production setup with OAuth2 is in the operations guide.

Start the server

Pinned image, loopback only, data removed on exit.

Shell
docker run --rm --name sirix-first-run -p 127.0.0.1:9443:9443 \
  -e SIRIX_AUTH_MODE=none \
  sirixdb/sirix@sha256:76cb3ec3f8c1efa2d62b526962ff5fed1f7b3f7436bb34db4685abc7320ad33a

Write revision 1

Any JSON document becomes a versioned resource.

Shell
curl -X PUT localhost:9443/first-run/message \
  -H 'Content-Type: application/json' \
  -d '{"message":"hello"}'

Change it, then go back

An update query commits revision 2; revision 1 stays readable.

Shell
curl -X POST localhost:9443/ \
  -H 'Content-Type: application/json' -d "{\"query\":\"replace json value of jn:doc('first-run','message').message with 'hi'\"}"
curl 'localhost:9443/first-run/message?revision=1'
# -> {"message":"hello"}

Embed it

io.sirix:sirix-core:1.0.0-beta7 on JDK 25. A runnable example commits two revisions and reads the first back.

CLI & JSONiq shell

sirix-cli and sirix-shell, as JARs or instant-start native binaries.

Live demo

Browse revisions, run queries and see structured diffs at demo.sirix.io.

Your data has a history.
Make it queryable.

Open source under BSD-3. Built in the open; feedback from real use is exactly what we're looking for.

1,200+ GitHub stars59,000+ Docker pulls23 contributorsBSD-3 licensed