Files
narl-workspace/docs/wiki/API.md
T
nvrl d8e72c688d files: a file belongs to a node, and a document holds its own
An upload while writing is now a file node of the document, through
POST /nodes/{id}/files, instead of a loose row of media that only the media
browser listed. It moves, seals, goes to the trash and comes back with the
document. Migration 0029 gives every embedded upload that had no node a
place under the document that embeds it (a living one before a trashed
one), keeping its media id, so no body changes; the sealed-folder triggers
now ask a document's folder for the files under it.

- nodes: `holds` and `place_for` say what may go where; a document holds
  files and nothing else, in moves, uploads and restores from the trash.
  A loaded document carries its files and whether its folder seals them.
- seal: a document moving into a sealed folder seals its files first.
- media: POST /media, GET /media and DELETE /media/{id} are gone, and with
  them MediaBrowser and :media.
- import: `plan/` beside `plan.md` comes back as the document's files, the
  shape export writes; an older export's media/ goes to the first document
  that embeds each file.
- sftp: a document's files are `<slug>/` beside its `.md`, listed once it
  has any, holding files only, moving with the `.md`.
- web: paste, drop and :upload while reading go to the document; the info
  sheet has a files section (Enter opens, i embeds, y copies how); the list
  shows +N on a document, za folds its files open, p drops marked files in.
2026-10-22 11:57:52 +02:00

6.2 KiB

API

Everything except the login, the health check and the two share routes requires the session cookie; /api/capture takes a token instead. Media answers Range requests, so a video seeks, sealed or not.

method path purpose
GET /api/health public: up, and the database answers — for a supervisor
POST /api/login, /api/logout session; /api/logout?everywhere=true ends every one
GET /api/me current user
GET /api/sessions every signed-in browser, the one seen last first
DELETE /api/sessions/{id} sign another browser out, and lock the vault
GET/POST /api/spaces the spaces / create one
GET/POST /api/nodes children (?parent=) or search (?q=, ?space=; a file matched by its text has in_text and the words around the match as excerpt, each matched word between U+0002 and U+0003) / create, from a template with template_id
GET/PUT/DELETE /api/nodes/{id} read / update only the fields sent — name, body, created_at, has_board; with base_updated_at, refused with 409 once it was saved since / delete into the trash, subtree and all — or for good, for something already in it
GET/DELETE /api/trash what was deleted, the last first / empty it
DELETE /api/trash/{id} delete one thing in the trash for good
POST /api/trash/{id}/restore back where it was deleted from, or to its space when that folder is gone
GET /api/spaces/{id}/board the lists and cards of one space
GET /api/agenda open cards due within a week or in hand, on every board, whether the diary has today, its entries on_this_day in earlier years, and today in WORKSPACE_TIMEZONE, with its bounds
GET /api/diary/days?month=2026-10 the diary's entries in a month, each with its local day
POST /api/diary/day { day? }: the entry of 2026-10-22, or of today in WORKSPACE_TIMEZONE, started from templates/day when there is none; answers { id, created }
GET /api/diary/around/{id} the entries before and after a diary entry, by date
GET /api/nodes/{id}/templates the templates of the space the node is in
POST /api/capture { text, to: "card" | "diary" | "note" | "document", space?, document?, note? }, with a session or Authorization: Bearer a token; note is a scratch note the text came from, dropped once it has landed
GET/POST /api/tokens the capture tokens / make one, { name }, answered with the token this once
DELETE /api/tokens/{id} remove a capture token
POST /api/spaces/{id}/lists add a list
PUT/DELETE /api/lists/{id} rename / delete a list and its cards
POST /api/lists/{id}/cards add a card
PUT/DELETE /api/cards/{id} update (title, body, due, repeat, checklist, labels, done, link) / delete; finishing a repeating card answers its copy as again
POST /api/cards/{id}/move to another list and place in it; again as above
GET /api/cards?q=… cards whose title, summary or a checklist step matches, open ones first; space keeps it to one board
GET/POST /api/scratch the scratchpad's notes, the one touched last first, or with ?q= those a search finds / a new note { body }
PUT/DELETE /api/scratch/{id} rewrite a note { body } / drop it
POST /api/nodes/{id}/move reparent, and place among the new siblings
POST /api/nodes/move { ids, parent_id }: several into one folder, all or none
POST /api/nodes/{id}/import import a folder or zip (multipart) under this node
POST /api/import import an export at the root: directories become spaces
GET /api/resolve?path= the node behind a slug path, work/notes/monday
GET /api/nodes/{id}/revisions a document's earlier versions, the newest first
GET /api/nodes/{id}/revisions/{rev} one earlier version, with its text
GET /api/nodes/{id}/backlinks the documents that link to this one, with the line each does it on
GET /api/nodes/{id}/cards the cards about this document: its own (node_id) and the ones whose summary links here — also in the node itself as cards
POST/DELETE /api/nodes/{id}/share mint or rotate / revoke a share link
GET /api/export the whole workspace, boards and dates included, as a zip
GET/POST /api/backup Proton Drive mirror: status / run now
POST/DELETE /api/backup/login log the mirror in to Proton, body {"username","password"} / forget its session
POST /api/backup/login/totp, /api/backup/login/captcha the two-factor code {"code"}, or the solved human check {"token"}, a waiting login asked for
GET /api/media/{id} serve a file's bytes (with Range)
POST /api/typst {source} of a ```typst block → {svg}, or {error} as typst words it
GET /api/spaces/overview every space with what was last written in it
POST /api/nodes/{id}/files upload files (multipart) into a space or folder, or to a document as its own; ?encrypt=true seals them, and a sealed folder always does
POST /api/nodes/{id}/inspect what a certificate, key or keystore file holds ({password} opens a protected one)
POST /api/nodes/{id}/encrypt, /decrypt seal a file, or a folder for good — what is in it and what lands later — or take it out
GET/POST/PUT /api/vault status / set the password once / change it
POST /api/vault/unlock, /api/vault/lock open / close the vault
GET/POST /api/ssh-keys the keys that may mount over SFTP / add one
DELETE /api/ssh-keys/{id} remove a key, which ends its mounts
POST /api/share/{token} public: read a shared document, body {"key"}; sets the share's media cookie
GET /api/share/{token}/media/{id} public: media inside a shared document, with that cookie
POST /api/share/{token}/typst public: a typst block the shared document holds, with that cookie