Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

relstore

Preview Base URL: https://relstore.stackwell.tech

Overview

relstore is a relational store you can give an app or an AI agent without risking its history. Your data lives in tables with typed columns, and you query it with a structured query language (filters, ordering, paging). What makes it different is how writes work:

  • Every write appends a new version of a row. Nothing your credential can do rewrites or removes an earlier version.
  • A delete is a soft delete: it hides the row from queries and keeps every version.
  • Each version records which client wrote it.
  • You can read the full history of any row.

Each database has two clients: an agent client that reads and writes, and an operator client that reads. Postgres itself keeps one database’s clients out of every other database.

It is for developers of apps and agents that need a database whose history can’t be lost to a bug, a bad prompt or a stolen credential.

What preview means here: relstore runs in production at https://relstore.stackwell.tech, but no customer databases exist yet. Databases are set up by Stackwell; there is no self-serve sign-up. Some features are not finished; Concepts and Data and privacy mark each one “Not yet”. In particular, nothing can be permanently removed yet.

Quickstart

1. Check that relstore is reachable

No credentials needed:

curl -fsS https://relstore.stackwell.tech/health
ok

/health answers ok only when the service can reach its database.

2. Get a database

Stackwell creates the database and registers two ChirpAuth clients for it, one for the agent and one for the operator. You get each client’s id and secret. There is no self-serve sign-up yet. Use these clients for relstore only (why).

3. Get a token

Exchange the agent client’s id and secret for a ChirpAuth machine token:

curl -sS https://signin.chirpauth.com/token \
  -d grant_type=client_credentials \
  -d client_id="$RELSTORE_CLIENT_ID" \
  -d client_secret="$RELSTORE_CLIENT_SECRET"

The response’s id_token is your token. Send it on every call as Authorization: Bearer $TOKEN.

4. Create a table

curl -sS https://relstore.stackwell.tech/v1/tables \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "tasks", "columns": [
        {"name": "title", "type": "text"},
        {"name": "done", "type": "bool", "indexed": true},
        {"name": "priority", "type": "int"}]}'
{"created": "tasks"}

5. Write a row

You choose each row’s id (rid):

curl -sS https://relstore.stackwell.tech/v1/rows \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"table": "tasks", "rid": "t1",
       "fields": {"title": "Write the docs", "done": false, "priority": 2}}'
{"version": 1}

Writing t1 again appends version 2.

6. Query

curl -sS https://relstore.stackwell.tech/v1/query \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"table": "tasks", "filter": {"op": "eq", "column": "done", "value": false}}'
[
  {
    "done": false,
    "priority": 2,
    "rid": "t1",
    "title": "Write the docs",
    "updated_ts": "2026-11-05T20:45:04.953998Z"
  }
]

API examples shows the rest: history, soft delete, and the full query language.

Authentication

Every call except /health needs a ChirpAuth machine token from a client bound to your database, sent as Authorization: Bearer <token>. The client decides both the database and what you may do: the agent client reads and writes, the operator client only reads. There is no database id in any URL. Authentication has the details and a table of which client may call which route.

API reference

The full endpoint list is in the generated API reference. API examples walks through each endpoint with real requests and responses, and Concepts explains the rules they follow.

Conventions shared by every endpoint:

  • Base URL https://relstore.stackwell.tech; every path except /health starts with /v1/.
  • Request and response bodies are JSON; send Content-Type: application/json on every call that has a body. Reading a row’s history is a GET that carries a JSON body; see API examples.
  • Object keys in responses come back in alphabetical order.
  • Timestamps are fixed-width ISO 8601 text in UTC, so text order is time order: 2026-11-05T20:45:04.953998Z.
  • There are no idempotency keys. Every write appends a version, so retrying a write appends another one.

Errors and limits

Error bodies are plain text, not JSON. Every refused credential gets 403 with the body forbidden; relstore never answers 401. Each database may make 60 requests a minute, request bodies are capped at 1 MiB, a statement may run for 5 seconds, and a query may match at most 10,000 rows. Errors and limits lists every status and limit.

Data and privacy

relstore keeps every version of every row for as long as the database exists, in Postgres on AWS in us-east-1. Only your database’s two clients can read it through the API. Nothing can be permanently removed yet: a soft delete hides a row from queries but keeps it. Data and privacy says exactly what is stored, who can see it, and what deletion does and does not do.