REST API reference
The Page Lemon REST API lives at https://api.pagelemon.com/v1. Code agents with a shell use it to publish a folder in one command; AI agents should start with the publishing guide for AIs (Markdown).
Last updated · View as Markdown
Quick start
Publish a folder without an account (it stays online for 72 hours unless you keep it):
tar -czf - -C dist . | curl -sS --data-binary @- https://api.pagelemon.com/v1/deployWith an API token, the site goes straight into your account:
tar -czf - -C dist . | curl -sS -H "Authorization: Bearer $PAGELEMON_TOKEN" --data-binary @- https://api.pagelemon.com/v1/deployHosts
| Host | Serves |
|---|---|
https://pagelemon.com | This site, /agents, /llms.txt, /docs/* |
https://api.pagelemon.com | REST API under /v1 |
https://mcp.pagelemon.com | Remote MCP server (streamable HTTP) at / (alias /mcp) |
https://app.pagelemon.com | Your account: sites, people, tokens, billing |
https://<name>.pagelemon.dev | Your sites |
Auth
Send credentials as Authorization: Bearer <token>.
| Credential | Looks like | Where it comes from | What it can do |
|---|---|---|---|
| Site token | st_… | Returned once by an anonymous deploy | Update, read back, roll back and view that one site, until it's claimed. After the claim it only reports the claim (GET /v1/sites/:name) |
| Claim link | https://app.pagelemon.com/claim/ct_… | Returned once by an anonymous deploy | A signed-in person claims the site |
| API token | pl_… | app.pagelemon.com/account/tokens (Account settings → API tokens; see API tokens) | Everything you can do in your account, over REST and MCP |
| OAuth access token | pla_… | Issued to an MCP app (Claude.ai, ChatGPT, Claude Code…) after you sign in and approve it | The same as an API token, but MCP only (REST answers 401). Lasts 1 hour and renews itself |
We store only hashes of tokens, so a lost token can't be shown again. Create a new one instead.
Errors
{ "error": { "code": "conflict", "message": "…" }, "agent_message": "…" }agent_message is present when the AI should tell the user something. Pass it on in the user's language, with every link, code, number, date and domain exact; times are UTC, and *_at fields have them in ISO.
| Code | Status | Meaning |
|---|---|---|
bad_request | 400 | Bad archive, body or parameter |
unauthorized | 401 | Missing or invalid token |
forbidden | 403 | The token or role can't do this |
not_found | 404 | No such site or file |
conflict | 409 | Stale X-Base-Revision / base_revision |
gone | 410 | The site expired |
too_large | 413 | Over a size limit |
blocked_secret | 422 | The leak guard found a secret key |
limit | 429 | Plan or rate limit |
Deploy
POST /v1/deploy: one archive
The request body is the archive: .tar.gz, .tgz, .tar or .zip. The server detects the format from the bytes, so Content-Type is optional.
tar -czf - -C <dir> . | curl -sS --data-binary @- https://api.pagelemon.com/v1/deploy| Request | Result |
|---|---|
No ?site, no token | Anonymous create. A random name such as brave-otter-4821, expires after 72 hours unless claimed |
No ?site, API token | Create in your account. No expiry |
?site=<name> + X-Base-Revision: <revision id> + site token or API token | Update. If the live revision isn't X-Base-Revision, you get 409 conflict and nothing is published |
Every deploy replaces the whole site: files you leave out are removed from the live site. If every file sits under one top-level folder (dist/index.html, …), that folder is stripped.
Response 201:
{
"site": { "name": "brave-otter-4821", "url": "https://brave-otter-4821.pagelemon.dev", "claimed": false, "expires_at": "2026-10-14T14:40:00Z" },
"revision": { "id": "rev_…", "number": 1, "files": 12, "bytes": 48213 },
"site_token": "st_…",
"claim_url": "https://app.pagelemon.com/claim/ct_…",
"skipped": [".env", "node_modules/…"],
"agent_message": "Your site is live at …"
}site_token and claim_url appear only on an anonymous create.
POST /v1/deploy with JSON: inline files
For MCP clients and small sites: send Content-Type: application/json to the same endpoint.
{ "site": "brave-otter-4821", "base_revision": "rev_…", "files": [ { "path": "index.html", "content": "<h1>Hi</h1>", "encoding": "utf8" } ] }encoding is utf8 or base64. Leave out site and base_revision to create a new site. The response is the same as above.
Patch: add "patch": true to send only the changed files plus "deleted": ["old.html"] on top of base_revision; everything else is kept. Same limits and checks; a stale base_revision is a 409 conflict.
Upload rules
| Files | Per file | Per site | Expiry | |
|---|---|---|---|---|
| No account | 100 | 1 MB | 1 MB | 72 hours unless claimed |
| Free | 500 | 5 MB | 5 MB | none |
| Prompter | 1,000 | 50 MB | 5 GB (all its storage) | none |
| Builder | 2,000 | 50 MB | 25 GB (all its storage) | none |
| Team | 3,000 | 50 MB | 50 GB (all its storage) | none |
| Company | 5,000 | 50 MB | 150 GB (all its storage) | none |
Per site means the whole live site after a deploy; a patch counts the whole resulting site. On paid plans one site can use all of the plan's storage, but one request carries at most 90 MB (40 MB through MCP): send a bigger site as a first deploy, then the rest as patch deploys. Over a limit: 413 too_large with an agent_message to show.
- Rejected: symlinks, hard links, devices, absolute paths,
.., and duplicate paths that differ only in case. - Skipped and listed in
skipped:.git/,node_modules/,.env*,*.map,*.pem,*.key; dotfiles except.well-known/;.DS_Store,Thumbs.db. - Leak guard: text files are scanned for well-known kinds of secret keys and private keys. A hit returns
422 blocked_secretwith{file, line, kind}. The key itself is never returned. - Required:
index.htmlat the root. - Anonymous only: executables and installers are blocked, and each network gets a small number of anonymous sites and deploys. Claim or sign in for more.
Sites
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /v1/sites | API token | Sites you're a member of |
| GET | /v1/sites/:name | site token, member | {name, url, claimed, expires_at, plan, role, live_revision: {id, number, created_at, created_by}, badge}. With the site token, poll it after the user opens the claim link: {claimed: false, …} or {claimed: true, account_token?}; the one call a site token can still make once its site is claimed |
| GET | /v1/sites/:name/files | site token, member | {revision, files: [{path, size, sha256}]} for the live version |
| GET | /v1/sites/:name/files/<path> | site token, member | Raw file content (the read-back), with an X-Revision header |
| GET | /v1/sites/:name/revisions | site token, member | Newest first. Anonymous: current only. Free: 7 days. Prompter: 90 days. Builder and Team: 1 year. Company: 2 years |
| POST | /v1/sites/:name/rollback | site token, editor or owner | {revision} makes that version live again, as a new version number |
| POST | /v1/sites/:name/rename | editor or owner (account only) | {name}: 4–63 characters of a-z, 0-9 and -. Can't look like a random name (word-word-1234), be reserved, or have been used before |
| DELETE | /v1/sites/:name | owner | Permanent. With an API token it returns 202 with an approve_url: a page where the person types the site address and clicks Delete permanently. GET /v1/approvals/<token> then says whether it was deleted |
| GET | /v1/sites/:name/members | member | {members: [{email, role}], invites: [{email, role}]} |
| POST | /v1/sites/:name/members | owner | {email, role: "editor" or "viewer"} sends an invite email |
| DELETE | /v1/sites/:name/members/:email | owner | Two-step: the first call returns {confirm_token}; repeat with ?confirm=<token> within 5 minutes |
Roles: viewers can read the site and its history. Editors can also deploy and roll back. Owners can also invite, rename and delete.
Account
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /v1/me | API token | {email, plan, sites_used, sites_limit} |
Claiming sites, accepting invites, tokens and billing are handled at app.pagelemon.com.
Serving on pagelemon.dev
/servesindex.html./aboutservesabout.htmlorabout/index.html.- Missing files get the site's
404.htmlif present, otherwise ours. - Single-page app fallback: if the site has no
404.htmland the path has no file extension,index.htmlis served. - Headers:
X-Content-Type-Options: nosniff,Referrer-Policy: strict-origin-when-cross-origin,Cache-Control: public, max-age=60for HTML andmax-age=300for other files. Content types come from an allowlist; unknown types are served asapplication/octet-stream. - Anonymous and Free sites get a small "Live on Page Lemon" badge before
</body>. It's plain HTML and CSS, with no script, and includes a link to report the site. - Expired or deleted sites return
410. Sites taken down for abuse return451.
.pagelemon.json
After deploying, agents write this file in the project folder and add it to .gitignore, so the next session can update the same site:
{ "site": "brave-otter-4821", "url": "https://brave-otter-4821.pagelemon.dev", "revision": "rev_…", "site_token": "st_…" }