OriginChain docs
query shapes · transactions

Transactions.

A transaction is how you make several writes land together or not at all. Reach for one when a single logical change touches more than one row — moving stock between two SKUs, debiting one balance to credit another, writing a record plus its audit trail. If your change is one row, you do not need a transaction: a single row write is already atomic.

OriginChain ships two transaction surfaces with different guarantees. Picking the wrong one is a correctness bug, so the difference is the first thing on this page.

preview surface

Every /tx response carries "_preview": true. The wire shape can still change between releases — pin your client and read the changelog before upgrading. No OriginChain SDK wraps transactions yet, in any language, so every example below drives the endpoints over plain HTTP.

1

Which surface to use.

/tx endpoints SQL session transaction
Isolation Snapshot isolation by default; serializable opt-in. Read committed. Not selectable.
Conflict detection Yes — read set revalidated at commit, 409 on loss. None. Concurrent writers do not abort each other.
Work you can do Row put / get / delete, plus scan-based read plans. Any SQL write statement, including data-definition statements.
Handle tx_id in the URL path. X-OC-Session-Id header.
Reach for it when The change is read-modify-write and a lost update would be a bug. You want several SQL statements to land in one frame and there are no concurrent writers to the same rows.

Sections 3 to 6 cover the /tx endpoints. Section 7 covers SQL session transactions.

2

What the isolation actually guarantees.

The default level on /tx is snapshot isolation, implemented as optimistic, commit-time validation. The mechanics are worth understanding, because one detail surprises people:

  • Reads hit live data, not a frozen snapshot. There are no version chains. Each read returns whatever the store holds at that instant and records the value it saw in a read set.
  • The snapshot is enforced at commit, not at read. Commit re-reads every key in the read set and compares. If anything changed, the commit aborts with 409 tx_conflict and nothing is written.
  • So a committed transaction saw a consistent view — but a re-read mid-transaction is not repeatable. Read the same key twice and the second read can return a newer value. The commit will then fail, so you cannot act on the inconsistency, but do not write code that assumes two reads agree.
  • Writes are buffered. Nothing is durable until commit, at which point the whole buffer lands in a single write-ahead-log frame — the storage layer's all-or-nothing unit.
  • You see your own writes. A read through the transaction returns its own buffered value; a buffered delete reads as absent.
snapshot isolation is not serializable

At the default level, phantoms and write skew are possible. A scan that matched three rows at the start can match four at commit if another writer inserted one, and the conflict check will not catch it — it validates the keys you observed, not the keys that would now match. Two transactions that read a shared invariant and each write a different row can both commit and break it. If your correctness argument depends on either of those not happening, use serializable.

Which reads count toward that check depends on how you read. This table is the most important thing on the page:

How you read Snapshot isolation Serializable
GET /tx/:id/rows/:schema/:pk Recorded. A concurrent change to that row aborts your commit with tx_conflict. Recorded, plus a read predicate for the cycle check.
POST /tx/:id/query NOT recorded. The scan gives you read-your-writes but no conflict protection at all. Recorded as read predicates — this is what catches phantoms and write skew.
POST /sql or GET /rows (outside the transaction) Not recorded. Invisible to the transaction. Not recorded. Invisible to the transaction.
the sharpest edge on this page

At the default level, a transaction that decides what to write based on a /tx/:id/query scan gets no conflict protection for that decision. The scan reflects your own buffered writes, but it records nothing in the read set, so the commit has nothing to revalidate and will succeed even if the scanned rows changed underneath you. Either read the specific keys you care about with GET /tx/:id/rows/… so they enter the read set, or open the transaction as serializable.

also not present

No range or gap locking — scans do not lock what they scanned. No deadlock or starvation defence: a long transaction contesting a hot key can keep losing the race, so bound your retries. No savepoints and no nested transactions. No data-definition statements inside a /tx transaction — schema migrations run through their own cutover path. Conflicts abort at commit and never block, so there are no lock-wait deadlocks by construction.

3

Before you start.

Transactions add no fields to your schema — they wrap writes against tables you have already registered. Every example below uses the shop.orders table from the quickstart:

namespace   = "shop"
table       = "orders"
primary_key = ["id"]

