An app's environments are available on hosts chosen by its agent. For example,
my-app-v2.substrio.com runs the same version and uses the same data as the dev sandbox, while
my-app.substrio.com continues to serve 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"}
With version, env.host.set first runs that version in the named sandbox, creating the sandbox if it does
not exist. One call can therefore put 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 accepts from instead of version, using a sandbox name or a host from env.host.list.
The version running in that environment goes live through the same process as a numbered promotion, including
check, backup, direct, and 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 returns the binding: host, label, root, environment, version (the version running now, or 0
when none), and created_at. Binding a host again moves it to the named environment. Binding it to its
current environment changes nothing.
env.host.list
The response, {"hosts": [{host, label, root, environment, version, created_at}], "roots": [...]},
lists the hosts of the app and its sandboxes by label, and the roots currently available for binding.
The rules
- One owner per label. Labels share a namespace with app slugs. A test host cannot take an app's slug, and an app cannot take a label already in use. A label resolves under every enabled root and has the same owner across them. This prevents another app from using the same label under a different 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 serves its environment. A sandbox host reads and writes the sandbox's data, keys, and secrets rather than the live 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 |