Objects and methods
Content moves over the standard JMAP blob endpoints (
/jmap/upload, /jmap/download) — a FileNode create references an uploaded blobId.
Version history
Every content update to a node snapshots the previous blob into its version history, in the same transaction as the update. The current content always lives on the node itself;FileVersion is history only, and every one of its properties is immutable:
The
urn:ietf:params:jmap:files capability advertises maxVersionsPerNode (10 at v0.30.0). It is the retention window and the pruning bound from one constant: versions past the newest N are pruned in the same transaction that created the newest one, and their blobs are released through the refcounted destroy path — never a direct delete, since a blob may be shared by deduplication. A client should gate its “version history” menu entry on the presence of that key rather than assuming the surface exists.
FileVersion/queryrequires afileNodeIdfilter. Versions are meaningless as an account-wide list, and requiring the anchor keeps the surface bounded by the retention cap. Newest first by default. ItsqueryStateis the FileNode collection state — versions only change when a node’s content changes, which moves that state, so one cursor is coherent for both.canCalculateChangesisfalse: there is no separate version change log, so re-query when FileNode changes.FileVersion/restorenever destroys history. It goes through the same storage chokepoint as aFileNode/set { blobId }content update, so the content you are currently on is snapshotted as a new version before being replaced — the version you came from stays reachable, and pruning applies as usual.sizeandcontentTypeare restored from the version record, which is authoritative because it captured them at snapshot time. Restoring the version whose blob is already current is a content no-op but still answers success. It takes an optionalifInStatefor the same optimistic concurrency asFileNode/set, checked atomically with the write.- Access follows the node. Reading the version list needs
mayReadon the owning node,restoreneedsmayWrite(a read-only shared mount answersforbidden). A node that does not exist and a node a shared caller may not read answer with identical wording — the version surface is not an existence oracle.
Behaviours worth knowing
typeis honored.type: "directory"creates a directory; the type field is validated, never guessed from the presence of a blob.- The tree is rooted; the wire uses
null. Every account has a storage root;parentId: nullin a query means “top level”. Directory moves are cycle-checked — a move that would create a parent cycle is rejected. - Trash rides the same transaction.
isTrashedupdates are atomic with the rest of the update (a recursive directory trash is one transaction), and trashed nodes restore from the visible trash (ADR-035). - Stars are per-viewer. Starring a shared file marks your view; the owner’s object is untouched.
- Thumbnails are server-derived for previewable types — clients do not upload their own.
- Share links are scoped and accounted. A
FileShareLinkgrants access to exactly its node subtree, enforcesmaxDownloadsexactly, refunds quota on deletion, and an anonymous reader can only decrypt blobs referenced by the files projection of the resolved account — the crypto layer backs the ACL (encryption at rest).