Each secret is a key your functions read as env.<KEY> (an API token, a signing key). A value is written and overwritten, never read back: every list answers names only.
Operations
| Operation | Route | MCP tool | What it does |
|---|---|---|---|
secret.write |
PUT /v1/_secrets (or POST) |
substrio_secret_write |
Sets key to value (both required, not empty), replacing a value already there |
secret.list |
GET /v1/_secrets |
substrio_secret_list |
Answers secrets (each {key, value: "***"}) and count |
secret.delete |
DELETE /v1/_secrets/{key} |
substrio_secret_delete |
Removes one secret: answers {success, deleted, key} |
Each write and delete is recorded in the app's audit log. Both are irreversible: the value is never readable, so nothing can put it back (no undo token), and an agent's call to either asks for its approval like any change of authority.
The CLI: substrio secrets set KEY (the value from stdin, --from-file or --from-env, never an argument), substrio secrets list, substrio secrets delete KEY (asks before it runs; --yes skips the question).
Refusals
| Code | Status | When | Field |
|---|---|---|---|
SECRET_NOT_FOUND |
404 | secret.delete of a key the app does not hold |
key |
INVALID_ARGUMENT |
400 | secret.write without a key or a value |
Worked example: an agent sets and removes a secret
Every call is a plain MCP tools/call with the account key on the account's MCP server; app names the app.
[
{"name": "substrio_secret_write", "arguments": {"app": "shop", "key": "API_TOKEN", "value": "..."}},
{"name": "substrio_secret_list", "arguments": {"app": "shop"}},
{"name": "substrio_secret_delete", "arguments": {"app": "shop", "key": "API_TOKEN"}}
]
The delete answers {"success": true, "deleted": true, "key": "API_TOKEN"}. The same delete again is refused with SECRET_NOT_FOUND and field: "key", so the agent corrects the name from secret.list rather than retrying.
Worked example: a site and a function that reads the secret, shipped with no hash
The function reads the secret as env.API_TOKEN (the function's env, a global). Commit the function and the page to the app's history as text, so you compute no hash and upload nothing, then make the version the commit answered live (version 3 here):
[
{"name": "substrio_version_commit", "arguments": {"app": "shop", "branch": "main", "message": "with a token",
"units": [
{"path": "functions/status.js", "text": "export default async () => new Response(env.API_TOKEN ? 'ready' : 'no token');\n"},
{"path": "functions/status.json", "text": "{\"routes\":[\"GET /status\"]}"},
{"path": "site/index.html", "text": "<!doctype html><h1>Shop</h1>\n"}]}},
{"name": "substrio_live_set", "arguments": {"app": "shop", "version": 3}}
]
See versioning ("Developing over MCP": tree, read, edit, search, commit, promote) and static sites (large files uploaded with no hash).