Agents over plain HTTPS

Every MCP tool is also available through HTTPS under the same name, with the same credential and response.

Docs menu

An agent that can make an HTTPS request does not need an MCP client, an SDK, or an OpenAPI file. If your agent can only open links, see Agents that can only open links.

The routes

On the platform host, https://mcp.substrio.com, an app tool specifies its app. The same routes are available on an app's host, where tools run for that app without naming it.

Route What it does
POST /v1/_agent/exchange Redeems a connect code ({"code": "substrio_ae_..."}) for a session. No credential. The answer's key is shown once, and next says how to use it (agent sessions).
GET /v1/_agent/tools The everyday tools this credential reaches (the account tools, then the build loop's), as {"tools": [{"name", "title"}]}, filtered by its grant exactly as MCP tools/list is. ?area=all lists every tool it reaches. On an app's host, the app's own tools follow whole, as app_tools.
GET /v1/_agent/tools/<name> Any one tool it reaches, listed or not: name, title, description and inputSchema (an app tool's has the app argument).
POST /v1/_agent/tools/<name> Runs the tool. The body is its arguments object. The answer is the operation's own HTTP status and body. When the tool has a next step, the body ends with next: [{"tool", "why"}], plus pass and as when a field of this answer is the next tool's argument. A refusal is the standard error body, with fix when it has one.

Send an agent session, account key, or system key as Authorization: Bearer <key>, as on /mcp. Without a credential, only POST /v1/_agent/tools/substrio_connect with {"code": "..."} is available. It performs the exchange above by tool name.

An app tool specifies its app with "app": "<slug>" in the body. The call runs for that app and returns the same result. substrio_route_call returns 200 with the route response inside: {"status", "headers", "body"}.

Refusals

Code Status When
AGENT_CODE_INVALID 401 the connect code is unknown, already used or past its life
AGENT_TOOL_UNKNOWN 404 no tool of that name reaches this credential
INVALID_ARGUMENT 400 an argument is wrong; field names it (app for an app tool with none)
AGENT_ALREADY_CONNECTED 409 substrio_connect with a credential that already works: keep using it; the code was not used
APP_NOT_NAMED 400 a /v1 call that runs at an app, with no X-Substrio-Tenant and on a host no app serves
CREDENTIAL_REQUIRED 401 a call other than substrio_connect with no credential, or with one that is not valid; a key refused at an app answers its own code there (KEY_EXPIRED, PERMISSION_DENIED, ...)
REQUEST_TOO_LARGE 413 the body is larger than mcp_request_max_bytes

Example

This call creates an app in account 7 with an agent session or account key.

curl -X POST https://mcp.substrio.com/v1/_agent/tools/substrio_account_app_create \
  -H "Authorization: Bearer $SUBSTRIO_KEY" -H 'Content-Type: application/json' \
  -d '{"id": 7, "slug": "my-app"}'
# 1. Redeem the code your owner gave you.
curl -X POST https://mcp.substrio.com/v1/_agent/exchange \
  -H 'Content-Type: application/json' -d '{"code":"substrio_ae_..."}'
# {"id":"...","kind":"bearer",...,"key":"substrio_ab_...","next":"Keep key, shown only once. ..."}

# 2. Make an app, then commit a function with one route.
curl -X POST https://mcp.substrio.com/v1/_agent/tools/substrio_account_app_create \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"id": 7, "slug": "my-app"}'
curl -X POST https://mcp.substrio.com/v1/_agent/tools/substrio_version_commit \
  -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
  -d '{"app":"my-app","message":"hello","units":[
        {"path":"functions/hello.js","text":"module.exports = async () => new Response(\"hi\");"},
        {"path":"functions/hello.json","text":"{\"routes\":[\"GET /hello\"]}"}]}'
# {"number":1,...,"next":[{"tool":"substrio_sandbox_create","why":"..."},...]}

# 3. Run it in a sandbox, check it, make it live.
curl -X POST .../v1/_agent/tools/substrio_sandbox_create -d '{"app":"my-app","name":"dev"}' ...
curl -X POST .../v1/_agent/tools/substrio_live_set -d '{"app":"my-app","version":1,"sandbox":"dev"}' ...
curl -X POST .../v1/_agent/tools/substrio_route_call -d '{"app":"my-app","method":"GET","path":"/hello","sandbox":"dev"}' ...
curl -X POST .../v1/_agent/tools/substrio_live_set -d '{"app":"my-app","version":1}' ...
curl https://my-app.substrio.com/hello

The same loop over MCP is in agents.md; the tools themselves are in MCP.

