REST API
Overview
The SirixDB REST API is fully asynchronous, built on Vert.x and Netty. It supports both JSON and XML databases through content negotiation. All mutating operations automatically commit a new revision, making every previous state permanently queryable.
Base URL: https://localhost:9443
Content negotiation: every request must include Content-Type and/or Accept headers set to application/json or application/xml.
Authentication: OAuth2 via Keycloak. Obtain a token from POST /token and include it as Authorization: Bearer <token> on all subsequent requests.
Concurrency control: updates and deletes require an ETag header containing the rolling hash of the context node (obtained via HEAD or GET). If the hash has changed since the read, the server rejects the write to prevent lost updates.
Authentication
POST /token
Obtain or refresh an OAuth2 access token.
Content-Type: application/json or application/x-www-form-urlencoded
Request body (obtain token):
{
"username": "admin",
"password": "admin"
}
Request body (refresh token):
{
"refresh_token": "<refresh_token>"
}
Response:
{
"access_token": "eyJhbG...",
"refresh_token": "eyJhbG...",
"token_type": "Bearer",
"expires_in": 300
}
POST /logout
Revoke the current access and refresh tokens.
Request body: the user principal JSON object returned by the authentication system.
GET /user/authorize
Initiate OAuth2 Authorization Code flow (redirects to Keycloak).
| Parameter | Type | Description |
|---|---|---|
redirect_uri |
string | URI to redirect to after authentication |
state |
string | Opaque state value for CSRF protection |
Databases
GET /
List all databases.
Role: view
| Parameter | Type | Default | Description |
|---|---|---|---|
withResources |
boolean | false | Include the list of resource names per database |
Response:
{
"databases": [
{
"name": "shop",
"type": "json",
"resources": ["products", "customers"]
}
]
}
PUT /:database
Create a database. Optionally include initial resources.
Role: create
| Content-Type | Behavior |
|---|---|
application/json |
Create a JSON database |
application/xml |
Create an XML database |
multipart/form-data |
Create a database with multiple resources (each part specifies its own Content-Type) |
GET /:database
Get database metadata and the list of all resource names.
Role: view
POST /:database
Create multiple resources in an existing database.
Role: create
Content-Type: multipart/form-data — each part must set its own Content-Type (application/json or application/xml).
DELETE /
Delete all databases.
Role: delete
DELETE /:database
Delete a database and all its resources.
Role: delete
Content-Type: must match the database type (application/json or application/xml).
Resources
PUT /:database/:resource
Create a new resource with initial content. If the database does not exist, it is created automatically.
Role: create
| Parameter | Type | Description |
|---|---|---|
commitMessage |
string | Optional commit message for the initial revision |
commitTimestamp |
string | Optional custom timestamp (yyyy-MM-ddTHH:mm:ss or yyyy-MM-dd HH:mm:ss.SSS, UTC). Use only when importing existing versioned data. |
Request body: the JSON or XML document to store.
Example:
curl -X PUT https://localhost:9443/shop/products \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '[{"name":"Laptop","price":999},{"name":"Phone","price":699}]'
Response: the stored document, enriched with SirixDB metadata when withMetadata is used.
GET /:database/:resource
Retrieve a resource or query it with JSONiq.
Role: view
Revision selection
| Parameter | Type | Description |
|---|---|---|
revision |
integer | Retrieve a specific revision by number |
revision-timestamp |
ISO datetime | Retrieve the revision closest to this timestamp (e.g. 2024-01-15T10:30:00) |
start-revision |
integer | Start of a revision range |
end-revision |
integer | End of a revision range (inclusive) |
start-revision-timestamp |
ISO datetime | Start of a timestamp range |
end-revision-timestamp |
ISO datetime | End of a timestamp range |
Node selection and serialization
| Parameter | Type | Description |
|---|---|---|
nodeId |
long | Retrieve a specific node by its stable key |
withMetadata |
boolean | Include nodeKey, hash, and descendantCount for every node |
maxLevel |
integer | Maximum tree depth to serialize (deeper subtrees are skipped) |
prettyPrint |
boolean | Format the output for readability |
Pagination (JSON only)
| Parameter | Type | Description |
|---|---|---|
nextTopLevelNodes |
integer | Return the next N top-level nodes |
lastTopLevelNodeKey |
long | Node key to start pagination from |
Query execution
| Parameter | Type | Description |
|---|---|---|
query |
string | A JSONiq expression (URL-encoded) |
startResultSeqIndex |
integer | Start index in the result sequence (0-based) |
endResultSeqIndex |
integer | End index in the result sequence (inclusive) |
Example — retrieve revision 1:
curl https://localhost:9443/shop/products?revision=1 \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
Example — temporal query (all revisions of the root):
curl "https://localhost:9443/shop/products?query=jn:all-times(jn:doc('shop','products'))" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
HEAD /:database/:resource
Get the ETag (rolling hash) of a node. Use this before POST or DELETE operations.
Role: view
| Parameter | Type | Description |
|---|---|---|
nodeId |
long | Node to get the hash for |
revision |
integer | Specific revision |
Response headers:
| Header | Description |
|---|---|
ETag |
Rolling hash of the node (required for updates) |
POST /:database/:resource
Insert, replace, or modify content within a resource. Each POST creates a new revision.
Role: modify
Required header: ETag — the hash obtained from a prior HEAD request.
| Parameter | Type | Description |
|---|---|---|
nodeId |
long | Context node for the operation |
insert |
string | Insertion mode: asFirstChild, asLeftSibling, asRightSibling, or replace |
commitMessage |
string | Optional commit message |
commitTimestamp |
string | Optional custom timestamp (UTC) |
If both nodeId and insert are omitted, the root node is replaced entirely.
Example — insert a new product as the last child:
# 1. Get the ETag of the array node
ETAG=$(curl -sI "https://localhost:9443/shop/products?nodeId=1" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | grep -i etag | tr -d '\r' | cut -d' ' -f2)
# 2. Insert a new element
curl -X POST "https://localhost:9443/shop/products?nodeId=1&insert=asFirstChild" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "ETag: $ETAG" \
-d '{"name":"Tablet","price":449}'
DELETE /:database/:resource
Delete a resource or a subtree within it.
Role: delete
Required header: ETag — the hash obtained from a prior HEAD request.
| Parameter | Type | Description |
|---|---|---|
nodeId |
long | Node to delete. If it is a structure node, the entire subtree is removed. If omitted, the whole resource is deleted. |
Queries
POST /
Execute a JSONiq expression. Use this for longer or multi-line queries that are inconvenient to URL-encode.
Role: view
Content-Type: application/json
Request body:
{
"query": "for $rev in jn:all-times(jn:doc('shop','products')) return {\"rev\": sdb:revision($rev), \"count\": count($rev[])}",
"startResultSeqIndex": 0,
"endResultSeqIndex": 9
}
| Field | Type | Description |
|---|---|---|
query |
string | JSONiq expression |
startResultSeqIndex |
integer | Start index in result sequence (0-based) |
endResultSeqIndex |
integer | End index in result sequence (inclusive) |
History and Diffs
GET /:database/:resource/history
Get the revision history of a resource.
Role: view
Produces: application/json
| Parameter | Type | Description |
|---|---|---|
revisions |
integer | Return only the last N revisions |
startRevision |
integer | Start of revision range |
endRevision |
integer | End of revision range |
If no parameters are given, the full history is returned.
Response:
{
"history": [
{
"revision": 1,
"revisionTimestamp": "2024-01-15T10:30:00.000Z",
"author": "admin",
"commitMessage": "initial import"
},
{
"revision": 2,
"revisionTimestamp": "2024-01-15T11:00:00.000Z",
"author": "admin",
"commitMessage": "price update"
}
]
}
GET /:database/:resource/diff
Compute the diff between two revisions (JSON resources only).
Role: view
Produces: application/json
| Parameter | Type | Required | Description |
|---|---|---|---|
first-revision |
integer | yes | The base revision |
second-revision |
integer | yes | The revision to compare against |
startNodeKey |
long | no | Restrict the diff to a subtree rooted at this node |
maxDepth |
long | no | Maximum depth to traverse |
include-data |
boolean | no | Include full subtree data for inserts (default: false) |
Response:
{
"database": "shop",
"resource": "products",
"old-revision": 1,
"new-revision": 2,
"diffs": [
{"type": "update", "nodeKey": 6, "value": 899},
{"type": "insert", "nodeKey": 10, "insertPosition": "asRightSibling", "data": "{\"name\":\"Tablet\",\"price\":449}"}
]
}
GET /:database/:resource/pathSummary
Get the path summary — a compact overview of all unique paths in the resource.
Role: view
Produces: application/json
| Parameter | Type | Description |
|---|---|---|
revision |
integer | Specific revision (default: latest) |
Response:
{
"pathSummary": [
{"nodeKey": 1, "path": "/[]", "references": 1, "level": 1},
{"nodeKey": 2, "path": "/{}", "references": 2, "level": 2},
{"nodeKey": 3, "path": "/name", "references": 2, "level": 3},
{"nodeKey": 4, "path": "/price", "references": 2, "level": 3}
]
}
Temporal Navigation
JSON: temporal functions
For JSON resources, use the jn: temporal functions in your JSONiq queries. Each takes a JSON item and returns it from other revisions:
| Function | Description |
|---|---|
jn:all-times($item) |
The item from every revision where it exists |
jn:first($item) |
The item in the first revision |
jn:last($item) |
The item in the most recent revision |
jn:future($item) |
All future revisions (excluding current) |
jn:future($item, true()) |
All future revisions (including current) |
jn:past($item) |
All past revisions (excluding current) |
jn:past($item, true()) |
All past revisions (including current) |
jn:previous($item) |
The immediately preceding revision |
jn:next($item) |
The immediately following revision |
jn:first-existing($item) |
The first revision where the item existed (was created) |
jn:last-existing($item) |
The last revision where the item existed (before deletion) |
Additionally, sdb:item-history($item) returns the item from every revision where it was inserted or modified.
Example — track how a value changed across revisions:
let $item := sdb:select-item(jn:doc('shop','products'), 6)
for $v in sdb:item-history($item)
return {"rev": sdb:revision($v), "price": $v}
Example — count products in every revision:
for $v in jn:all-times(jn:doc('shop','products'))
return {"rev": sdb:revision($v), "count": count($v[])}
XML: temporal XPath axes
For XML resources, SirixDB provides custom XPath axes with the same semantics:
all-time:: first:: last:: future:: future-or-self:: past:: past-or-self:: previous:: previous-or-self:: next:: next-or-self::
See the JSONiq Function Reference for the full list of temporal and inspection functions.
Error Codes
| Status | Meaning |
|---|---|
200 |
Success |
400 |
Bad request (malformed body, missing required parameters) |
401 |
Unauthorized (missing or invalid token) |
403 |
Forbidden (insufficient role) |
404 |
Database, resource, or node not found |
409 |
Conflict (ETag mismatch — concurrent modification detected) |
Roles
Access is controlled by Keycloak roles assigned to users or groups:
| Role | Grants |
|---|---|
create |
Create databases and resources |
view |
Read and query resources |
modify |
Update resources |
delete |
Delete databases, resources, or nodes |
Roles can be scoped to a specific database by prefixing: shop-create, shop-view, etc.
Quick Setup
# Start SirixDB + Keycloak
git clone https://github.com/sirixdb/sirix.git
cd sirix
docker-compose up
# Get a token
TOKEN=$(curl -s -X POST https://localhost:9443/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}' | jq -r .access_token)
# Store a JSON document
curl -X PUT https://localhost:9443/shop/products \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '[{"name":"Laptop","price":999},{"name":"Phone","price":699}]'
# Query it
curl "https://localhost:9443/shop/products" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"