Model
Secrets are a vault on a claimed site. Values are encrypted at rest (AES-GCM); only names are ever listed back — there's no "reveal value" anywhere, including the API. MCP does not set secrets — claim first, then use the project UI, CLI, or API.
Project UI
- Claim the site (see Claim).
- Open the project → Secrets.
- Add name + value → Save.
CLI
aft login
cd your-project # aft.json / .aft/state.json present
aft env list
aft env set ANTHROPIC_API_KEY=sk-…
aft env set OTHER_KEY "value with spaces"
aft env unset ANTHROPIC_API_KEY
Requires login and ownership of the claimed site. Same vault as
the dashboard — aft secrets is an alias for
aft env.
API
# names only
curl https://api.aft.page/v1/sites/{slug}/secrets \
-H "Authorization: Bearer …" # or Cookie session
curl -X PUT https://api.aft.page/v1/sites/{slug}/secrets/ANTHROPIC_API_KEY \
-H "Content-Type: application/json" \
-H "Authorization: Bearer …" \
-d '{"value":"sk-…"}'
curl -X DELETE https://api.aft.page/v1/sites/{slug}/secrets/ANTHROPIC_API_KEY \
-H "Authorization: Bearer …"
Owner or editor session, or a valid deploy edit token, can call
these. GET only ever returns names.
aft.json capabilities
Declare intended names so approve-on-deploy can show them to the owner before anything is granted:
{
"name": "my-app",
"runtime": "next",
"capabilities": {
"secrets": ["ANTHROPIC_API_KEY"],
"egress": ["api.anthropic.com"]
}
}
This is a declaration, not a grant — see Capabilities for the full approve-on-deploy flow.
Where secrets actually go
Values live in the encrypted aft vault. For next /
worker sites with a live upstream Worker,
aft env set also syncs to that Worker via the
Same names as process.env in your
app). Static sites never see secrets at runtime.
Set secrets after the site Worker exists (post-deploy). If you set them earlier, they stay in the vault and sync when the upstream registers.
Try URLs: sqlite only
Paste-repo on Run does not provision Postgres
or MySQL. The try box can use a local sqlite file when the app
already has an ORM engine switch (Django
DATABASES, Flask/FastAPI + SQLAlchemy via
DATABASE_URL / SQLALCHEMY_DATABASE_URI).
Apps that talk the Postgres/MySQL wire protocol
(pg, mysql2, Prisma
postgresql, raw psycopg without Django,
Phoenix/Ecto with postgrex /
Ecto.Adapters.Postgres, Rails with
gem pg and no sqlite3) fail honestly. We
do not rewrite those adapters. Self-healing runtime installs
missing language toolchains (Ruby, Elixir, pip) — not databases.
D1 is a Worker binding for apps built on Code — not a drop-in for
pg.
After you claim the URL, add a real
DATABASE_URL under
Secrets and re-run.
Next: Custom domains — put a claimed site on your own hostname.