Use casesProduct teamsControlled writes03 / 12
How it fits together

Every gate has to say yes

A write reaches your database only when the server allows writes, the caller's grant allows this query, the values pass their checks, and the SQL is the kind of write the key may do. An agent's write also waits for a person to approve it. Any one of them can refuse, before the database is touched.

Offby default, at every level
1 rowan agent's write may change, unless raised
15 minfor a person to approve it
Who writes
The governed core: the write path
Your data
↗

Partner backend

POST /q/acme_submit_order
↗

Support lead, signed in

POST /q/correct_address
↗

AI agent

tools/call close_ticket
The governed corewrite path↗

01 · Server

ALLOW_WRITES=1

The ceiling. Until it's set, nothing writes, whatever a key says.

403 read_only

02 · Grant

allow_writes: true

This key, through this one query. "*" never carries a write.

403 read_only

03 · Values

qty: integer 1–100

Typed, checked, bound. A token's claim fills the owner, never the request.

400 param_invalid

04 · SQL guard

write_ops · tables

One statement, a write of an allowed kind, on allowed tables.

403 write_op_not_allowed

05 · A person MCP

agent_writes: approval

Agents only. Someone else approves these exact arguments; it runs once.

max_affected_rows: 1 · more is rolled back 409 approval_pending
↗

Your database

PostgreSQL · shop

The write commits only after every gate has passed. An agent's runs in a transaction, rolled back past its row ceiling.

↗

The record

Run history: every write, with its key, its rows, and for an agent approved_by Write requests: each agent request, kept 30 days Audit log: approvals, rejections and every grant change
↗ A person approves Console · Approvals Approve
What's keptevery write, and who let an agent make it
history acme_submit_order v1 key acme-orders success 1 row history close_ticket v2 key ops-agent success 1 row
request wr_7f3 approved_by ops-lead
Stablecompatibility promise Expmay change in a minor release Saving a write query needs no permission; running one does.
Who writesPartners, signed-in people, AI agentsA partner's key, a person's sign-in token, or an agent over MCP, each calling one saved write query.
The governed coreServer → grant → values → SQL guardWrites are off until the server allows them. A grant opens one query for one caller, the values are checked and bound, and the SQL must be the kind of write the key may do. Any one refuses, before the database is touched.
Only for agents StableA person approvesAn agent's first call writes nothing. Someone else approves the exact arguments, within 15 minutes, and the write runs once, rolled back past its row ceiling.
Your databaseThe write commits
The recordRun history, write requests, auditEvery write with its key; an agent's with who approved it.

The same governed core as the full architecture.

For operational workflows

Let a partner or a form write, through one approved query

Sometimes the job is putting data in: a partner submitting orders, a form filing a request, a support lead correcting a record. Writing is off by default at every level, and you open one query, for one caller, and nothing else.

✓QUERYAPIGATE_ALLOW_WRITES is the server-wide ceiling: until it is set, nothing writes, whatever a key says.
✓A key or role writes only with allow_writes, which allowed_write_ops can narrow to insert, update or delete.
✓Or make exactly one saved query writable for one key. Every other query, and ad-hoc SQL, stays read-only.
✓Values are typed and bound, never SQL, and every write is in the run history. For AI agents, each write waits for a person to approve it.
Controlled writes
# 1. the server allows writes at all
QUERYAPIGATE_ALLOW_WRITES=1

# 2. one write with typed values; the partner (42) is fixed in the SQL
{"name": "acme_submit_order", "description": "Acme submits an order",
 "connection_name": "shop",
 "sql": "INSERT INTO orders (partner_id, sku, qty) VALUES (42, :sku, :qty)",
 "parameters": {"sku": {"type": "string", "pattern": "[A-Z]-[0-9]{3}"},
                "qty": {"type": "integer", "min": 1, "max": 100}}}

# 3. Acme's key: this one query may write, nothing else
{"name": "acme-orders", "connections": [],
 "queries": [{"name": "acme_submit_order", "allow_writes": true}]}

POST /q/acme_submit_order
X-API-Key: sk_live_…orders
{"sku": "A-100", "qty": 3}
More use casesAll twelve →

Not sure which fits?

The example APIs ship with a generated database and one scoped key per scenario, so you can try each of these in a few minutes without connecting anything of your own.

Run the examplesCompare alternatives