What it is
aft.page hosts small software — one-pagers, prototypes, agent-built HTML, static SPAs — at a shareable HTTPS URL in seconds. The name comes from the aft of a ship: the back of the vessel — take what you built and put it out into the world.
Prefer the MCP tools below. If MCP is unavailable, call the API directly.
Quick start
Thin remote MCP — one deploy tool (html or files),
plus aft_deploys, aft_rollback,
aft_health. No account. Point your host at:
https://mcp.aft.page/mcp
MCP host config (preferred)
Add a server named aft-page with the remote URL
(Cursor, Claude Desktop, and other Streamable HTTP clients):
{
"mcpServers": {
"aft-page": {
"url": "https://mcp.aft.page/mcp"
}
}
}
Reload the host, then ask:
Deploy this HTML to aft.page
The agent should call deploy and return a live
URL.
Tools
aft.page does not build your app. Detect first: plain HTML →
upload those files; Vite/React → npm run build then
dist/ only; Next static export → out/.
Never upload src/ or node_modules.
deploy
One tool. Pass html or files —
not both. Goes live immediately. One-file
pages use html; multi-file / SPA build output uses
files (always include index.html).
| Input | Type | Required | Notes |
|---|---|---|---|
html |
string | one of | Full document; prefer <!DOCTYPE html>…</html> |
files |
array | one of | Built static files. Paths relative; UTF-8 or base64. |
preferred_slug |
string | no |
Hint like about-me. First hit never
overwrites — collision gets a suffix. Required with
edit_token.
|
edit_token |
string | no |
From first deploy / .aft/state.json. When
set, MCP PATCHes this slug — same URL, new rollback row.
|
Returns text + structured content:
Live: https://{slug}.aft.page
Claim: https://aft.page/claim?token=…
slug: …
deploy: dep_…
files: 1
Claim within 60 minutes or drop again.
Limits: 500 files · 25 MB each · 100 MB total. Paths: no
.., no leading /, no
\.
aft_deploys
List rollback-able deploys for a slug. Needs
edit_token from .aft/state.json. Same
D1 history as the project UI after claim.
aft_rollback
Point the live slug at a prior deploy_id. Same URL.
Unclaimed: edit_token. After claim, the anonymous
token is retired: the owner mints one with
POST /v1/sites/{slug}/deploy-token while signed in
and hands it to the agent (DELETE revokes it).
aft_health
Ping the deploy API. No inputs. Returns
ok and the API base.
Prompt: deploy_to_aft
Optional prompt that steers the agent:
- Detect first: plain HTML vs JS app (Vite/React/Next).
- Call
deploywithhtmlorfiles. - Vite/React:
npm run build, thendist/only. Next static export:out/. - Pass
preferred_slugwhen sensible. Persist.aft/state.json; later deploys sendedit_token. - Return the live HTTPS URL as the deliverable.
- Do not invent account or repository setup steps.
Arg: html_or_files (optional context string).
API (no MCP)
Base: https://api.aft.page.
CORS: * on deploy + site GET.
GET /health
{ "ok": true }
POST /v1/deploy
Query: optional ?slug=preferred-name. Optional header
X-Aft-Client: mcp|web|extension|curl|cli|mac (metrics).
Three body
shapes:
A) Raw HTML
POST /v1/deploy?slug=hello
Content-Type: text/html; charset=utf-8
<!DOCTYPE html><html><body><h1>Hi</h1></body></html>
B) JSON files (agents)
POST /v1/deploy
Content-Type: application/json
{
"files": [
{ "path": "index.html", "content": "<h1>App</h1>" },
{ "path": "style.css", "content": "body{margin:0}" }
]
}
C) Multipart — fields
file0 + file0_path, …
Success (200)
{
"ok": true,
"slug": "hello",
"deployId": "dep_…",
"url": "https://hello.aft.page",
"files": 1,
"bytes": 42,
"editToken": "aft_edit_…",
"claimUrl": "https://aft.page/claim?token=…",
"owned": false,
"notice": "Claim within 60 minutes or drop again."
}
Errors include no_files,
too_many_files, file_too_large,
payload_too_large, bad_path,
reserved_slug, slug_exhausted,
internal. Invalid ?slug= patterns are
ignored (random unique slug); only reserved names return
reserved_slug.
Serving
| URL | Behavior |
|---|---|
https://{slug}.aft.page/ |
Primary — index.html |
https://{slug}.aft.page/{path} |
Static asset |
https://api.aft.page/s/{slug}/ |
Path fallback |
If the requested object is missing, the Worker soft-falls back to
that deploy’s index.html (SPA-style). Content-Type
follows the file extension when the upload type is missing or
application/octet-stream (so CSS is
text/css).
Slugs
- Prefer valid
?slug=/preferred_slugwhen provided. - Else allocate a random unique slug.
-
Clients (paste UI, extension, agents) may derive a hint from
<title>/<h1>and pass it as?slug=— the Worker does not parse HTML for a name. -
First POST never overwrites — append a short
word suffix on collision. PATCH with
edit_tokenupdates that slug in place. -
Reserved:
www,api,app,admin,docs,status, …
Claim / manage
https://aft.page/claim?token=…
The live URL is the product. After deploy, open
claimUrl to add email or Google. Unclaimed sites also
show Claim on the live slug. After claim, Manage opens the project
page (invite, rollback, secrets, custom domains — first one free).
Keep editToken to prove you deployed.
Limits (today)
| Limit | Value |
|---|---|
| Files per deploy | 500 |
| Per-file size | 25 MB |
| Total payload | 100 MB |
| Auth | None (anonymous) |
| Unclaimed retention | Deleted after 30d idle. Visit, PATCH, or claim to keep. |
| Overwrite | Never |
| Custom domains / private invite | After claim · first one free (Domains tab) |
Examples
Minimal HTML via curl
curl -sS -X POST "https://api.aft.page/v1/deploy?slug=curl-smoke" \
-H "content-type: text/html; charset=utf-8" \
--data-binary @index.html
Agent rule of thumb
- Prefer MCP
deploy(htmlorfiles). - Return the live URL.
- Optionally link the preview shell.
- Do not invent auth steps that do not exist yet.
Troubleshoot
| Symptom | Fix |
|---|---|
| MCP tools missing | Reload host; check absolute path; Node 20+ |
reserved_slug |
Change title / slug |
| Size errors | Stay under 25 MB / file, 100 MB total |
| Unstyled CSS | Ensure path ends in .css; re-deploy |
| Site 404 | Wait for DNS; try /s/{slug}/ on API host |