REST API

Publish with a POST.

The whole page lifecycle over plain HTTP: publish, list, read stats, update, delete, and read form submissions. One token, curl-friendly, no SDK.

01 What this is

Everything you can do from a chat with the MCP connector, you can also do from a script. Same account, same plan limits, same URLs, just plain JSON over HTTPS.

Reach for the API when you want to publish from a backend, a CI job, a cron task, or a one-line curl. Reach for MCP instead when you want an AI assistant to build and publish pages for you in chat, or when you need the drag-and-drop photo upload slots (those need a browser).

02 Authenticate

Every request carries a Personal Access Token in the Authorization header.

  1. Sign in, then create a token in Settings, Connect AI clients. It's shown once, so copy it.
  2. Send it as a bearer token on every call: Authorization: Bearer yapp_pat_…
export YAPP_TOKEN=<your token>
Keep the token secret. It has the same access as your account. Revoke it any time in Settings, and it stops working on the next request.
On Windows? The examples below are written for macOS and Linux shells. Three things differ:
  1. Call curl.exe, not curl. In PowerShell, curl is an alias for a different tool that does not accept -X or -H.
  2. Set the token with $env:YAPP_TOKEN="…" in PowerShell, or set YAPP_TOKEN=… in the classic Command Prompt, and read it back as $env:YAPP_TOKEN or %YAPP_TOKEN%.
  3. Put the JSON body in a file and send -d "@body.json". Do not paste JSON straight into the command: Command Prompt keeps the single quotes around -d '{"…"}', and Windows PowerShell 5.1 drops the inner double quotes, so either way the body arrives as invalid JSON. A file avoids both.
The publish call on Windows, with the body in body.json next to you:
{"html":"<h1>Hello world</h1>","name":"My page"}
curl.exe -X POST https://yapp.page/api/v1/pages -H "Authorization: Bearer $env:YAPP_TOKEN" -H "Content-Type: application/json" -d "@body.json"

03 Base URL

All endpoints live under one versioned base.

https://yapp.page/api/v1

Published pages get their own subdomain, so a page's live URL is https://<id>.yapp.page/, not a path under the API.

04 Endpoints

Seven calls cover the full lifecycle, including image upload. Responses are JSON.

POST /pages

Publish a new page. Send exactly one content source: html (inline), htmlUrl (a public https URL we fetch byte-for-byte, best for large files), or fileBase64 + mime (host a PDF, image, or ZIP as-is). Optional: filename, name, slug, expires (days), password.

curl -X POST https://yapp.page/api/v1/pages \ -H "Authorization: Bearer $YAPP_TOKEN" \ -H "Content-Type: application/json" \ -d '{"html":"<h1>Hello world</h1>","name":"My page"}'
201 Created
{ "id": "k3n8x2", "url": "https://k3n8x2.yapp.page/", "editKey": "e_9f…" }

Big document or one you already host? Skip inline HTML and let yapp fetch it:

-d '{"htmlUrl":"https://example.com/report.html","slug":"q3-report","expires":30}'
POST /pages/:id/assets

Upload an image (or any static file) straight from disk. One multipart file field, no URL and no base64 needed. The name defaults to the uploaded filename, or set name to control the path (e.g. images/hero.jpg). Uploading a name that already exists replaces it. Reference the file from your HTML by that name.

curl -X POST https://yapp.page/api/v1/pages/k3n8x2/assets \ -H "Authorization: Bearer $YAPP_TOKEN" \ -F "[email protected]"
200 OK
{ "ok": true, "name": "hero.jpg", "size": 84213, "url": "https://k3n8x2.yapp.page/hero.jpg" }

Typical flow: publish the HTML that references hero.jpg, then upload the file. It appears on the live page instantly.

GET /pages

List your active pages, newest first.

curl https://yapp.page/api/v1/pages \ -H "Authorization: Bearer $YAPP_TOKEN"
200 OK
{ "count": 1, "pages": [ { "id": "k3n8x2", "name": "My page", "url": "https://k3n8x2.yapp.page/", "views": 128, "expiresAt": "2026-08-01T00:00:00.000Z", "hasForm": false, "submissions": 0, "hasPassword": false } ] }
GET /pages/:id/stats

