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/healthstarts with/v1/. - Request and response bodies are JSON; send
Content-Type: application/jsonon every call that has a body. Reading a row’s history is aGETthat 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.