Some agents can only open links with GET requests and cannot supply headers or a body. They can also access tools through these links. The session secret is in the path, so the link itself is the credential.

  1. Your owner makes a connect code (POST /v1/_accounts/{id}/agent/enrollments; the console makes none).
  2. Open https://mcp.substrio.com/v1/_agent/go/<code>. It redeems the code for a session of kind link, as the exchange does, and answers the session's home. Opened again within agent_enroll_ttl_seconds (10 minutes) it answers the same home; after that it is AGENT_CODE_INVALID. A code opened as a link is spent: no other exchange redeems it.
  3. Every URL the answers give starts with the link, https://mcp.substrio.com/v1/_agent/link/<link>. Open only those.
Link What it does
GET /v1/_agent/go/<code> Redeems the code and answers the home (below).
GET /v1/_agent/link/<link> The home: account (id, name), approvals_waiting (your owner's calls a person must approve: id, tool, summary, expires_at), tools ([{"name", "title", "url", "about"}], the everyday tools this session reaches), head_max_bytes, and every answer's members (below).
GET /v1/_agent/link/<link>/<tool>/about One tool's definition, as GET /v1/_agent/tools/<name>: its inputSchema names the arguments.
GET /v1/_agent/link/<link>/<tool>?<arguments> Runs the tool, as POST /v1/_agent/tools/<name>. Each query argument is the tool's argument of that name: a number where the schema says integer, true or false where it says boolean, else a string. What nests (units, an object, a list) goes whole in args, one JSON object, URL-encoded: args={"units":[...]}. The answer is the operation's own status and body.

Every answer, a refusal too, adds:

  • owner: your owner's line (account, plan, apps, storage, the limits it meets), as an MCP result ends with it.
  • tell_your_owner: before each call, tell your owner in one line what you are about to do; after it, what happened.
  • next: on a success, the tools that come next, each with its url.
  • home: the link's home, where every tool is listed.

A body that is not a JSON object comes as result, a string. Every link answer is sent with Cache-Control: no-store, X-Robots-Tag: noindex, nofollow and Referrer-Policy: no-referrer.

A call that requires your owner's approval returns APPROVAL_REQUIRED (403) with an approve_url for the console page. Ask your owner to approve it there, then open the same link again to run the call once.

A link has the same lifetime and idle limits as a bearer session (agent_bearer_max_seconds and agent_bearer_idle_seconds). It is listed and revoked with the account's agent sessions under kind link. An ended link returns CREDENTIAL_REQUIRED (401); ask your owner for a new code.

The request's head (its line and headers) is at most head_max_bytes (4096). A link past it is refused 431 before it reaches a tool. Write a larger file one unit a call (substrio_version_commit), or edit it in parts (substrio_source_edit).

Code Status When
AGENT_CODE_INVALID 401 the code is unknown, spent by another exchange, or past its life
SECRET_KEY_IN_URL 400 a key that belongs in a header is in the URL (an account key, the system key, an agent's substrio_ab_ key), in the link's place or as access_token
CREDENTIAL_REQUIRED 401 the path holds no link, or the link has ended
AGENT_TOOL_UNKNOWN 404 no tool of that name reaches this link; home lists the ones that do
INVALID_ARGUMENT 400 an argument is wrong (field names it), or args is not one JSON object
APPROVAL_REQUIRED 403 the call needs your owner's approval: approve_url
# The code your owner gave you, opened:
GET https://mcp.substrio.com/v1/_agent/go/substrio_ae_...
# {"account":{"id":7,"name":"Ada"},"approvals_waiting":[],
#  "tools":[{"name":"substrio_account_app_create","title":"...",
#            "url":"https://mcp.substrio.com/v1/_agent/link/substrio_al_.../substrio_account_app_create",
#            "about":".../substrio_account_app_create/about"},...],
#  "head_max_bytes":4096,"next":[...],"owner":{"type":"text","text":"Account 7, Free plan: ..."},
#  "tell_your_owner":"Before each call, ...","home":"https://mcp.substrio.com/v1/_agent/link/substrio_al_..."}

# Make an app, commit a function, run it, make it live:
GET .../substrio_account_app_create?id=7&slug=my-app
GET .../substrio_version_commit?app=my-app&message=hello&args=%7B%22units%22%3A%5B...%5D%7D
GET .../substrio_sandbox_create?app=my-app&name=dev
GET .../substrio_live_set?app=my-app&version=1&sandbox=dev
GET .../substrio_route_call?app=my-app&method=GET&path=%2Fhello&sandbox=dev
GET .../substrio_live_set?app=my-app&version=1