Saga Transactions
Write to multiple agents atomically. SapixDB executes each step in sequence and automatically compensates all applied steps if anything fails — no two-phase commit coordinator, no distributed locks.
saga feature flag. This is available on the Enterprise plan. Community builds return 404 on saga routes.State machine
A saga moves through these states exactly once:
Pending → Executing → Committed (all steps applied)
→ Compensating → Compensated (all applied steps rolled back)
→ Failed (compensation itself failed)| State | HTTP status | Meaning |
|---|---|---|
pending | — | Created, execution not yet started |
executing | — | Steps are being applied one by one |
committed | 201 Created | All steps applied — the happy path |
compensating | — | A step failed; rolling back applied steps |
compensated | 207 Multi-Status | All applied steps successfully reversed |
failed | 207 Multi-Status | Compensation itself failed for at least one step |
API
Create and execute a saga
A single POST creates and immediately executes the saga. The request blocks until all steps commit or all applied steps are compensated.
POST /v1/saga
Authorization: Bearer <key>
Content-Type: application/json
{
"steps": [
{
"target_agent_url": "http://analytics:7475",
"payload_b64": "<base64-msgpack>",
"flags": 0
},
{
"target_agent_url": "http://billing:7475",
"payload_b64": "<base64-msgpack>",
"flags": 0
}
]
}| Field | Type | Description |
|---|---|---|
target_agent_url | string | HTTP base URL of the target SapixDB instance |
payload_b64 | string | Base64-encoded MessagePack record payload |
flags | u8 | BlockFlags bitmask — 0 = plain data, 2 = TOMBSTONE |
Response — 201 Created on full commit, 207 Multi-Status on compensation or failure:
{
"id": "a1b2c3d4e5f6a7b8",
"coordinator_agent_id": "orders",
"state": "committed",
"created_at_ms": 1754478000000,
"completed_at_ms": 1754478000042,
"steps": [
{
"step_id": "step_1",
"target_agent_url": "http://analytics:7475",
"payload_b64": "...",
"flags": 0,
"status": "applied",
"record_hash": "a3f9c2e1...",
"compensation_hash": null,
"error": null
}
]
}Step status values
| Status | Meaning |
|---|---|
pending | Not yet attempted |
applied | Record written to target agent |
compensated | TOMBSTONE written to target agent (step rolled back) |
failed | Apply or compensation failed — check the error field |
List all sagas
GET /v1/saga
Authorization: Bearer <key>
// Response
{
"sagas": [ ... ],
"total": 5
}Returns newest-first.
Get one saga
GET /v1/saga/a1b2c3d4e5f6a7b8 Authorization: Bearer <key>
Returns a single SagaTransaction object. Returns 404 if not found.
How compensation works
When a step fails, SapixDB iterates all applied steps in reverse order and writes a TOMBSTONE record (flags: 2) to each one. The compensation payload identifies the original record being reversed:
{
"_saga_compensation": true,
"saga_id": "a1b2c3d4e5f6a7b8",
"compensating_for": "<original_record_hash>"
}Steps that were never applied (pending) are skipped — only applied steps get compensated. The compensation_hash on each step is set to the content hash of the TOMBSTONE record written for it.
flags: 2. It does not delete the original record — it appends a logical deletion marker to the strand. Readers that filter out TOEMBSTONEs will no longer see the compensated data; the underlying record remains in the immutable chain for audit purposes.Quick start
This example writes an order event to two agents atomically. If the billing write fails, the analytics write is automatically compensated.
import { encode } from "@msgpack/msgpack";
async function writeSaga(analyticsUrl: string, billingUrl: string) {
const analyticsPayload = Buffer.from(
encode({ event: "order_placed", order_id: "ord_123", amount: 4999 })
).toString("base64");
const billingPayload = Buffer.from(
encode({ charge: 4999, order_id: "ord_123", currency: "usd" })
).toString("base64");
const res = await fetch(`${SAPIX_URL}/v1/saga`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
steps: [
{ target_agent_url: analyticsUrl, payload_b64: analyticsPayload, flags: 0 },
{ target_agent_url: billingUrl, payload_b64: billingPayload, flags: 0 },
],
}),
});
const saga = await res.json();
if (saga.state !== "committed") {
throw new Error(`Saga ${saga.id} ended in state: ${saga.state}`);
}
return saga.id;
}