An app's environments answer on hosts its agent chooses: my-app-v2.substrio.com runs exactly what the
sandbox dev runs, on the sandbox's own data, while my-app.substrio.com keeps serving the live version.
When the test passes, one call makes that version live.
- A root domain is a domain apps are served under, enabled by Substrio before any app can use it.
- A test host is
<label>.<root>: one DNS label under an enabled root, bound by an app to one of its environments (liveor a sandbox) withenv.host.set.
The agent's loop: test on a host, then promote
substrio_version_create ... # version 2 pushed (version 1 is live)
substrio_sandbox_create {"name": "dev"}
substrio_live_set {"version": 2, "sandbox": "dev"}
substrio_env_host_set {"host": "my-app-v2.substrio.com", "environment": "dev"}
-> {"host": "my-app-v2.substrio.com", "label": "my-app-v2", "root": "substrio.com",
"environment": "dev", "version": 2, "created_at": 1793080000}
GET https://my-app-v2.substrio.com/hello -> version 2, the sandbox's data
GET https://my-app.substrio.com/hello -> version 1, the app's data
substrio_live_set {"from": "my-app-v2.substrio.com"}
-> {"live": 2, "previous": 1, ...}
substrio_env_host_remove {"host": "my-app-v2.substrio.com"}
env.host.set with a version runs that version in the named sandbox first, making the sandbox when it does
not exist yet, so one call puts a version on a host:
{"name": "substrio_env_host_set", "arguments": {"host": "my-app-v1.substrio.com", "environment": "one", "version": 1}}
Operations
| Operation | Route | MCP tool | CLI | Who |
|---|---|---|---|---|
env.host.set |
POST /v1/_env_hosts |
substrio_env_host_set |
substrio env host set host=H environment=E [version=N] |
the app's key or agent |
env.host.list |
GET /v1/_env_hosts |
substrio_env_host_list |
substrio env host list |
the app's key or agent |
env.host.remove |
DELETE /v1/_env_hosts/{host} |
substrio_env_host_remove |
substrio env host remove H --yes |
the app's key or agent |
version.live.set takes from instead of version: a sandbox name, or a host from env.host.list. The
version that environment runs goes live, through the same apply as a numbered promote (check, backup,
direct, plan): see versioning.md.
env.host.set
| Member | ||
|---|---|---|
host |
required | <label>.<root>, any case (stored lowercase) |
environment |
required | live, a sandbox name (dev) or its whole slug (my-app--dev), or another host of the app's |
version |
optional | runs this version in the sandbox before binding (a new sandbox is made); not with live |
It answers the binding: host, label, root, environment, version (what the environment runs now,
0 when nothing) and created_at. Binding a host again moves it to the named environment; binding it to the
environment it already has changes nothing.
env.host.list
{"hosts": [{host, label, root, environment, version, created_at}], "roots": [...]}: every host of the app
and its sandboxes, by label, and the roots a host may be bound under now.
The rules
- One owner a label. A label is in the same name space as app slugs: no test host takes an app's slug, no app takes a held label, and a label answers under every enabled root, as an app's slug does. one label has one owner under every root. This is also what keeps a phishing look-alike of another app's name off any root domain.
- Reserved:
www,api,mcp,platform, every app's slug, and any label with--(sandbox hosts,<app>--<sandbox>). - One label under the root:
a.b.substrio.comis refused (a root's certificate names one level). - The label rule: lowercase letters, digits and inner hyphens, 1 to 63 bytes. The refusal suggests the
nearest valid host:
eventsta_v15.substrio.comsuggestseventsta-v15.substrio.com. - Isolation: a host answers with its environment: a sandbox host reads and writes the sandbox's data, keys and secrets, never the app's.
- Removal: removing a host frees its label at once; removing a sandbox or an app removes its hosts in the same transaction.
Refusals
Each refusal uses the standard error body with
field naming what to correct. Who holds a taken host is never shown.
| Code | Value | Status | next_action | field | When |
|---|---|---|---|---|---|
HOST_TAKEN |
0xC048 | 409 | correct_request |
host |
another app holds the label |
HOST_INVALID |
0xC049 | 400 | correct_request |
host |
not <label>.<root>, more than one label, or a label that breaks the rule; detail says what and where, suggestion the nearest valid host |
HOST_RESERVED |
0xC04A | 409 | correct_request |
host |
www, api, mcp, platform, an app's slug, or a -- label |
ENV_HOST_NOT_FOUND |
0xC04B | 404 | refresh_resource |
host |
env.host.remove of a host the app does not hold |
ENVIRONMENT_NOT_FOUND |
0xC04C | 404 | correct_request |
environment or from |
no such sandbox or host of the app's, or (for from) it runs no version |