[[columns]]
name = "id"
ty   = "str"
required = true

[[columns]]
name = "customer"
ty   = "str"

[[columns]]
name = "amount_cents"
ty   = "i64"

[[columns]]
name = "status"
ty   = "str"

[[columns]]
name = "notes"
ty   = "str"

[[columns]]
name = "placed_ms"
ty   = "u64"

# Enforced INSIDE a transaction too - a violation aborts the whole
# transaction at COMMIT, not just the offending write.
[[check_constraints]]
name       = "amount_positive"
expression = "amount_cents > 0"

Constraints declared on the table — foreign keys, back-references, CHECK, uniqueness — are enforced at commit, on the whole buffer. One bad row aborts the entire transaction, not just that write.

The shell examples assume $OC_HOST, $OC_TENANT and $OC_TOKEN are set — see Authentication.

4

The lifecycle, end to end.

Five endpoints, all under /v1/tenants/:tenant/tx. Open one, buffer work against its id, then commit or roll back.

Open a transaction

POST /tx/begin takes no body. It returns a ULID tx_id, the logical read tick the transaction is anchored to, and the isolation level you actually got — always check that field rather than assuming.

POST /tx/begin
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/begin" \
  -H "Authorization: Bearer $OC_TOKEN"

# {
#   "tx_id":     "01JTS0Q4M9V3K7X2N8R1P6C0DE",
#   "read_ts":   184213,
#   "isolation": "snapshot_isolation",
#   "_preview":  true
# }

Buffer writes

POST /tx/:tx_id/rows/:schema buffers an upsert. The row body is flat — the same shape the non-transactional row endpoint takes. It is validated against the manifest immediately, so a type or shape error comes back as a 400 right here rather than at commit. Referential and CHECK constraints are the ones deferred to commit.

POST /tx/:tx_id/rows/:schema
# Two writes, one transaction. Neither is durable yet.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID/rows/shop.orders" \
  -H "Authorization: Bearer $OC_TOKEN" -H "Content-Type: application/json" \
  -d '{ "id": "ord-1001", "customer": "cus-77", "amount_cents": 4200,
        "status": "pending", "notes": "gift wrap", "placed_ms": 1714478049000 }'

# { "tx_id": "01JTS0Q4M9V3K7X2N8R1P6C0DE", "op": "put",
#   "buffered": true, "_preview": true }

curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID/rows/shop.orders" \
  -H "Authorization: Bearer $OC_TOKEN" -H "Content-Type: application/json" \
  -d '{ "id": "ord-1002", "customer": "cus-77", "amount_cents": 1500,
        "status": "pending", "notes": "", "placed_ms": 1714478051000 }'

Read through the transaction

GET /tx/:tx_id/rows/:schema/:pk answers from the transaction's own buffer first, then from committed state. The read is recorded in the read set — which is exactly what makes a concurrent change to that row abort this transaction at commit.

GET /tx/:tx_id/rows/:schema/:pk
# Reads the tx's own buffered write first, then committed state.
curl "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID/rows/shop.orders/ord-1001" \
  -H "Authorization: Bearer $OC_TOKEN"

# The row is nested under "row" - unlike the non-transactional
# /rows/:schema/:pk endpoint, which returns the row object bare.
# {
#   "row": { "id": "ord-1001", "customer": "cus-77", "amount_cents": 4200,
#            "status": "pending", "notes": "gift wrap",
#            "placed_ms": 1714478049000, "_oc_row_version": 1 },
#   "tx_id":    "01JTS0Q4M9V3K7X2N8R1P6C0DE",
#   "_preview": true
# }
#
# The same GET on /rows/shop.orders/ord-1001 (outside the tx) 404s -
# nothing has been committed yet.

Buffer a delete

DELETE /tx/:tx_id/rows/:schema/:pk buffers a removal. Note the commit-time cost: any transaction with a buffered delete makes the engine inspect every table on the instance for references back to the deleted rows, where a write-only transaction inspects just the tables it touched.

DELETE /tx/:tx_id/rows/:schema/:pk
curl -X DELETE \
  "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID/rows/shop.orders/ord-0900" \
  -H "Authorization: Bearer $OC_TOKEN"

