Page Lemon

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

On this page

Quick start

Publish a folder without an account (it stays online for 72 hours unless you keep it):

bash
tar -czf - -C dist . | curl -sS --data-binary @- https://api.pagelemon.com/v1/deploy

With an API token, the site goes straight into your account:

bash
tar -czf - -C dist . | curl -sS -H "Authorization: Bearer $PAGELEMON_TOKEN" --data-binary @- https://api.pagelemon.com/v1/deploy

Hosts

HostServes
https://pagelemon.comThis site, /agents, /llms.txt, /docs/*
https://api.pagelemon.comREST API under /v1
https://mcp.pagelemon.comRemote MCP server (streamable HTTP) at / (alias /mcp)
https://app.pagelemon.comYour account: sites, people, tokens, billing
https://<name>.pagelemon.devYour sites

Auth

Send credentials as Authorization: Bearer <token>.

CredentialLooks likeWhere it comes fromWhat it can do
Site tokenst_…Returned once by an anonymous deployUpdate, 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 linkhttps://app.pagelemon.com/claim/ct_…Returned once by an anonymous deployA signed-in person claims the site
API tokenpl_…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 tokenpla_…Issued to an MCP app (Claude.ai, ChatGPT, Claude Code…) after you sign in and approve itThe 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

json
{ "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.

CodeStatusMeaning
bad_request400Bad archive, body or parameter
unauthorized401Missing or invalid token
forbidden403The token or role can't do this
not_found404No such site or file
conflict409Stale X-Base-Revision / base_revision
gone410The site expired
too_large413Over a size limit
blocked_secret422The leak guard found a secret key
limit429Plan 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.

bash
tar -czf - -C <dir> . | curl -sS --data-binary @- https://api.pagelemon.com/v1/deploy
RequestResult
No ?site, no tokenAnonymous create. A random name such as brave-otter-4821, expires after 72 hours unless claimed
No ?site, API tokenCreate in your account. No expiry
?site=<name> + X-Base-Revision: <revision id> + site token or API tokenUpdate. 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:

json
{
  "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.

json
{ "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

FilesPer filePer siteExpiry
No account1001 MB1 MB72 hours unless claimed
Free5005 MB5 MBnone
Prompter1,00050 MB5 GB (all its storage)none
Builder2,00050 MB25 GB (all its storage)none
Team3,00050 MB50 GB (all its storage)none
Company5,00050 MB150 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_secret with {file, line, kind}. The key itself is never returned.
  • Required: index.html at 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

MethodPathAuthNotes
GET/v1/sitesAPI tokenSites you're a member of
GET/v1/sites/:namesite 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/filessite token, member{revision, files: [{path, size, sha256}]} for the live version
GET/v1/sites/:name/files/<path>site token, memberRaw file content (the read-back), with an X-Revision header
GET/v1/sites/:name/revisionssite token, memberNewest first. Anonymous: current only. Free: 7 days. Prompter: 90 days. Builder and Team: 1 year. Company: 2 years
POST/v1/sites/:name/rollbacksite token, editor or owner{revision} makes that version live again, as a new version number
POST/v1/sites/:name/renameeditor 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/:nameownerPermanent. 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/membersmember{members: [{email, role}], invites: [{email, role}]}
POST/v1/sites/:name/membersowner{email, role: "editor" or "viewer"} sends an invite email
DELETE/v1/sites/:name/members/:emailownerTwo-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

MethodPathAuthNotes
GET/v1/meAPI token{email, plan, sites_used, sites_limit}

Claiming sites, accepting invites, tokens and billing are handled at app.pagelemon.com.

Serving on pagelemon.dev

  • / serves index.html. /about serves about.html or about/index.html.
  • Missing files get the site's 404.html if present, otherwise ours.
  • Single-page app fallback: if the site has no 404.html and the path has no file extension, index.html is served.
  • Headers: X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Cache-Control: public, max-age=60 for HTML and max-age=300 for other files. Content types come from an allowlist; unknown types are served as application/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 return 451.

.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:

json
{ "site": "brave-otter-4821", "url": "https://brave-otter-4821.pagelemon.dev", "revision": "rev_…", "site_token": "st_…" }

Something unclear or out of date? Tell us and we'll fix the page.