Skip to content

Endpoints

Everything is JSON over HTTPS at /api/v1/, authenticated with Authorization: Bearer <token> (login/register return the token). Mutations happen over REST, and a WebSocket at /api/v1/ws fans out change events. The rule is "REST is truth, WS is invalidation."

Auth & profile

Method Path Notes
POST /auth/register first account becomes server admin. Later ones as the server allows: anyone, with an invite code, or no one
POST /identity/proof the server signs a client's nonce with its identity key (how it proves itself through a proxy)
POST /auth/login · /auth/logout rate-limited per IP+username
GET/PATCH /auth/me display name, avatar, accent color, bio
PUT /auth/status online / busy / hidden
POST /auth/reset-password redeem an admin-issued one-time code
GET /auth/devices · DELETE /auth/devices/{id} per-device sessions
GET /users/{id}/profile public profile card

Groups, channels, messages

Method Path
GET/POST /groups · PATCH /groups/{id} (admin: name/icon/color)
GET /groups/{id}/members · /groups/{id}/presence
POST /groups/{id}/invites · /invites/redeem
GET/POST /groups/{id}/channels · /channels/{id}/messages (?before= / ?after=)
PATCH/DELETE /messages/{id} (author, and admins may delete)
GET/POST/DELETE /groups/{id}/emotes
POST /attachments (multipart) · GET /attachments/{id}

Sessions

Method Path
POST /sessions (posts a join card to the channel)
GET /channels/{id}/sessions · /sessions/{id}
POST /sessions/{id}/end · DELETE /sessions/{id} (creator/admin)
POST /sessions/{id}/prompts · /prompts/{id}/start · /prompts/{id}/stop
PUT /prompts/{id}/submission (autosave while running)
PUT/DELETE /prompts/{id}/vote (vote for a piece of an ended prompt, not one's own, or take the vote back)
POST /prompts/{id}/voting/close (the prompt's starter, the session's creator or an admin. The detail then has each piece's votes)

Documents

Method Path Notes
GET/POST /documents list (own + shared) with each document's word_count and excerpt. ?q= searches titles and the documents' words (not their JSON), and each hit has a snippet
GET/PATCH/DELETE /documents/{id} detail (Yjs state, with ?quiet=1 for bulk reads that shouldn't log "opened"), rename/reformat, delete (owner), which moves it to Recently Deleted
POST /documents/{id}/restore put a document back from Recently Deleted (owner)
GET /document-trash the caller's Recently Deleted, newest first, with deleted_at
DELETE /document-trash/{id} delete one for good (owner)
POST /document-trash/empty empty Recently Deleted
GET/POST /documents/{id}/updates Yjs update log with ?since= catch-up (truncated past 16 MiB: reload the state). One update may be up to 4 MiB, and a larger one is 413 update_too_large
POST /documents/{id}/awareness ephemeral cursor/selection presence
POST /documents/{id}/snapshot version-history snapshot (auto, named or draft). Also refreshes the list's text, word count and excerpt
PUT /documents/{id}/card what the list's card shows (synopsis, cover, tags) and the pictures the document's settings name, which the people it is shared with may then read. Write access. Sends document.card
GET /documents/{id}/versions · /documents/{id}/versions/{vid} version history
GET/PUT/DELETE /documents/{id}/shares · /documents/{id}/shares/{kind}/{id} read/write grants (friends or groups)
POST /documents/{id}/move move into/out of a folder
GET/POST /documents/{id}/threads anchored feedback threads
PATCH/DELETE /document-threads/{id} resolve/reopen, delete
POST /document-threads/{id}/replies reply to a thread
GET/POST /document-folders list/create folders
PATCH/DELETE /document-folders/{id} rename/delete (documents keep, unfoldered)
POST /document-folders/{id}/share share every document in a folder at once
GET /document-capabilities max_schema_level: the newest document schema level this server stores (404 on servers before levels = 1)

Deleted documents read as missing everywhere (404 to owner and collaborators alike, no room, no images) until restored. After 30 days the server deletes them for good. Lists change live: the owner's devices get document.listchanged (create, rename, move, delete, restore) and document.folders (folder changes) on their user room, and an open document's room gets document.deleted.

Schema levels. Every document has a schema_level: the newest vocabulary (node types, marks, attributes) its content uses. Level 1 is everything through 1.0.6. An editor bound to content newer than its schema deletes what it doesn't know, for everyone, so the content routes (detail, updates both ways, and snapshot) take the client's level as ?sl=N (absent = 1) and answer 403 client_too_old below the document's. An update that needs a newer level says so (schema_level in the body, at most the sender's sl and the server's max_schema_level, else 400 schema_unsupported), and accepting it raises the document in the same step. From level 2 up, document.update is sent to docv{level}:{id} instead of document:{id}. Clients left in the plain room get document.deleted with outdated: <level> when a document is raised, and never the newer content. Level 2 is text color, highlight, a font or size on part of a paragraph, sub/superscript, paragraph indents, borders and shading, checklists, tables, footnotes and endnotes, a table of contents, picture placement and captions, sections, and tracked changes. Level 3 is a table cell's own borders, and a picture's outline and rounded corners.

Voice, canvas, friends, misc

Method Path
GET/POST /groups/{id}/voice · POST /voice/{id}/join · /voice/leave · /voice/{id}/signal
GET/POST /groups/{id}/boards · /boards/{id}/elements · PATCH/DELETE /elements/{id}
GET /link-preview?url= (server-fetched page metadata)
GET /fonts/{family}/{subset}-{weight}-{style}.ttf (a font of the online library that the server fetches once and keeps, so browsers never ask a third party)
GET/POST/DELETE /friends, /friends/requests, /dms
POST /notes/share
GET/PUT /plugins/{id}/data/{scope}/{scope_id}/{key}
GET /admin/stats · /admin/users · POST /admin/users/{id}/logout · /admin/users/{id}/reset-code
GET/PUT /admin/registration (who may make an account) · GET/POST /admin/invites · DELETE /admin/invites/{id}

The full wire types live in crates/writform-proto and are exported to TypeScript for the client. They are the source of truth if you're building against the API.