# { "tx_id": "01JTS0Q4M9V3K7X2N8R1P6C0DE", "op": "delete",
#   "buffered": true, "_preview": true }
#
# A later GET through the SAME tx reads ord-0900 as absent (404),
# even though it is still present in committed state until COMMIT.
#
# Deleting a row that does not exist is NOT an error - it buffers
# zero operations and the commit succeeds having written nothing.

Commit

POST /tx/:tx_id/commit revalidates the read set, runs the constraint checks, and writes the buffer as one frame. On success the body carries the log position the frame landed at. On failure nothing was written, and the error field tells you whether retrying can help.

POST /tx/:tx_id/commit
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID/commit" \
  -H "Authorization: Bearer $OC_TOKEN"

# 200 - everything landed in one write-ahead-log frame:
# { "committed": true, "tx_id": "01JTS0Q4M9V3K7X2N8R1P6C0DE",
#   "lsn": { "segment": 12, "offset": 884210 }, "_preview": true }
#
# 409 - somebody else changed a row this tx had read:
# { "error": "tx_conflict", "tx_id": "01JTS0...",
#   "read_lsn": 184213, "current_lsn": 184298, "_preview": true }
about the lsn field

On a single-writer instance lsn is an object — { segment, offset }. On an instance running consensus replication the position is stamped after the round, and the field comes back null; the response header X-OC-Replication-Path tells you which path served you. A commit that buffered nothing also succeeds, and reports the sentinel { segment: 0, offset: 0 } — nothing was appended. Treat committed: true as the durability signal, not the shape of lsn.

commit always forces a disk flush

A /tx commit flushes its log frame to stable storage before it answers, regardless of how the instance's general write-flush policy is tuned. A crash immediately after a 200 cannot lose the transaction. That also means a commit is more expensive than an ordinary row write — batch your work into fewer, larger transactions rather than many tiny ones.

Roll back, and check state

POST /tx/:tx_id/rollback discards the buffer. GET /tx/:tx_id reports the current state and survives the terminal transition, so you can always ask what happened to an id.

rollback + status
# Discard everything the transaction buffered.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID/rollback" \
  -H "Authorization: Bearer $OC_TOKEN"
# { "rolled_back": true, "tx_id": "01JTS0...", "_preview": true }

# Ask what happened to a transaction at any time.
curl "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID" \
  -H "Authorization: Bearer $OC_TOKEN"
# { "tx_id": "01JTS0...", "read_ts": 184213, "state": "committed",
#   "isolation": "snapshot_isolation", "_preview": true }
state Meaning
active Open. Accepts row ops, queries, commit and rollback.
committing A commit is in flight. Transient.
committed Terminal. Every buffered op landed in one write-ahead-log frame.
rolled_back Terminal. Buffer discarded — by an explicit rollback, or by a commit that failed a constraint check or an internal error.
conflict Terminal. Commit lost the race: a row in the read set changed, or (serializable only) the commit-time cycle check fired.
expired Terminal. The idle sweeper aborted it after no client activity for the idle window. Buffer discarded, exactly as on rollback.

Rolling back a transaction that is already terminal returns 409, not a silent success. An unknown or other-instance tx_id returns 404 — deliberately indistinguishable, so an id cannot be probed across instances. Terminal handles are kept for about an hour so a late status poll still gets a truthful answer; after that the same poll returns 404.

5

Serializable isolation.

Pass ?isolation=serializable to /tx/begin and the engine layers serializable snapshot isolation over the machinery above: reads register predicates, writes register predicates for both the before and after image, and commit runs a dangerous-structure check that aborts the pivot of a read-write dependency cycle. Write skew and phantoms are rejected, not merely observed, and the failure arrives as 409 serialization_failure.

The only other accepted value is snapshot_isolation (the default). Anything else is a 400.

POST /tx/begin?isolation=serializable
# Opt in at begin. The response echoes what you actually got.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/begin?isolation=serializable" \
  -H "Authorization: Bearer $OC_TOKEN"

# { "tx_id": "01JTS1...", "read_ts": 184301,
#   "isolation": "serializable", "_preview": true }

