The /veris/* control plane
Every twin serves the same set of routes for reading and changing its world.
Every twin serves two things: a vendor API, which your code calls, and a control
plane at /veris/*, which your agent calls.
Both are on the URLs veris sandbox services get prints. url is what your
code points at. control_url is where /veris/* lives. For an HTTP twin they
are the same. For a Postgres twin url is a connection string and control_url
is still HTTP.
No API key is needed. Anyone holding the control_url can read and change that
twin’s world.
Your application never calls these. Only your agent, your setup, and your
read-back do. Traffic here is recorded under tier=control and is kept separate
from what your code sent.
Holding a control URL in your shell
The examples on this page, and everywhere else in these docs, write
$CONTROL_URL for one twin’s control URL. veris sandbox services get <twin>
prints it on the Control URL line, and --json puts the same value in
control_url:
export CONTROL_URL=$(veris sandbox services get stripe --json | jq -r .control_url)Each twin has its own, so set it again when you move to another twin.
Routes
| Route | Method | What it does |
|---|---|---|
/veris/health | GET | Is it up, and what is it |
/veris/manual | GET | Short operating notes for this twin: credentials, API versions, what faults it accepts |
/veris/schema | GET | Every table and column, described in plain language |
/veris/data | GET | Bare, returns row counts per table. With ?entity_type= returns rows |
/veris/data | POST / PATCH / DELETE | Add, edit, remove rows |
/veris/files | POST | Upload file bytes. See Files |
/veris/requests | GET | Every request this twin served. See the trace |
/veris/operations | GET | Every operation this twin serves. ?surface=rest, graphql or mcp narrows it |
/veris/snapshot | POST | A full dump of this twin’s state, table by table, with its state_version |
/veris/reset | POST | Reset this twin only. Takes {"profile": "..."} or {"data": {...}} |
/veris/reset-world | POST | Return the sandbox’s shared clock to live time |
/veris/client/probe | POST | Re-check that the registered callback destination is reachable |
Reading a world
veris sandbox data get stripe
veris sandbox data get stripe customers --limit 50 --offset 0curl "$CONTROL_URL/veris/data"
curl "$CONTROL_URL/veris/data?entity_type=customers&limit=50&offset=0"Paged reads report a total. limit goes up to 1000.
Writing to a world
New rows come from a file keyed by twin name — here a customers.json holding
{"stripe": {"customers": [{"id": "cus_1", "email": "a@example.test"}]}}:
veris sandbox data add customers.jsoncurl -X POST "$CONTROL_URL/veris/data" \
-H 'Content-Type: application/json' \
-d '{"data": {"customers": [{"id": "cus_1", "email": "a@example.test"}]}}'veris sandbox data set stripe customers id=cus_1 email=b@example.test changes a
row that already exists, and veris sandbox data delete stripe customers id=cus_1 removes one: the PATCH and DELETE of the same route.
Everything is validated before anything is written. A bad column, a bad type, or a broken relationship is refused with an explanation and the world is untouched.
The manual
GET /veris/manual returns the operating facts for one twin: which credentials
it accepts, which API versions it serves, which error statuses you can arm, and
anything specific about how it handles a repeated write. Read it before designing
anything against that twin.
It does not list endpoints. GET /veris/operations does.
Reset
POST /veris/reset resets one twin. It does not touch the others and makes no
promise that they still agree with each other afterwards.
It takes at most one replacement, data or profile. Any other key is refused
with a 422 before anything changes, rather than being ignored.
For a coherent reset across every twin at once, use veris sandbox reset. See
Reset, snapshot, baseline.
Routes that do not exist
/openapi.json, /docs and /redoc are not served on a twin. For the
operations a twin serves use GET /veris/operations; for its tables and columns
use GET /veris/schema.