Test hosts and root domains

Bind a sandbox to its own host, try a version there, then promote straight from that host to live.

Docs menu

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 (live or a sandbox) with env.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.com is 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.com suggests eventsta-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