# On a sharded instance the request is REFUSED - never silently downgraded:
# HTTP 501
# { "error": "serializable_sharded_unsupported",
#   "isolation": "serializable",
#   "available_isolation": ["snapshot_isolation"] }
single-shard only

The dependency graph that makes serializable work is held in memory inside a single serving process. On a sharded instance it cannot see a peer's dependencies, so begin refuses the request with 501 serializable_sharded_unsupported and lists available_isolation. This is deliberate: you learn the level is unavailable before you build a transaction on a guarantee you would not have got. A transaction opened at single-shard whose instance is resharded mid-flight is refused at its next query or at commit for the same reason.

reads must go through the transaction

Serializable only sees the reads you make through the transactionGET /tx/:id/rows/… and POST /tx/:id/query. A read issued on the ordinary /sql or /rows endpoints registers nothing, so basing a serializable write on it gives you no guarantee at all.

single active writer, too

The same in-memory constraint applies to consensus replication. On an instance running quorum replication a serializable commit is refused with 501 serializable_raft_unsupported — peer replicas cannot reproduce the dependency graph. Serializable is available on single-shard instances with a single active writer. Snapshot isolation works everywhere.

6

Retrying a conflict.

Optimistic concurrency means conflicts are normal, not exceptional. Any client that writes contended rows needs a retry loop. Two rules:

  • Retry the whole transaction, from a fresh begin. A conflicted transaction is terminal — you cannot resume it, and reusing the id returns 409. Re-read your inputs too; the values you decided on are the ones that just went stale.
  • Bound the loop and back off. There is no starvation defence in the engine, so an unbounded retry against a hot key is an infinite loop you wrote yourself.

Retry on tx_conflict and serialization_failure. Do not retry constraint_violation — the same data will fail again.

bounded retry with exponential backoff
import time, requests

RETRYABLE = {"tx_conflict", "serialization_failure"}

def run_tx(apply, attempts=5):
    """apply(tx_id) buffers the work. Returns the commit body."""
    for n in range(attempts):
        tx_id = requests.post(f"{BASE}/tx/begin", headers=H).json()["tx_id"]
        try:
            apply(tx_id)
        except Exception:
            requests.post(f"{BASE}/tx/{tx_id}/rollback", headers=H)
            raise

        r = requests.post(f"{BASE}/tx/{tx_id}/commit", headers=H)
        if r.status_code == 200:
            return r.json()
        if r.status_code == 409 and r.json().get("error") in RETRYABLE:
            time.sleep(0.05 * (2 ** n))   # back off, then rebuild from scratch
            continue
        r.raise_for_status()

    raise RuntimeError("transaction did not converge after 5 attempts")
7

SQL session transactions.

BEGIN, COMMIT and ROLLBACK are accepted as statements on POST /sql. Write statements issued between them buffer instead of executing, and the whole buffer lands in one frame at COMMIT. This is how you make several SQL statements atomic.

The buffer is keyed by (instance, session id). Send an explicit X-OC-Session-Id header on every statement in the flow. Without it the engine falls back to keying the buffer by your bearer token, which means two concurrent flows sharing a token share a transaction buffer — and neither gets the fence that detects a lost transaction.

BEGIN / statements / COMMIT on POST /sql
# Every statement in the flow MUST carry the same X-OC-Session-Id.
SID=$(uuidgen)

curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/sql" \
  -H "Authorization: Bearer $OC_TOKEN" -H "X-OC-Session-Id: $SID" \
  -H "Content-Type: application/json" -d '{"sql":"BEGIN"}'
# { "kind": "tx", "op": "begin", "ops_committed": 0, "session_id": "..." }

curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/sql" \
  -H "Authorization: Bearer $OC_TOKEN" -H "X-OC-Session-Id: $SID" \
  -H "Content-Type: application/json" \
  -d '{"sql":"UPDATE shop.orders SET status = $1 WHERE id = $2",
       "params":["shipped","ord-1001"]}'

curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/sql" \
  -H "Authorization: Bearer $OC_TOKEN" -H "X-OC-Session-Id: $SID" \
  -H "Content-Type: application/json" -d '{"sql":"COMMIT"}'
