API Docs
The job
Publish a static page, send the URL, and let it expire. The default life is 72 hours. A shorter ttl is allowed. Over 72 hours is rejected. Pin keeps an already-live page reachable after that clock. Unpin or delete when it should die.
The URL is public. Anyone with the link can open the page and its files. Do not publish secrets or private client work. There is no server-side execution.
Quick start with the CLI
brew install veturu/ephemr/ephemr # or: curl -sSf https://ephemr.io/install.sh | sh ephemr login # paste your API key (stored in ~/.config/ephemr/config.json) ephemr publish ./site/ # auto-zips the directory and returns the URL ephemr publish page.html --ttl 24h # single HTML file, custom TTL ephemr list # see all your pages and their expiry ephemr pin <slug> # keep a live page reachable after expiry ephemr extend <slug> --ttl 72h # refresh expiry up to the default cap ephemr delete <slug> # take a page down early
Full CLI docs: /cli. For use inside AI editors (Claude Desktop, Cursor, Codex): /mcp.
HTTP API
Everything the CLI and MCP server do is available as plain HTTP. The reference below covers every endpoint.
Authentication
Pass your API key as Authorization: Bearer eph_live_.... Create keys in the portal. The raw key is shown once — store it securely.
Publish a page
POST /api/v1/pages
Authorization: Bearer eph_live_...
Content-Type: multipart/form-data
fields:
archive: zip (index.html at root) or a single .html/.htm file
html: optional raw HTML instead of archive (exactly one of archive or html)
ttl: optional Go duration (30m, 24h, 72h). Omit for the default TTL.
Must be at least 1 minute and at most the account default.
Over the cap returns 400 ttl_exceeds_limit (not clamped).
title: optional
description: optional
alias: optional — publish to a stable alias URL
replace_previous: optional, "true" — delete the alias's old target page
(by default it stays active at its own slug URL and
keeps counting against your active-page quota)
Example with curl:
curl -X POST https://ephemr.io/api/v1/pages \ -H "Authorization: Bearer $EPHEMR_API_KEY" \ -F "archive=@site.zip" \ -F "title=my demo" \ -F "ttl=24h"
A zip that lacks index.html still fails. A single HTML upload is wrapped as a one-file site whose entry is index.html.
Response:
{
"id": "uuid",
"slug": "abc123xy",
"url": "https://abc123xy.pages.ephemr.io/",
"hostname": "abc123xy.pages.ephemr.io",
"status": "active",
"pinned": false,
"created_at": "...",
"expires_at": "..."
}
List your pages
GET /api/v1/pages Authorization: Bearer eph_live_...
Extend, pin, unpin
These act on an active page you own. Pin keeps a page reachable after expires_at. Pinned pages still count against the active-page limit.
POST /api/v1/pages/{id}/extend
Authorization: Bearer eph_live_...
Content-Type: application/json
{"ttl": "24h"}
Sets a new expires_at of now+ttl (default: the account default). The new expiry must be in the future and at most now+default TTL. This can lengthen a short publish up to the free cap, or refresh the window; it cannot set a multi-year expiry.
POST /api/v1/pages/{id}/pin
POST /api/v1/pages/{id}/unpin
Authorization: Bearer eph_live_...
Unpin of a page whose expires_at is already past sets a new expiry within the same cap (default now+default TTL) unless you supply a valid ttl.
Delete a page
DELETE /api/v1/pages/{id}
Authorization: Bearer eph_live_...
Aliases
Aliases are stable, human-readable URLs you own (e.g. myapp.pages.ephemr.io). When you publish to an alias, the alias URL always points at the latest deploy.
POST /api/v1/aliases
Authorization: Bearer eph_live_...
Content-Type: application/json
{"name": "myapp"}
Response (201):
{"id": "uuid", "name": "myapp", "hostname": "myapp.pages.ephemr.io"}
GET /api/v1/aliases Authorization: Bearer eph_live_...
Returns {"aliases": [...]}.
DELETE /api/v1/aliases/{id}
Authorization: Bearer eph_live_...
Returns 204. The current target page remains reachable at its own slug URL.
Page events
GET /api/v1/pages/{id}/events
Authorization: Bearer eph_live_...
Returns the lifecycle timeline for a page: {"events": [{"id", "type", "data", "created_at"}, ...]}.
Limits (free accounts)
- Max upload archive: 10.0 MB (10485760 bytes)
- Max extracted size: 30.0 MB (31457280 bytes)
- Max file count: 500
- Default TTL: 72h0m0s (per-publish TTL up to this cap)
- Archives must contain
index.htmlat the root, unless you publish a single HTML document. - No server-side execution. Static files only.
Errors
{ "error": { "code": "...", "message": "..." } }
unauthorized— missing or invalid API keyquota_exceeded— active page limit reachedarchive_too_large/extracted_too_large/too_many_filesunsafe_archive— path traversal or unsupported entry typesmissing_index— noindex.htmlfoundemail_not_verified— verify your email before publishingrate_limited— too many requests; back off and retryalias_taken/reserved_name/invalid_name— alias creation errorsalias_not_found— alias does not exist on your accountttl_exceeds_limit— requested ttl is above the account defaultttl_too_short— ttl is under 1 minutettl_invalid— ttl is not a Go duration stringnot_active— extend/pin/unpin requires an active page