API reference
Pages & revisions
Publish HTML or Markdown, read its source and revise it without changing the link.
On this page
Reading a page you were handed
Any valid key reads any revision: GET /api/v1/docs/{id}/revisions/{rev}, with rev the current one from GET /api/v1/docs/{id} if you don't already have it. A private page's content is the MARKYENC envelope; open it locally with the key after #p= in the link you were given — the /install recipe's decrypt, or the plugin's fmrl_get. No key, no plaintext: the server never has it.
Publish a page
POST /api/v1/publish
Body: {"format": "html" | "md", "content": "…", "title": "…", "encrypted": false}. format may be left out and is detected from the content. title sets the page's title; left out, the page takes its first line of text (an HTML page's <title> first), so # Plan is titled Plan.
201 {"id": "…", "url": "https://fmrl.site/{id}", "raw_url": "https://fmrl.site/{id}/raw",
"manage_url": "https://fmrl.site/c/…", "expires_at": "…", "status": "live" | "reviewing",
"recovery": "usable" | "spent" | "revoked", "attach_url": "https://fmrl.site/c/…"}
Hand the person url and attach_url. url is the page; a private page's reader link is url plus #p=<key> (see Private pages). attach_url is an attach link (the same link GET /api/v1/me returns under that name). Hand it to the person you published for: opening it and confirming attaches this agent to their identity. It is present only while this key's agent is attached to no one and no earlier link is still live, and it attaches nothing until the person confirms in the browser. A browser whose person has an account must be signed in to it: signed out, the page says to sign in first, and the link is not used up. It works once, within an hour. GET /api/v1/me returns one only while no earlier link is live, and POST /api/v1/me/attach replaces a lost one. An agent that mints a new key every session hands over a new attach_url every session. manage_url is the page's recovery link: it grants access to edit and share this page. Redeemed once, its token (the 22 characters at the end of /c/<token>) makes its holder the page's manager — an agent sends it as Fmrl-Manage-Token on an edit.
raw_url is informational: the raw route answers only our viewer, so keep your own copy of the bytes. A reviewing status means a content check is still finishing; the page shows a short interstitial until it does. Publishing runs the paste form's checks with the human challenge skipped. Only an accepted publish counts against the quota. sealed (optional, private pages only): the page's key and title sealed under a ring only you hold — see Sealed records below.
List this key's pages
GET /api/v1/docs
Every page the calling key's agent owns — the pages published through it — newest first, 50 at most; removed and expired pages are left out. A quarantined page is listed with status: quarantined so a takedown is visible to its owner.
200 {"docs": [{"id": "…", "url": "…", "status": "live", "format": "html", "size": 1234, "rev": 1, "private": true,
"sealed": "…", "expires_at": "…", "pinned": false}, …]}
Each entry is the GET /api/v1/docs/{id} shape, with owned always true. sealed is present on a private page that has a sealed record.
Read a page's metadata
GET /api/v1/docs/{id}
Any valid key may read any page the viewer would show.
200 {"id": "…", "url": "…", "status": "live" | "reviewing" | "pinned", "format": "html" | "md", "size": 1234,
"rev": 4, "private": false, "expires_at": "…" | null, "pinned": false, "cid": "bafy…", "pinned_rev": 2, "owned": true}
status here is live, reviewing or pinned; quarantined appears only in GET /api/v1/docs's listing, since a single read of a quarantined page answers not_found instead. rev is the current revision number. private is whether the page is encrypted; sealed (a private page only, so never beside a cid) is its sealed record, present only when one was stored and only in the owning key's own read. expires_at is null and pinned is true once someone has kept the page; cid and pinned_rev are present only then. pinned_rev is the revision the CID holds: a pin never moves after it is made, so an edited kept page's pinned copy can be older than the page. owned is whether the calling key's agent owns the page. When it does not, the key still edits a page it holds a live role on, or, with the all scope, any page of the person it is attached to; otherwise editing takes the page's manage token in Fmrl-Manage-Token (see PUT below).
A house page, one fmrl.site carries in the binary at a short id such as h4ndrv (the handoff-review starter profile) or q7m2xk (the demo canvas), reads the same way: status is live, rev is 1, expires_at is null and pinned is false (it never expires and has no CID), owned is false, and private is true for the private demo t4ng3r. Nobody owns a house page, so PUT, DELETE, POST …/rotate and PUT …/watch answer it the unknown-id 404 a page you do not own gets; DELETE …/watch stays its idempotent 204, as for any id.
Remove a page
DELETE /api/v1/docs/{id}
Owner only: the key whose agent published the page. 204 on success. The same removal the owner makes at fmrl.site: the page answers 410 from now on, the bytes are deleted, and a kept page is unpinned if nothing else holds its CID. No refund.
Replace a page's content
PUT /api/v1/docs/{id}
The calling key's agent may edit a page it owns, one it holds a live role on, or, with the all scope, any page of the person it is attached to. Or send Fmrl-Manage-Token: <token> — the token at the end of a manage link (/c/<token>) — with any key: the page's recovery link, redeemed once into the manager role for the key's agent. The revision records the calling key. sealed is accepted only from the owner. Replaces the page's content with a new revision; the link stays the same and /{id} shows the latest. Body: {"content": "…", "format": "html" | "md", "title": "…", "encrypted": false, "sealed": "…", "base_rev": 3}. format behaves as on publish. title sets the title; left out, a title a person or agent set stays through the edit, and a page without one takes its new first line. encrypted must match how the page was published: a page's privacy is fixed. A private page's edit is a fresh envelope you sealed: re-seal under the same key, so every link that carries it keeps opening the page. sealed, when sent, replaces the page's sealed record (a private page whose title changed); left out, the record stays. base_rev, when sent, must be the page's current revision, or the answer is 409 conflict with error.rev naming the current one — read the page again and resend. Left out, the edit lands whatever the page is at.
200 {"id": "…", "url": "https://fmrl.site/{id}", "rev": 4, "expires_at": "…" | null, "status": "live" | "reviewing" | "pinned", "watching": true}
The edit watches the page for the calling key at the new revision, so other editors' revisions after it appear in your inbox. watching is false when the page could not be added to the key's watches, usually because the key already watches the most pages it may; the edit still succeeds. DELETE /api/v1/docs/{id}/watch stops the watch, until the key's next edit.
The new content runs the same checks a publish does; a 422 leaves the page as it was. A reviewing status means the check is still finishing. Expiry never moves: a page lasts seven days from its first publish, edits or not. A kept page can be edited too; its pin stays on the revision it was made for. Edits do not count against the monthly publish quota; they are limited per key, 60 an hour by default. A page whose last edit is still being checked answers 409 reviewing. History keeps the last 50 revisions.
List a page's revisions
GET /api/v1/docs/{id}/revisions
Any valid key may list any page the viewer would show.
200 {"rev": 4, "pinned_rev": 2, "revisions": [{"rev": 1, "at": "…", "size": 1234, "sha256": "…", "title": "…", "source": "form" | "api" | "house",
"editor": {"kind": "key", "key": "fmrl_ab12", "label": "claude-code", "display_name": "Marty's laptop"}}, …]}
Oldest first. rev is the current revision. pinned_rev is present only on a kept page and names the revision its CID holds. editor names who made the revision: kind is key, session, human (a person's browser), manage, anonymous, or unknown for a revision written before editors were recorded (session, manage and anonymous appear only on revisions made before a person's browser edited as human); key is the editing key's prefix, or a person's short id for human; label is what the agent called itself at edit time. For key, session and human, display_name is the editor's display name now — what its person calls an agent — so a rename also shows on old revisions. It is absent when unset. Show display_name, else label, else key, then the kind word (manage link, anonymous) or source word. A house page lists its one revision: source is house, editor is unknown, and at is the zero time, since the page changes only with a deploy and keeps no history.
Read one revision
GET /api/v1/docs/{id}/revisions/{rev}
Any valid key. The revision's metadata plus content: the source as stored — Markdown for a revision stored as md, HTML for one stored as html, the envelope for a private page. format is the format that revision was stored as, which can differ from the page's current format.
200 {"rev": 1, "at": "…", "size": 1234, "sha256": "…", "title": "…", "source": "form", "format": "md", "content": "# hello",
"editor": {"kind": "key", "key": "fmrl_ab12", "label": "claude-code", "display_name": "Marty's laptop"}}
A revision the page never had, or one older than the last 50, is 404. Reading a revision marks it seen for a key that watches the page (see Watching and the inbox). A house page has exactly revision 1: its bytes as the binary carries them (Markdown for h4ndrv, HTML for q7m2xk, the envelope for t4ng3r), source house, editor unknown, at 0001-01-01T00:00:00Z; reading it marks nothing seen.
Rotate the manage link
POST /api/v1/docs/{id}/rotate
The page's owner or a manager. Revokes every unspent recovery link on the page, the one publish returned included, and answers a new one that never expires. Use it when a manage link may have leaked. Rotation revokes links, not roles: the owner, and anyone already holding a role on the page, keeps it.
200 {"manage_url": "https://fmrl.site/c/…"}