# { "kind": "tx", "op": "commit", "ops_committed": 2, "session_id": "..." }
read committed — no conflict detection

A SQL session transaction gives you atomicity, not isolation. There is no read set and no snapshot, so two concurrent sessions doing read-modify-write on the same row will both commit and one update is lost — no error, no 409. If a lost update would be a bug, use the /tx endpoints instead. The level is not selectable here: a BEGIN carrying an ISOLATION LEVEL clause is refused rather than quietly downgraded.

a statement does not see the buffer above it

This surface does not read its own writes — a deliberate divergence from PostgreSQL. A SELECT after a buffered INSERT in the same flow will not see the inserted row, and an UPDATE … WHERE matches against committed state, not against what you buffered. One consequence worth spelling out: two INSERTs of the same primary key inside one flow collapse into a single row at commit — last one wins, with no duplicate-key error. Sequence read-then-write work so the reads happen before BEGIN.

the buffer is memory, and it can be lost

It lives in the serving process. If the engine restarts, or the instance fails over to its standby, the buffer is gone — the next statement in that flow returns 409 with either "transaction not found — it may have been lost to an engine restart" or "transaction lost on failover". Nothing was written. The recovery is the same in both cases: send ROLLBACK, then retry the whole transaction. The engine deliberately refuses to forward the statement to the new primary, because there it would execute as a durable standalone write that your later COMMIT or ROLLBACK could not undo.

a forgotten BEGIN expires after five minutes

The buffer is swept once it is five minutes old, measured from BEGIN — total age, not idle time. That bound is deliberate: without it, one forgotten BEGIN would make every later write on that session silently buffer forever. After the sweep the session reads as not-in-a-transaction, so subsequent statements execute normally and a late COMMIT honestly reports that there is nothing open.

8

Reading inside a transaction.

Point reads by primary key use GET /tx/:tx_id/rows/:schema/:pk, shown above. For anything broader there is POST /tx/:tx_id/query — with two constraints worth knowing before you plan around it.

  • It takes a plan document, not a SQL string. There is no way to run a SQL SELECT inside a /tx transaction today. The body is the same internal plan format the /v1/query endpoint accepts.
  • Full-table scans only. Read-your-writes is wired into the scan leaf and nowhere else, so a plan that would read the store through an index, a column scan, a row-count fast path, or a graph traversal is refused with 400 rather than answered with a stale result. Scans wrapped in filter, project, sort, limit, distinct, aggregate, join or set operators are fine — the operators compose over correct inputs.
POST /tx/:tx_id/query
# /tx/:id/query takes a PLAN document, not a SQL string. Every node
# carries an "op" tag; the leaf here is a full-table scan.
curl -X POST "https://$OC_HOST/v1/tenants/$OC_TENANT/tx/$TX_ID/query" \
  -H "Authorization: Bearer $OC_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "op":    "filter",
    "child": { "op": "scan", "schema": "shop.orders" },
    "predicate": { "op": "eq", "path": "customer", "value": "cus-77" }
  }'

# { "rows": [ ... ], "tx_id": "01JTS0...", "_preview": true }
#
# Rows reflect this transaction's own buffered writes. A plan whose
# reads bottom out anywhere other than a scan leaf is refused with 400.

A scan-only read is O(table). If your transaction needs an indexed lookup, do the lookup before begin and pass the primary keys in — then read those keys through the transaction so they enter the read set.

9

Every error you can get.