Traffic stats for one page. Human traffic only, bot hits are filtered out. Detailed breakdowns (referrers, countries, devices) require the Pro plan.

curl https://yapp.page/api/v1/pages/k3n8x2/stats \ -H "Authorization: Bearer $YAPP_TOKEN"
200 OK
{ "id": "k3n8x2", "views": { "total": 128, "today": 4, "week": 31, "month": 128 }, "visitors": 96, "referrers": { "google.com": 40 }, "countries": { "US": 55, "DE": 20 }, "devices": { "desktop": 70, "mobile": 26 }, "botHitsFiltered": 12, "expiresAt": "2026-08-01T00:00:00.000Z" }
PATCH /pages/:id

Change any subset in one call. Replace content with html or htmlUrl. Set name, expires (a number of days or "forever"), password (a string to set, or null to remove), and slug (the page moves to the new URL, the old one stops working).

curl -X PATCH https://yapp.page/api/v1/pages/k3n8x2 \ -H "Authorization: Bearer $YAPP_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"Launch","expires":90,"html":"<h1>Updated</h1>"}'
200 OK
{ "id": "k3n8x2", "url": "https://k3n8x2.yapp.page/" }

Password protection and custom expiry are Pro features. A few common single-field edits:

-d '{"slug":"launch-2026"}' # move to launch-2026.yapp.page -d '{"password":"s3cret"}' # gate the page -d '{"password":null}' # make it public again -d '{"expires":"forever"}' # stop auto-delete
DELETE /pages/:id

Delete a page permanently. The URL stops working and form submissions are removed. This cannot be undone.

curl -X DELETE https://yapp.page/api/v1/pages/k3n8x2 \ -H "Authorization: Bearer $YAPP_TOKEN"
200 OK
{ "ok": true }
GET /pages/:id/submissions

Read form submissions captured from a page, newest first. yapp auto-captures any <form> on a page. Add ?limit= (1 to 200, default 50). A page keeps a rolling 10 messages on Free and 500 on Pro (unlimited on Team): once full, the oldest is dropped as new ones arrive.

curl "https://yapp.page/api/v1/pages/k3n8x2/submissions?limit=50" \ -H "Authorization: Bearer $YAPP_TOKEN"
200 OK
{ "count": 1, "submissions": [ { "createdAt": "2026-07-09T10:00:00.000Z", "data": { "email": "[email protected]" } } ] }
Photos and assets. Three ways to get images onto a page: upload the file directly with POST /pages/:id/assets (shown above, the simplest), point your HTML at a public https URL, or bundle a whole site as a ZIP via fileBase64. The drag-and-drop drop-slots (for uploading a file from inside a chat) are the MCP connector's browser flow; over HTTP you just POST the file.

05 Errors & limits

Errors return a JSON body: { "error": "message", "code": "PLAN_LIMIT" }. The code field is present on some errors (like plan limits) and absent on others.

StatusMeans
400Bad request, invalid slug, missing content, or a malformed body.
401Missing, invalid, or revoked token.
402Plan limit reached (for example, password or extra pages on the free plan). Carries code.
403The token's account does not own this page.
404No such page.
409That slug is already taken.
413Content too large (12 MB max per request).
415The body was not sent as JSON, so it was ignored. Add -H "Content-Type: application/json".
429Rate limited. The API allows 60 requests per minute per IP.
Two 400 codes worth knowing. BAD_FIELD_TYPE means html or htmlUrl was sent as something other than a single JSON string, for example null, a list of lines, or a nested object. NO_CHANGES means the request contained no field the API recognises, so nothing was updated. Both usually mean a script or AI client built the request body wrong. The fix is the same in either case: send the entire document as one string in the html field, exactly as in the update example above.

Prefer publishing from a chat? See Use with AI for the MCP connector.