Why capabilities
An agent can build an app that wants more than static files —
an API key, a webhook it needs to call, read access to a data
source. aft.json lets that app say so up front,
by name, instead of silently reaching for it. The owner of the
site reviews exactly what was requested and decides whether to
approve it — the same shape as a permission prompt, not a
blank check.
aft.json shape
{
"name": "expense-approval",
"runtime": "static",
"capabilities": {
"data": ["expenses:read", "expenses:approve"],
"secrets": ["slack-webhook"],
"egress": ["hooks.slack.com"]
}
}
| Field | Meaning |
|---|---|
data |
Named data actions the app wants, like expenses:read. |
secrets |
Secret names the app expects to exist — see Secrets / env. |
egress |
Outbound hosts the app expects to call, like hooks.slack.com. |
Approve-on-deploy
If a deploy includes an aft.json with a
capabilities block, aft.page records the request
and includes it in the deploy response — that's the first
place an owner sees what an app is asking for. Nothing is
granted automatically:
- pending — requested, not yet approved.
- approved — the owner accepted the request (all of it, or a subset), or a prior approval already covers it.
Only the site owner can approve — from the project dashboard, or the API below.
API
# see what's requested / approved
curl https://api.aft.page/v1/sites/{slug}/capabilities -H "Cookie: …"
# approve everything that's currently requested
curl -X POST https://api.aft.page/v1/sites/{slug}/capabilities -H "Cookie: …"
# approve a specific subset instead
curl -X POST https://api.aft.page/v1/sites/{slug}/capabilities \
-H "Content-Type: application/json" -H "Cookie: …" \
-d '{"approved":{"data":["expenses:read"],"secrets":[],"egress":[]}}'
GET returns
{ slug, capabilities: { requested, approved, status, summary } }
(or capabilities: null if nothing's been
declared). Owner-only; requires a claimed site.
What's enforced today
Declaring and approving is real. Enforcing is partial. Be precise about what each field buys you right now:
- secrets — the vault itself is shipped (store, list names, delete); declaring a name here just adds it to what the owner reviews. See Secrets / env for the current gap between "stored" and "wired into a running app."
- egress — visibility only. Declaring a host shows the owner what your app intends to call; there's no proxy yet that blocks or allows outbound requests based on this list.
- data — recorded and approvable. Nothing on our side acts on it today.
Treat capabilities as an honest declaration and an
audit trail, not a sandbox — don't rely on it to stop an app
from doing something you haven't approved yet.
Next: Frameworks — what builds cleanly, what's verified, and what still needs a runtime.