This is the full developer documentation for aeria
# Overview
> An MCP server. Your own agent makes apps with its tools, on Cloudflare.
aeria is an MCP server. You use it from Claude Code, Claude Desktop, claude.ai or any client that speaks MCP: your own agent calls aeria’s tools. aeria has no agent of its own.
With its tools, your agent makes apps. Each app starts from the seed app, its code kept in Cloudflare Artifacts, built in a sandbox (a Cloudflare container), run on `aeria.run`. What you or your agent make belongs to a workspace: its members, apps, artifacts, connections and events.
## The flow
[Section titled “The flow”](#the-flow)
Every change to an app follows the same five steps, each one tool call from your client:
```
1 create app_create the seed forked, production running
2 branch app_branch_create a branch from main: a session, its sandbox
3 change app_file_write, in the branch's sandbox
app_command_run
4 preview app_preview the branch built, on its own host
5 promote app_promote merged when not behind main,
production updated
```
A session is a branch is a sandbox: each open branch has its own sandbox, its own preview on its own host, and its own events. Production runs main; a merge is the only way to promote. Each step in detail: [The flow](/flow/).
## What your client does, and what aeria does
[Section titled “What your client does, and what aeria does”](#what-your-client-does-and-what-aeria-does)
* **Your agent** reads, writes and runs code through aeria’s tools. It never runs git and never holds a credential: Claude Code, Claude Desktop and claude.ai work the same way.
* **aeria** runs every code tool as an exec in the branch’s sandbox, builds, keeps each build, and uploads it with its own key.
* **You** answer what only a person may: a call held at `ask`, a secret typed on `app.aeria.dev`, your sign-in, your consent to a client.
Every tool call is an event, pushed to the web app on `app.aeria.dev` as it happens.
## The guides
[Section titled “The guides”](#the-guides)
* [Connect a client](/connect/): Claude Code, claude.ai, any MCP client, at `app.aeria.dev/mcp`.
* [The flow](/flow/): create, branch, change, preview, promote; what each refusal means.
* [Tools](/tools/): every tool by area, each with its level (`allow`, `ask`, `deny`).
* [Connections](/connections/): Stripe, Shopify or any service, called through aeria without your client ever holding its key.
* [Artifacts](/artifacts/): pages, PDFs, sheets, emails and videos your agent makes, kept in versions.
* [Security](/security/): what holds, in plain words.
* [Words](/words/): one word per thing, the same in every tool and every guide.
# Artifacts
> Pages, PDFs, sheets, emails and videos your agent makes, kept in versions.
An artifact is what your agent makes beyond its answer: a page, a PDF, a sheet, an email, a video. Each is a git repository in Cloudflare Artifacts, started from its kind’s template, built in a sandbox, kept in versions, opened at its link.
## Why a sandbox
[Section titled “Why a sandbox”](#why-a-sandbox)
An artifact is code that renders: React to HTML, a PDF, a spreadsheet, a video with its voice. That takes tools no MCP client holds (claude.ai runs nothing): a browser, LibreOffice, ffmpeg, a voice. aeria’s sandbox holds them all, pinned, so an artifact builds the same way every time, offline.
An artifact is a session of its own: one sandbox, its own events.
## The kinds
[Section titled “The kinds”](#the-kinds)
```
kind what opens shown as
page dist/index.html a frame
pdf dist/document.pdf a PDF
email dist/email.html a frame, no script
excel dist/book.xlsx a table (with dist/book.json)
video dist/video.mp4 a player (with dist/poster.png)
```
Each template’s `CLAUDE.md` says how to change it; your agent reads it like any file.
## Its life
[Section titled “Its life”](#its-life)
```
pulled artifact_pull {artifact, kind}: a new one from its kind's
template; the same name again: the one there
changed artifact_file_read reads a file (no sandbox);
artifact_file_write and artifact_command_run run in its
sandbox, each change pushed
saved artifact_save {artifact, message}: built, checked, kept as
its next version
shared artifact_access_set {artifact, access}
archived artifact_archive: hidden, its link refused, its sandbox
purged; its repository, versions and record kept
unarchived artifact_unarchive: found again, its link answers again
```
## A save
[Section titled “A save”](#a-save)
`artifact_save` builds the artifact in its sandbox (10 minutes at most), checks each output, and keeps it as the next version.
* One save at a time: another meanwhile is refused `saving`.
* A failed build is refused `build_failed` with its last 30 lines; nothing is kept.
* An output missing or empty is refused `no_output`; one past 100 MB, `too_large`.
* Saving the same commit as the latest version answers that version.
## Its link
[Section titled “Its link”](#its-link)
```
https://-.aeria.run/ its latest version
https://-.aeria.run/version/ one version
```
It opens its first output, or the one `?path=` names. It is served on `aeria.run`, never on `aeria.dev`: what an agent wrote never runs on aeria’s own domain.
| Access | Who opens it |
| ----------- | ----------------------------------- |
| `self` | its maker only (the default) |
| `workspace` | a signed-in member of its workspace |
| `public` | anyone, no sign-in |
A `self` or `workspace` artifact signs its visitor in for its host first. Another member opening a `self` one is refused `not_allowed`; an archived one answers `no_artifact`.
## Its name
[Section titled “Its name”](#its-name)
Lower-case letters, digits and dashes, 63 at most, unique in its workspace.
## Its tools
[Section titled “Its tools”](#its-tools)
`artifact_pull`, `artifact_file_read`, `artifact_file_write`, `artifact_command_run`, `artifact_save`, `artifact_search`, `artifact_access_set`, `artifact_tags_set`, `artifact_archive`, `artifact_unarchive`. Each with its level: [Tools](/tools/#artifacts).
# Connect a client
> Claude Code, claude.ai or any MCP client, at app.aeria.dev/mcp.
aeria’s MCP server is at:
```
https://app.aeria.dev/mcp
```
Your client connects by OAuth: aeria asks you to sign in, then asks your consent for one workspace. The client then acts in that workspace only.
## Before you connect
[Section titled “Before you connect”](#before-you-connect)
1. Sign in on `app.aeria.dev` with your email: a code is sent to it. A new address ticks the terms and gets its own workspace.
2. Add a passkey if you want one: it signs you in in one step next time.
## Claude Code
[Section titled “Claude Code”](#claude-code)
```
claude mcp add --transport http aeria https://app.aeria.dev/mcp
```
Claude Code opens your browser for the sign-in and the consent. Then ask it something that calls aeria, for example “who am I on aeria?”: `user_whoami` answers your email and your workspaces.
Claude Code answers a held call itself: a tool at `ask` shows its question in Claude Code, and you allow or deny it there.
## claude.ai and Claude Desktop
[Section titled “claude.ai and Claude Desktop”](#claudeai-and-claude-desktop)
Add a custom connector with the URL `https://app.aeria.dev/mcp`, then connect it: the sign-in and the consent open in your browser.
These clients cannot answer a held call themselves: a tool at `ask` waits on `app.aeria.dev/held/`, where you allow or deny it. The call stays open meanwhile; when the client stops waiting first, the call stays held and its outcome is an event (`event_search`).
## Any MCP client
[Section titled “Any MCP client”](#any-mcp-client)
Any client that speaks MCP over HTTP and OAuth 2.1 connects the same way. aeria knows how long each client waits and whether it can answer a held call; a client it does not know gets the shortest wait and no question of its own.
## What your client learns
[Section titled “What your client learns”](#what-your-client-learns)
* **aeria’s instructions**, sent when the client connects: what aeria is, the flow, that every code tool names its app and branch, that an `ask` waits for you.
* **Each tool’s description:** what it does, what it refuses, what to call next.
* **Each app’s and artifact’s `CLAUDE.md`**, read like any file: how to change that code.
## Long calls
[Section titled “Long calls”](#long-calls)
A long exec (a build, a test run) answers when it is done, within the time your client waits. Past it, the call answers `{exec, status: running}` with its steps so far and goes on; your agent then calls `exec_wait {exec}`, which answers when the exec ends.
## Your grants
[Section titled “Your grants”](#your-grants)
Each client you allowed is a grant, for one workspace. `grant_search` lists them. You end one on `app.aeria.dev/w//grants`: its tokens end with it, and its next call is refused.
# Connections
> Stripe, Shopify or any service, called through aeria; your client never holds its key.
A connection is your workspace’s link to an outside service: Stripe, Shopify, or any service with an OpenAPI document or a GraphQL schema. Its operations become tools; your agent calls them through aeria, and neither your client nor a sandbox ever holds the service’s key.
## Set one up
[Section titled “Set one up”](#set-one-up)
1. Your agent calls `connection_ask {service}` (a service of aeria’s catalog), or `connection_ask {spec}` for any other.
2. aeria answers a link, `app.aeria.dev/connect/`: yours alone, 15 minutes, used once.
3. You open it, signed in: the service, its hosts, where to find its key, its fields.
4. You type the values. Each secret is sealed in your browser before it leaves it; aeria receives the plain fields and the sealed values only.
A refusal gives the link back. A secret’s value is never sent in clear, never repeated in a refusal, never in an event.
## Its operations as tools
[Section titled “Its operations as tools”](#its-operations-as-tools)
Once set, each operation of the service is a tool:
* **Named** `_`: the operation’s id in snake case (`stripe_get_balance`); a GraphQL query’s or mutation’s field the same (`shopify_products`).
* **Found** with `tool_search {keyword}`, each with its level. A connection whose document could not be read is listed as `unavailable`, with why.
* **At `deny`** until you expose it: `tool_level_set {tool, level}`.
* **Called** with `tool_call {tool, params}`: aeria builds the request, the egress adds the connection’s headers and sends it, the service’s JSON is answered. Each call is an event, its secrets masked.
They are never in your client’s tool list: a client keeps the list it got as it connected, and Stripe’s operations alone are 612.
## Where the key goes
[Section titled “Where the key goes”](#where-the-key-goes)
A connection’s secret is opened in one place only: your workspace’s egress, a Worker that holds your workspace’s key. It adds the secret to the request’s headers toward the connection’s own hosts, and nowhere else (`host_refused`). A preview never opens a connection (`preview`). In the service’s answer, any secret it sent back is replaced by its placeholder.
## A spec
[Section titled “A spec”](#a-spec)
A service of the catalog has its spec already; for another, your agent writes one in YAML:
```
label: Stripe
help: https://dashboard.stripe.com/apikeys
fields:
secret_key: {label: Secret key, secret: true, pattern: "^(sk|rk)_"}
account: {label: Connected account, optional: true}
hosts: [api.stripe.com]
headers:
- {name: Authorization, format: "Bearer {secret_key}"}
- {name: Stripe-Account, format: "{account}"}
openapi: {url: "", digest: ""}
```
```
label the service's name (required)
help where its key is made
fields name: {label, secret, pattern, variable, optional}
hosts its hosts, one at least
token {url, form}: a token exchange the egress runs
headers [{name, format}], from {field} or {token}
openapi {url, digest}: the document, pinned by its sha256
graphql {url}: the endpoint, read by introspection
```
A part that does not read is refused `invalid`. A host on `aeria.run`, where agent-written code runs, is refused `app_host`.
## Its name
[Section titled “Its name”](#its-name)
Lower-case letters, digits and single dashes, starting with a letter, 31 characters at most; not one of aeria’s tools’ first words, not an app’s (`taken`).
## An app is a connection too
[Section titled “An app is a connection too”](#an-app-is-a-connection-too)
Every app is a connection of its workspace, named as the app: its operations read from its production’s `/openapi.json`, its one secret the app’s own key, made by aeria. It is archived with its app.
## Its tools
[Section titled “Its tools”](#its-tools)
`connection_ask`, `connection_search`, `connection_show`, `connection_archive`, `tool_search`, `tool_call`; `connection_set` is the setup page’s only. Each with its level: [Tools](/tools/).
Archived, a connection’s tools and secrets are no longer used; its record is kept, and its name is free again.
# The flow
> Create an app, branch it, change it, preview it, promote it.
What you do from your MCP client, and what aeria does for each call. A session is a branch is a sandbox: the unit of work, with its own sandbox, its own preview, its own events.
```
1 create app_create the seed forked, production running
2 branch app_branch_create a branch from main: a session
3 change app_file_*, app_command_run in the branch's sandbox
4 preview app_preview the branch built, on its own host
5 promote app_promote merged, production updated
```
Every call is an event of its app and branch, pushed to the web app as it happens.
## 1. Create an app
[Section titled “1. Create an app”](#1-create-an-app)
`app_create {name}` makes an app from the seed: its repository forked in Cloudflare Artifacts, its own D1 database and R2 bucket made, the seed’s kept build uploaded as its production. Nothing is built: it answers `{app, url}` at once, production running at `-.aeria.run`.
To find an app: `app_search`, then `app_show` (its host, its open branches, previews and deployments).
## 2. Start a branch
[Section titled “2. Start a branch”](#2-start-a-branch)
`app_branch_create {app, name}` cuts a branch from main: a session. Its sandbox starts at its first exec.
* **Its name:** lower-case letters, digits and single dashes, starting with a letter; never `main`. Its preview’s host `--` is 63 characters at most, refused `taken` when another host has it.
* **The same name again** joins that open branch: the answer warns that another session wrote it (when, its last commit).
* **Every other code tool** names an open branch of that app, else it is refused `no_branch`.
## 3. Change its files
[Section titled “3. Change its files”](#3-change-its-files)
* `app_file_read`, `app_code_search`: read from Cloudflare Artifacts, no sandbox.
* `app_file_write`, `app_file_delete`, `app_file_move`: an exec in the branch’s sandbox: checkout, change, commit, push. It answers `{branch, commit, steps}`.
* `app_command_run {app, branch, command}`: any command in the checkout (`bun add zod`, a script), its output answered, its changes committed and pushed.
* `app_test`, `app_check`: the tests, the lint and types.
* The generators (`app_model_create`, `app_page_create`…): each runs one of the seed’s own commands ([Tools](/tools/#the-generators)).
Your client never runs git and never holds a credential. One exec runs at a time per branch: another call meanwhile is refused `busy`, naming the exec that holds it.
A sandbox stops after a short idle time, its whole disk kept in a snapshot first; its next start restores it in about 1.5 s. When the snapshot is gone (30 days, or a new sandbox image), the start is cold: the checkout cloned again, the answer says so (`cold: true`, why).
## 4. Preview the change
[Section titled “4. Preview the change”](#4-preview-the-change)
`app_preview {app, branch}` runs `[checkout, install, test, build]` in the branch’s sandbox, keeps the bundle, and deploys it on the branch’s own host, `--.aeria.run`. It answers `{url, steps}`.
* **Production is untouched:** the preview has its own script, its own D1 and R2, and its settings’ preview values. It never opens a connection.
* **Its database:** main’s migrations, then the branch’s. `fresh_database: true` makes it again from them (after a migration was renamed or removed).
* **A build depends on its source tree only:** the same tree reuses the kept bundle (`build: "reused"`).
* **A failed step** answers with every step’s output; nothing is uploaded.
## 5. Promote it
[Section titled “5. Promote it”](#5-promote-it)
`app_promote {app, branch}` is at `ask`: it waits for your answer. Once allowed, it promotes exactly the bundle its preview showed:
1. production’s D1 bookmarked (Time Travel), then the branch’s migrations run;
2. the bundle uploaded as production;
3. main moved to the branch, fast-forward;
4. the branch closed and purged: its preview, its D1 and R2, its sandbox and snapshots. Its code lives on in main; its events are kept.
It answers `{url, deployment}`. An upload that fails stops the promote there, with why: production runs its last deployment, main has not moved, the branch stays open to promote again.
### When a promote is refused
[Section titled “When a promote is refused”](#when-a-promote-is-refused)
| Refusal | Why | What your agent does |
| ----------------- | ----------------------------------------------- | ------------------------------------------------------ |
| `behind_main` | main moved since the branch was cut | `app_branch_rebase`, `app_preview`, then promote again |
| `preview_stale` | commits after the branch’s last preview | `app_preview`, then promote again |
| `conflict` | the rebase met a real conflict, its files named | fix the files, rebase again |
| `migration_clash` | two migrations with the same number | rename one (`app_file_move`), rebase again |
A migration only adds: it never drops nor renames a table or a column (the build refuses it), so an earlier deployment still runs on the new schema.
## After
[Section titled “After”](#after)
* **Roll back:** `app_rollback` uploads an earlier kept bundle. Production runs main, but after a rollback, which serves the earlier deployment until the next promote.
* **Restore data:** `app_database_restore` puts production’s D1 back to a point in time, within its last 30 days.
* **Close a branch unpromoted:** `app_branch_close` purges it as a promote does; its commits stay in the repository, its events are kept.
* **Archive an app:** `app_archive` hides it, stops its schedules, and closes its open branches; its host stops serving. Its repository, database, bucket and bundles are kept: nothing a person owns is deleted.
# Security
> What holds, in plain words.
What aeria guarantees, one statement each.
## Your secrets
[Section titled “Your secrets”](#your-secrets)
* **aeria stores no secret it can open.** It keeps them encrypted (sealed); only your workspace’s egress, a Worker of its own, holds the key that opens them, and aeria keeps no copy of that key.
* **A secret never passes through your MCP client.** No tool takes a secret’s value and none answers one. You type it on `app.aeria.dev`, where your browser seals it before it is sent.
* **A secret is never in a sandbox or in an app’s code.** They hold a placeholder, `aeria-secret:`. The egress puts the secret in its place on the way out, toward the hosts declared for it only, in a header, over https.
* **A new host waits for you.** When a promote adds a host a secret goes to, that secret is held until its owner allows the new host on `app.aeria.dev`; meanwhile the new host is refused `host_refused`.
## Setting a secret
[Section titled “Setting a secret”](#setting-a-secret)
1. Your agent calls `app_setting_ask {app, name}` (or `connection_ask` for a [connection](/connections/)).
2. aeria answers a link to `app.aeria.dev`, valid minutes; never a value.
3. You open it signed in and type the value; the page seals it to your workspace’s key and sends only the sealed value.
4. The app’s code gets the placeholder; the egress opens the value for one request at a time.
## Sandboxes
[Section titled “Sandboxes”](#sandboxes)
* **One sandbox per session:** each open branch and each artifact runs in a microVM of its own.
* **No Cloudflare key in a sandbox, ever.** Only aeria’s controlplane holds it; every upload is aeria’s, never the sandbox’s.
* **Git through aeria only:** a sandbox reaches its repository with a placeholder, swapped for a token minted for that repository, minutes long. A branch can push to itself, never to main; main moves only by aeria’s own merge at a promote.
* **Read-only elsewhere:** the package registries (npm, PyPI…) for reading; nothing else.
## Signing in and clients
[Section titled “Signing in and clients”](#signing-in-and-clients)
* **You sign in** on `app.aeria.dev` with an email code (one use, minutes) or a passkey. There is no password.
* **An MCP client connects by OAuth**, with your consent for one workspace. Its access token lasts an hour, its refresh token is rotated, and both act in that workspace only. You revoke a grant on `app.aeria.dev`; its tokens end with it.
* **Tokens and codes are stored hashed**, never in clear.
## Every call is an event
[Section titled “Every call is an event”](#every-call-is-an-event)
Every tool call, a refused one too, is an event of your workspace: who, which client, what was asked, the outcome. A secret param is masked and a secret answer field is null. Events are kept.
## What your apps run on
[Section titled “What your apps run on”](#what-your-apps-run-on)
* **Apps run on `aeria.run`, never on `aeria.dev`:** an app’s code can never set a cookie for aeria.
* **One app cannot read another’s cookies.** Only cookies named `__Host-…` count on `aeria.run`; every other cookie is dropped before an app runs, and a cookie set for the whole domain is stripped from its answer.
* **A private app or artifact** signs its visitor in for that one host: a member of the workspace (or the artifact’s maker, for `self`) is sent back with a one-use code, and the host gets its own cookie. A visitor who may not see it is refused before the app’s code runs.
* **Each app is isolated** in Cloudflare’s Workers for Platforms: every `fetch()` it makes passes aeria’s outbound Worker (but a Durable Object’s), and it opens no raw connection.
* **Nothing is public until `aeria.run` is on the Public Suffix List:** until then, making an app or an artifact `public` is refused `public_suffix_pending`.
## Keys rotated, keys lost
[Section titled “Keys rotated, keys lost”](#keys-rotated-keys-lost)
When your workspace’s egress key is lost, a new one is made, and each secret sealed to the old one is refused `unopened` until you set it again; `app_setting_search` names them.
## Erasure
[Section titled “Erasure”](#erasure)
When you ask to be erased, your email, name and passkeys are deleted, your sign-ins and grants ended; your events are kept under your id only, every email in them masked. A workspace you were the only owner of is archived.
# Tools
> Every tool of aeria, by area, each with its level.
Everything aeria does is a tool. Your MCP client calls it on `/mcp`; the same tool is a route under `/api/v1`; every call is an event.
## The levels
[Section titled “The levels”](#the-levels)
Each tool has one level in your workspace:
```
allow called at once
ask held until you allow or deny it
deny not offered; a call is refused unknown_tool
```
A tool marked `none` below is never offered to an MCP client: it is a value you type, your own word (a sign-in, a consent), or an operator’s decision. You do it on `app.aeria.dev`.
* **Change a level:** `tool_level_set {tool, level}`. It is at `ask` itself: a raise is answered by an owner only, so your agent never widens its own powers. Your client is told to list its tools again.
* **A held call** pauses your client’s call until you answer: in the client itself when it can ask (Claude Code), else on `app.aeria.dev/held/`. Allowed, it runs as you; denied, it is refused `denied`, with who and when.
## Finding and calling tools
[Section titled “Finding and calling tools”](#finding-and-calling-tools)
* **`tools/list`** holds aeria’s own tools not at `deny`.
* **`tool_search {keyword}`** finds aeria’s tools and every connection’s operations, each with its level.
* **`tool_call {tool, params}`** calls an operation (a connection’s or an app’s) at its own level.
A tool that lists is `_search`: it takes `keyword`, `page` and `per_page` (20 by default, 100 at most) and answers `page`, `per_page` and `total_count`.
## Sign-in and workspaces
[Section titled “Sign-in and workspaces”](#sign-in-and-workspaces)
| Tool | Level | What it does |
| ------------------- | ----- | ------------------------------------------------------------- |
| `user_whoami` | allow | you and your workspaces |
| `grant_search` | allow | the MCP clients you allowed, each its workspace |
| `grant_revoke` | none | a client’s grant ended, its tokens with it |
| `workspace_create` | none | a workspace, you its owner |
| `workspace_search` | allow | your workspaces |
| `workspace_show` | allow | its name, members, apps’ count and public key |
| `workspace_set` | allow | its display name; never its slug |
| `workspace_archive` | none | its apps archived, its tokens ended, nothing deleted |
| `tool_search` | allow | aeria’s tools and connections’ operations, each its level |
| `tool_level_set` | ask | one tool’s level in the workspace |
| `event_search` | allow | the workspace’s events: by app, branch, person, tool |
| `exec_wait` | allow | a running exec, answered when it ends |
| `held_search` | allow | the calls waiting for a person |
| `held_answer` | none | a held call allowed or denied |
| `session_search` | allow | the open branches and artifacts, each its sandbox and preview |
| `member_search` | allow | the workspace’s members |
| `member_invite` | ask | an email invited, with its role |
| `member_role_set` | ask | a member’s role |
| `member_archive` | ask | a member removed, their record kept |
| `billing_show` | allow | its plan, balance, limit, reload, card’s last digits |
| `billing_limit_set` | ask | its spending limit, within the plan’s |
| `usage_search` | allow | its usage by meter, app, branch or day |
Also yours alone, on `app.aeria.dev`: `terms_accept`, `user_logout`, `passkey_add`, `invitation_accept`, `billing_reload_set` (each `none`).
## An app and its code
[Section titled “An app and its code”](#an-app-and-its-code)
Every code tool names an open branch of its app and runs as an exec in that branch’s sandbox, one at a time ([The flow](/flow/)).
| Tool | Level | What it does |
| ------------------- | ----- | ------------------------------------------------------------ |
| `app_create` | allow | an app from the seed, its production running |
| `app_show` | allow | its host, open branches, previews, last deployments |
| `app_search` | allow | the workspace’s apps |
| `app_archive` | ask | hidden, its schedules stopped, nothing deleted |
| `app_access_set` | ask | `private` (its workspace’s members) or `public` (anyone) |
| `app_branch_create` | allow | a branch cut from main; the same name joins it |
| `app_branch_search` | allow | its branches, each its sandbox’s state and preview |
| `app_branch_close` | ask | closed unpromoted and purged; its commits and events kept |
| `app_code_search` | allow | its files at a branch, no sandbox |
| `app_file_read` | allow | one file at a branch, no sandbox |
| `app_file_write` | allow | checkout, write, commit, push |
| `app_file_delete` | allow | checkout, remove, commit, push |
| `app_file_move` | allow | checkout, move, commit, push |
| `app_command_run` | allow | any command in the checkout, its changes pushed |
| `app_seed_update` | allow | the seed’s changes since the app’s fork merged into a branch |
## Build, preview, promote
[Section titled “Build, preview, promote”](#build-preview-promote)
| Tool | Level | What it does |
| ----------------------- | ----- | ------------------------------------------------------------------- |
| `app_test` | allow | the tests |
| `app_check` | allow | the lint and types |
| `app_preview` | allow | built, deployed on the branch’s own host |
| `app_promote` | ask | the preview’s bundle made production, main moved, the branch purged |
| `app_branch_rebase` | allow | the branch rebased onto main |
| `app_deployment_search` | allow | production’s deployments, the current one |
| `app_rollback` | ask | an earlier deployment served again |
| `app_database_restore` | ask | production’s D1 back to a point in time (30 days) |
| `app_log_search` | allow | the app’s logs |
## An app’s data and files
[Section titled “An app’s data and files”](#an-apps-data-and-files)
| Tool | Level | What it does |
| ------------------- | ----- | ------------------------------------------------------- |
| `app_sql_run` | ask | any statement on production’s D1 or a preview’s |
| `app_model_search` | allow | its models, read from the code at a branch |
| `app_model_show` | allow | one model, and whether production runs it |
| `app_object_url` | none | a signed URL for one object of the app’s bucket |
| `app_backup_create` | allow | production’s D1 exported |
| `app_backup_search` | allow | its backups |
| `app_backup_load` | allow | a backup loaded into a preview’s D1, never production’s |
## Settings, secrets and connections
[Section titled “Settings, secrets and connections”](#settings-secrets-and-connections)
No tool takes or answers a secret’s value: you type it on `app.aeria.dev` ([Security](/security/)).
| Tool | Level | What it does |
| --------------------- | ----- | ------------------------------------------------ |
| `app_setting_ask` | allow | a link for you to type a secret setting |
| `app_setting_set` | none | one setting, a secret sealed in your browser |
| `app_setting_search` | allow | its settings, the missing ones named |
| `app_setting_archive` | ask | a setting removed, its record kept |
| `app_secret_rotate` | none | the app’s own key replaced, by the owner |
| `connection_ask` | allow | a link to a connection’s setup page |
| `connection_set` | none | the setup page’s: its fields, its secrets sealed |
| `connection_search` | allow | the workspace’s connections, never a value |
| `connection_show` | allow | one connection, never a value |
| `connection_archive` | ask | archived, its tools and secrets no longer used |
## Schedules and webhooks
[Section titled “Schedules and webhooks”](#schedules-and-webhooks)
| Tool | Level | What it does |
| -------------------------------------------------- | ----- | ------------------------------------ |
| `app_schedule_search`, `app_schedule_show` | allow | read from the code at a branch |
| `app_schedule_run_search`, `app_schedule_run_show` | allow | its runs |
| `app_schedule_pause`, `app_schedule_resume` | allow | production’s |
| `app_schedule_trigger` | allow | one run now, production or a preview |
| `app_webhook_search`, `app_webhook_show` | allow | read from the code |
| `app_webhook_delivery_search` | allow | its deliveries, asked of the app |
## Publishing, installing, calling operations
[Section titled “Publishing, installing, calling operations”](#publishing-installing-calling-operations)
| Tool | Level | What it does |
| ------------------- | ----- | ------------------------------------------------------------ |
| `app_publish` | ask | listed for other workspaces once an operator’s review passes |
| `app_public_search` | allow | the public apps |
| `app_install` | ask | a public app installed in another workspace |
| `tool_call` | allow | an operation called, at its own level |
## Artifacts
[Section titled “Artifacts”](#artifacts)
| Tool | Level | What it does |
| ---------------------- | ----- | ------------------------------------------------- |
| `artifact_pull` | allow | a new artifact of a kind, or the one of that name |
| `artifact_file_read` | allow | one file of it, no sandbox |
| `artifact_file_write` | allow | written, committed, pushed in its sandbox |
| `artifact_command_run` | allow | any command in its checkout |
| `artifact_save` | allow | built, checked, kept as its next version |
| `artifact_search` | allow | the workspace’s artifacts, newest first |
| `artifact_access_set` | ask | `self`, `workspace` or `public` |
| `artifact_tags_set` | allow | its tags |
| `artifact_archive` | ask | hidden, its link refused, nothing deleted |
| `artifact_unarchive` | allow | found again |
## Generated by connections
[Section titled “Generated by connections”](#generated-by-connections)
A connection adds one tool per operation of its service, named `_` ([Connections](/connections/)). An app adds its own the same way, from its production’s `/openapi.json`. Each is found by `tool_search`, called by `tool_call`, and starts at `deny` until you expose it with `tool_level_set`.
| Tools | Made from | Level |
| ---------------- | -------------------------------- | ----- |
| `stripe_*` | Stripe’s OpenAPI, pinned | deny |
| `shopify_*` | Shopify’s GraphQL schema | deny |
| `_*` | its OpenAPI or GraphQL | deny |
| `_*` | its production’s `/openapi.json` | deny |
## The generators
[Section titled “The generators”](#the-generators)
Each changes an app’s code by one of the seed’s own commands, run in the branch’s sandbox (checkout, command, test, commit, push); then `app_preview` shows it.
| Tool | Level | The seed’s command |
| ---------------------- | ----- | -------------------------------------------------------------------- |
| `app_add` | allow | `bun run add pages\|storage` |
| `app_remove` | ask | `bun run remove pages\|storage` |
| `app_model_create` | allow | `bun run model create ...` |
| `app_model_change` | allow | `bun run model change ` |
| `app_namespace_create` | allow | `bun run namespace create --sign-in ` |
| `app_namespace_change` | allow | `bun run namespace change ` |
| `app_page_create` | allow | `bun run page create --path --namespace ` |
| `app_page_change` | allow | `bun run page change ` |
| `app_schedule_create` | allow | `bun run schedule create ""` |
| `app_schedule_change` | allow | `bun run schedule change ` |
| `app_webhook_create` | allow | `bun run webhook create --verify --secret ` |
| `app_webhook_change` | allow | `bun run webhook change ` |
Their reads, each `allow`: `app_namespace_search`, `app_namespace_show`, `app_page_search`, `app_page_show`.
## Operators
[Section titled “Operators”](#operators)
aeria’s own staff’s tools, each `none`, refused `not_operator` to anyone else: `app_takedown`, `app_reinstate`, `artifact_takedown`, `artifact_reinstate`, `app_review`, `app_limits_set`, `user_erase`.
## Refusals
[Section titled “Refusals”](#refusals)
A refusal says why: `{error, message}` with its HTTP status, one code per refusal in every tool.
| Code | HTTP | Meaning |
| ----------------------- | ---- | -------------------------------------------------- |
| `busy` | 409 | the sandbox runs another exec |
| `no_branch` | 404 | no open branch of that name in the app |
| `behind_main` | 409 | main moved since the branch was cut |
| `preview_stale` | 409 | commits after the branch’s last preview |
| `conflict` | 409 | a rebase’s conflict, its files named |
| `migration_clash` | 409 | two migrations with the same number |
| `sandbox_limit` | 429 | the plan’s sandboxes at once, each running an exec |
| `app_limit` | 429 | the plan’s apps |
| `branch_limit` | 429 | the plan’s open branches per app |
| `payment_required` | 402 | the workspace is stopped until paid |
| `taken` | 409 | a name or a host already taken |
| `saving` | 409 | the artifact has a save running |
| `build_failed` | 422 | the build failed, its last lines answered |
| `no_output` | 422 | a declared output missing or empty |
| `too_large` | 413 | an output past 100 MB |
| `unopened` | 409 | a secret sealed to a lost key: set it again |
| `host_refused` | 403 | a secret’s host not declared or not allowed |
| `app_host` | 403 | a host on aeria.run, where agent-written code runs |
| `restore_failed` | 502 | the point in time was not restored |
| `public_suffix_pending` | 409 | aeria.run is not on the Public Suffix List yet |
| `top_up_required` | 402 | going public needs a first top-up |
| `not_public` | 403 | an installed app no longer listed |
| `taken_down` | 409 | the host is taken down |
| `not_operator` | 403 | an operator’s tool, called by anyone else |
| `not_allowed` | 403 | a visitor the access does not let in |
| `no_artifact` | 404 | no such artifact, or an archived one |
| `last_owner` | 409 | the workspace’s last owner demoted or removed |
| `an_app` | 409 | an app’s own connection, set or archived as one |
| `invalid` | 422 | a param or a spec that does not read |
| `not_sealed` | 422 | a secret’s value not in the sealed shape |
| `denied` | 403 | a held call denied, with who and when |
| `unknown_tool` | 404 | no such tool, or one at `deny` |
| `variable_taken` | 409 | a secret’s variable already used |
| `no_secret` | 403 | a placeholder of no live secret |
| `token` | 503 | a connection’s token exchange refused |
| `preview` | 403 | a preview never opens a connection |
# Words
> One word per thing, per verb and per state, in every tool and every guide.
aeria uses one word for each thing, verb and state: the same in its tools, their answers, the web app and these guides.
## Your workspace
[Section titled “Your workspace”](#your-workspace)
| Word | What it is |
| --------- | ------------------------------------------------------------------------------------- |
| workspace | what your work belongs to: its members, apps, artifacts, connections, events, billing |
| member | a person of a workspace: `owner` or `member` |
| grant | an MCP client you allowed for one workspace |
| sign-in | your signed-in browser on `app.aeria.dev`; never called a session |
| event | one tool call, or one line of an exec, kept in order |
| level | a tool’s in a workspace: `allow`, `ask`, `deny` |
| held | a call at `ask`, paused until you allow or deny it |
| slug | a workspace’s, an app’s, an artifact’s name in hosts: set at creation, never changed |
## Apps
[Section titled “Apps”](#apps)
| Word | What it is |
| ---------- | --------------------------------------------------------------------- |
| app | a service a workspace hosts, made from the seed |
| seed | the app every app starts from |
| session | an app’s open branch, or an artifact, with its sandbox |
| branch | an app’s line of work off main: a session |
| preview | a branch running on its own host, never production |
| production | an app’s main, running at `-.aeria.run` |
| bundle | a build’s output: the Worker’s modules and its migrations |
| deployment | one bundle uploaded as an app’s production |
| promote | a branch merged into main, its bundle a deployment |
| close | a branch ended: promoted, or closed (`app_branch_close`); then purged |
| purge | a closed branch’s preview, sandbox and snapshots deleted |
| archive | hidden, nothing deleted |
| restore | production’s data back to a point in time |
| load | a backup put into a preview’s database |
| schedule | an app’s code run at its cron expression |
| namespace | a group of an app’s callers (its users, grants, tenancy) |
| tenant | a group of a namespace’s users whose rows are theirs |
## Sandboxes
[Section titled “Sandboxes”](#sandboxes)
| Word | What it is |
| ---------- | ---------------------------------------------------------------------- |
| sandbox | a session’s container: where its code is written, tested and built |
| exec | one run of aeria’s steps in a sandbox |
| line | one line of an exec’s output: an event of its call |
| snapshot | a sandbox’s whole disk, kept when it stops, restored at its next start |
| repository | an app’s or an artifact’s git, in Cloudflare Artifacts |
## Artifacts and connections
[Section titled “Artifacts and connections”](#artifacts-and-connections)
| Word | What it is |
| ---------- | ----------------------------------------------------------------------------------------- |
| artifact | what your agent makes beyond its answer: a page, a PDF, a sheet, an email, a video |
| kind | an artifact’s type: `page`, `pdf`, `email`, `excel`, `video` |
| version | an artifact’s kept build, numbered |
| save | an artifact’s build kept as its next version |
| unarchive | an artifact found again |
| connection | a workspace’s link to an outside service |
| operation | one call of a connection’s or an app’s API, a tool |
| egress | the Worker that puts a secret in place of its placeholder, toward the secret’s hosts only |
## Billing
[Section titled “Billing”](#billing)
| Word | What it is |
| -------------- | ---------------------------------------------------------- |
| plan | a workspace’s fee, quota, limits and rates |
| quota | the usage a plan’s fee brings, pooled across the workspace |
| seat | a member past the first, paying the plan’s fee |
| balance | a workspace’s prepaid credit |
| top-up | credit bought with the card |
| reload | a top-up charged automatically under a set floor |
| spending limit | what a workspace may spend a month, set by the owner |
| meter | one kind of usage, metered and priced |
| ledger | a workspace’s every credit and debit |
## aeria’s staff
[Section titled “aeria’s staff”](#aerias-staff)
| Word | What it is |
| --------- | ------------------------------------------------------------------------------------- |
| operator | one of aeria’s own staff: reviews published apps, takes a host down and reinstates it |
| takedown | a host no longer served, by an operator; nothing deleted |
| reinstate | a taken-down host served again |