HTTP error When What to do
409 tx_conflict A key this transaction READ changed between begin and commit. Retry the whole transaction from begin. Nothing was written.
409 serialization_failure Serializable only. The commit-time check found a read-write dependency cycle (write skew or a phantom). Retry the whole transaction from begin. Nothing was written.
409 constraint_violation A foreign key, back-reference, CHECK or UNIQUE constraint failed at commit. Not retryable as-is. Fix the data, then run a fresh transaction. The handle goes to rolled_back.
409 (no code) transaction not found SQL session transactions only. The buffer was lost to an engine restart or a failover. Send ROLLBACK, then retry the whole transaction.
413 tx_buffer_cap_exceeded More than 4,096 buffered write operations in one transaction. Commit and open a new transaction, or use the batch row endpoint instead.
429 tx_open_cap_exceeded More than 64 concurrently active transactions for the instance. Commit or roll back what is open. Carries Retry-After: 1.
501 serializable_sharded_unsupported ?isolation=serializable requested on a sharded instance. Use snapshot isolation, or run on a single-shard instance.
501 serializable_raft_unsupported A serializable transaction reached commit on an instance running quorum replication. Use snapshot isolation on that instance.
501 (no code) cross-shard table A transaction touched a table owned by a different shard. Keep each transaction inside one shard's tables.
400 (no code) scan-only plan A /tx/:id/query plan reads the store outside a scan leaf. Re-express as a scan-based plan, or run the read outside the transaction.
400 (no code) params on BEGIN A params array was sent with BEGIN, COMMIT or ROLLBACK on /sql. Transaction-control statements take no bind parameters. Send sql only.
500 (no code) tx commit: … Validation, encoding or storage failure during commit. Nothing was written; the handle goes to rolled_back. Retry from a fresh begin.

A 404 from any /tx route means the id is unknown or belongs to another instance — the two are intentionally identical. A 409 from a row op means the transaction is no longer active; check GET /tx/:tx_id for which terminal state it reached.

10

Limits & gotchas.

Limit Value Why it exists
Buffered operations per transaction 4,096 The buffer is held whole in memory. Breach returns 413. For bulk loading use the batch row endpoint, which is a different path with different limits.
Concurrently active transactions 64 Per instance. Each open transaction pins its read and write sets. Breach returns 429 with Retry-After: 1.
Reads per transaction unbounded The 4,096 cap counts write operations only. The read set has no cap and is held in memory, so a transaction that reads a very large number of keys is a memory risk you own.
Buffered statements (SQL session) unbounded The SQL session buffer has no operation cap at all. Nothing stops you buffering more than fits in memory — keep these transactions small and let the five-minute age limit be your backstop.
Request body 8 MiB Per request, on every transaction route. A row larger than this cannot be buffered; breach returns 413.
Idle timeout (/tx) 5 min A sweeper aborts transactions with no client activity for this long and marks them expired. Every operation refreshes the clock, so an active transaction is never swept.
Total age (SQL session) 5 min Measured from BEGIN, not idle time. Activity does not extend it — a SQL session transaction cannot run longer than this, full stop.
one shard per transaction

On a sharded instance every table a /tx transaction touches — including the targets of any foreign key on a written row — must be owned by the same shard. Anything else is refused with 501. The check runs before the table is looked up, so you get the real reason rather than a misleading 404.

no schema changes inside a transaction

Data-definition statements are not part of the /tx surface at all, and schema migrations run through their own online-cutover path. Register or migrate a table before the transaction that uses it.

vector, full-text and graph writes are not transactional

The /tx surface covers rows — there is no /tx/:id/vector, /tx/:id/fts or /tx/:id/cypher route. Embeddings written through the vector endpoint and documents indexed through the full-text endpoint are separate write paths and do not join the transaction: they apply immediately and a rollback does not undo them. Sequence the row write inside the transaction and the derived write after a successful commit.

geospatial indexes need a repair after an in-transaction delete

Deleting a row inside a transaction does not maintain the spatial index for a geospatial column on that table. If you delete rows this way, run the table's geo reindex endpoint afterwards. Non-transactional deletes are unaffected.

read-only transactions still pay for commit

A transaction that only read still walks its whole read set at commit. If you are only reading and do not need a consistency check across the reads, skip the transaction and query normally — it is strictly cheaper.

no savepoints, no nesting

Transactions are flat. There is no SAVEPOINT, no partial rollback, and opening a transaction inside another is not a thing — you get two independent transactions that can conflict with each other.

Related.

  • Row CRUD — the non-transactional row endpoints these buffer against, and the batch path for bulk writes.
  • SQL — the full statement surface, including the write statements a SQL session transaction buffers.
  • Error reference — the shared error envelope every endpoint returns.
  • Multi-node — what sharding and failover change about the guarantees on this page.