replace big-tech and host basic infrastructure yourself! ordner.fyi
ordner docs developers reference.md
32 kB · 618 lines
Markdown
at main

Reference #

Every type, method, argument, condition, comparator, facet, error and limit of the API, as the server checks them. Lengths count characters (Unicode code points) unless they say bytes. A JSON Schema of every type says the same, generated from the same constants.

Capabilities, abbreviated below:

URN
core urn:ietf:params:jmap:core
filenode urn:ietf:params:jmap:filenode
files https://ns.ordner.fyi/svc/jmap/files
photos https://ns.ordner.fyi/svc/jmap/photos

Each method needs its capability in using and a right of the credential over the account (the credential's scopes, intersected with a delegation's rights in a delegated account; Signing in §9).

1. Methods at a glance #

Method Capability Right Section
FileNode/get, /changes, /query, /queryChanges filenode drive:read §2
FileNode/set, FileNode/copy filenode drive:write §2
FileNode/facets files drive:read §7
FileAnnotation/set files drive:annotate §4
AnnotationSchema/get, /changes files drive:read §5
AnnotationSchema/set files drive:annotate §5
AnnotationView/get, /changes files drive:read §6
AnnotationView/set files drive:annotate §6
FileVector/get, /changes, /query files drive:read §9
FileVector/set files drive:annotate §9
AnnotationSource/get, /changes files drive:read; the owner reads every source, anyone else only their own §11
AppData/destroy files drive:annotate §12
PhotoFace/get, /changes, /query photos drive:read (embeddings: drive:faces) §8
PhotoFace/set photos the drive's owner, or drive:faces §8
PhotoPeople/get photos drive:read §8
PhotoPeople/set photos drive:write §8
PhotoTags/get photos drive:read §8
AlbumSuggestion/get, /changes, /query, /queryChanges photos drive:read §10
AlbumSuggestion/set photos drive:annotate (deciding: drive:write) §10
Blob/upload, Blob/get, Blob/lookup urn:ietf:params:jmap:blob a write right (upload), drive:read RFC 9404
Quota/get, … urn:ietf:params:jmap:quota RFC 9425

Faces, people and suggestions are methods of a drive's own account; an account a share opened to the person (a view) answers them accountNotSupportedByMethod. FileVector/get also works in a view.

2. FileNode #

A file, folder or symbolic link of a drive (draft-ietf-jmap-filenode-14).

2.1 Properties #

Property Type
id Id stable across rename and move
parentId Id | null null at the top of the drive
nodeType "file" | "directory" | "symlink"
blobId Id | null the content, a file's only; bound to the person who read it
target String[] | null a symbolic link's target
size UnsignedInt | null bytes, a file's only
name String 1–255 octets of UTF-8, no / or control characters, not ., .., .versions
type String | null media type, a file's only
created, modified, changed UTCDate changed is server-set on every write of the node, including annotations
accessed UTCDate | null
executable Boolean
isSubscribed Boolean always true
myRights { mayRead, mayAddChildren, mayRename, mayDelete, mayModifyContent, mayShare }
shareWith, role filenode-14
annotations { <sourceId>: FileAnnotation } | null every source's annotation of the current content (§3); read-only
photo Photo | null an analysed picture's metadata (§2.3)
album { coverId, locationKeep, faceNamesKeep } | null a folder directly under Pictures/Albums; the share rules that keep location, and those that keep face names (§8.6)
notPicture Boolean the photo worker read it and found no picture
unreadable String | null why the photo worker gave up reading it
shareAccess, officeAccess, openBy sharing and editing; not part of this API

FileNode/get answers every property unless properties names some; id is always answered. ids: null is refused when the account holds more than maxObjectsInGet nodes: query, then get by id.

2.2 Query conditions #

FileNode/query's filter is a FilterCondition or a FilterOperator { operator: "AND" | "OR" | "NOT", conditions: [] } (NOT: none of the conditions). Within one condition object, all members must hold. An unknown member is unsupportedFilter; a value of the wrong type is invalidArguments.

Standard (filenode-14):

Condition Value Matches
isTopLevel Boolean nodes at the top of the drive (or not)
parentId Id the folder's children
ancestorId Id everything below the folder
descendantId Id the node's ancestors
nodeType file | directory | symlink
role, hasAnyRole String, Boolean
blobId Id files of that content
isExecutable Boolean
createdBefore, createdAfter, modifiedBefore, modifiedAfter, accessedBefore, accessedAfter UTCDate Before is <, After is ≥
minSize, maxSize UnsignedInt size ≥ minSize, size < maxSize
name, type String exactly
nameMatch, typeMatch String a glob (*, ?) over the name or type
text String nameMatch or typeMatch of the value, or body
body String what applications wrote: every word a prefix of a word of a description, a label, a searchable field or a text, without case
changedAfter UTCDate changed ≥ the value (Keeping up §3)

Annotations (files):

Condition Value Matches
hasLabel String files with a label of that name from any source, without case (NFC)
annotatedBy SourceId files the source has a current annotation of
isAnnotated Boolean false: files whose current content the calling source has not annotated (its queue); true: those it has. A picture's .xmp sidecar is in neither
field { source, name, equals | in | min | max | exists | contains } §5.3
similarTo { model, vector, minSimilarity } §9.3

Pictures and faces (photos):

Condition Value Matches
isPhoto Boolean analysed pictures, and files not read yet that look like pictures by type or name
isPhotoRead Boolean analysed pictures only
isNotPicture Boolean files that look like pictures and are not
isMarkedNotPicture Boolean every file the worker found no picture in
isPhotoStale, isPhotoUnlinked Boolean the photo worker's queues
isAlbum Boolean album folders
photoTakenAfter, photoTakenBefore LocalDate (2026-10-14T09:12:03) taken ≥, taken <
personName String pictures with a face of that name, without case
hasFaces Boolean pictures with at least one face region
keyword String read pictures whose photo.keywords hold it, without case
hasShareWith Boolean shared nodes

2.3 Comparators #

sort is a list of { property, isAscending }; ties are broken by name, then id.

property
name, type, size, created, modified, nodeType, tree filenode-14
photoTaken by photo.taken (an unread picture by modified), nodes without one last
field with source and name: a sortable field of that source (§5); nodes without a value last either way; else unsupportedSort
similarity only with a similarTo condition: the most similar first, whatever isAscending says

2.4 The photo value #

null on anything that is not an analysed picture (and while a changed picture is being read again). Otherwise:

Field Type
taken LocalDate the camera's wall clock
takenOffset ±HH:MM | null
width, height Int pixels as displayed
orientation 1–8 EXIF orientation
fileOrientation 1–8 | null the orientation in the file's own bytes
camera { make, model, lens } | null
exposure { fNumber, exposureTime, iso, focalLength } | null
location { latitude, longitude, altitude } | null null to a reader of an album shared without location
title, description String | null the person's (XMP)
keywords String[] the person's (XMP dc:subject)
rating −1–5
faces PhotoFaceRegion[] §8.1
thumbnailBlobId Id | null about 400 px, WebP
displayBlobId Id | null about 2,048 px, WebP, for formats a browser cannot show
renderedBlobId Id | null the edited picture, JPEG
edits { crop, angle } | null
renderedFrom { orientation, edits } what the renditions show
sidecarId Id | null the picture's .xmp sidecar file
originId Id | null an album copy's library picture
analysed String the content it was read from (server-set)

With drive:write, FileNode/set writes photo/title, photo/description, photo/keywords, photo/rating, photo/taken, photo/takenOffset, photo/location, photo/orientation, photo/edits and photo/faces (the person's faces, §8.2), into the sidecar first. Every other field is server-set: writing it is invalidProperties (stating its current value is no change).

3. FileAnnotation #

One source's annotation of a file's current content, the value of FileNode.annotations[<sourceId>].

Field Type
source Source { id, actor: { id, name }, app: { id, kind: "app" | "pw", name } | null }
blobId Id the content it was made from, which is the file's now (an annotation of other content is not answered)
updated UTCDate its last write
description String | null ≤ 2,000 characters
labels [{ name, confidence }] ≤ 100; name 1–100 characters, unique without case; confidence 0–1 or null
textBlobId Id | null the file's text as the source wrote it, text/plain; charset=utf-8
fields { <name>: value } values of the source's fields (§5) its schema still names, of the type it says now

annotations is null when no source has a current annotation. Everyone who may read the file reads every source's annotations (a share reader too). A copy of the file carries them (same content); move and rename keep them. Downloading textBlobId needs only a read of the file.

4. FileAnnotation/set #

{ accountId, ifInState?, update: { <nodeId>: Update }, destroy: [<nodeId> | "<nodeId>/<sourceId>"] }. There is no create: the node is the key. At most 256 updates and destroys together (else requestTooLarge). The state is FileNode's: every write changes it and is pushed as a FileNode change.

4.1 The caller's own annotation #

Update = { blobId, description?, labels?, text?, fields?, faces? }; nothing else.

  • blobId (required): the file's blobId as this person read it. The write lands only while the file still holds that content; else SetError contentChanged (properties: ["blobId"]). A blob id that is not a content blob id this person read is invalidProperties.
  • Absent fields stay as they were; { blobId } alone records "read, nothing to say", which takes the file off the caller's queue.
  • description: String ≤ 2,000 or null.
  • labels: the whole list, replacing the caller's; [] clears it.
  • text: the blob id of an upload of type text/plain (charset UTF-8), ≤ 1,048,576 bytes (tooLarge), valid UTF-8; null removes it. Its words are searchable at once.
  • fields: { <name>: value | null } for fields of the caller's schema (§5): null removes a value, absent ones stay. A name the schema does not define, or a value not of its type, is invalidProperties with properties: ["fields/<name>"].
  • faces: only on an analysed picture (§8.3); elsewhere invalidProperties.
  • A write over an annotation of older content starts afresh: nothing of the old one carries over.

updated[<nodeId>] is null, or, when the update held faces, { faces: [<faceId> | null], facesRefused: [{ at, type, description }] } (§8.3).

4.2 Removing from another source's annotation #

An update naming another source only removes from that source's annotation: { source, description: null?, labels?, text: null?, fields: { <name>: null }? }. labels is the list to keep, a subset of that source's labels (a label not there is forbidden). It takes no blobId and no faces (another source's faces are changed through photo/faces, §8.2); a description, text or field value other than null is forbidden. This is how the person (or a front-end acting for them) declines what an application said after keeping it as their own.

4.3 Destroy #

"<nodeId>" removes the caller's own annotation of the node (its text included); "<nodeId>/<sourceId>" removes that source's. A node not read by the caller is notFound.

4.4 SetErrors #

type When
notFound no such file, or one the caller cannot read
contentChanged blobId is not the file's content now
invalidProperties a member that is not one of the above; a folder; a value out of its bounds (properties names it)
tooLarge text over 1 MiB
forbidden an embedding without drive:faces; anything but a removal in another source's annotation
serverFail the write did not land (other writes kept changing the file); try again

5. AnnotationSchema: fields an application defines #

5.1 The schema #

One per source per account; its id is the source id.

{ "id": "<sourceId>", "source": Source, "updated": UTCDate,
  "fields": { "<name>": FieldDefinition } }
FieldDefinition Type Default
type string | strings | number | boolean | date | localDate —
title { <language tag>: String } — x-default required; each 1–64 characters
shown Boolean true shown in the web apps' details
facet Boolean false offered as a filter with counts (§7)
searchable Boolean false string values enter the full-text index (body, text)
sortable Boolean false offered as a sort (field comparator)
unit String | null null 1–16 characters, shown after a number
  • Names match [a-z][a-zA-Z0-9]{0,63}; at most 64 fields.
  • Language tags are x-default or BCP 47 (de, pt-BR).
Type Value
string String ≤ 1,000
strings ≤ 100 Strings of ≤ 200
number a finite number
boolean Boolean
date UTCDate (2026-11-15T10:00:00Z)
localDate a calendar date YYYY-MM-DD

5.2 Methods #

  • AnnotationSchema/set { accountId, schema: { fields } | null } replaces the caller's whole schema (null removes it). Answers { accountId, oldState, newState, schema }, the schema with defaults filled in. An invalid schema is the method error invalidArguments naming what is wrong. Changing a field's type drops its values; removing a field drops its values; both leave the index at once and are never answered again.
  • AnnotationSchema/get { accountId, ids }: ids are source ids, null for every source's.
  • AnnotationSchema/changes as RFC 8620; push type AnnotationSchema.

5.3 The field condition #

{ field: { source, name, <operator> } }, at least one operator:

Operator Value Matches
equals a value of the field's type the value, strings without case (NFC)
in list of values any of them
min, max number, UTCDate or YYYY-MM-DD inclusive bounds; for number, date, localDate only
exists Boolean the node has a value (or not)
contains list of Strings a strings value holding any of them

A field the source's schema does not define matches nothing (and exists: false everything). Several field conditions combine with AND, OR and NOT.

6. AnnotationView: views an application contributes #

A saved query the web apps list beside their own places ("Invoices", "Screenshots"). A view is a query, never a folder: it lists what the index answers now, so an application keeps a view current by writing fields.

Property Type
id Id server-set, from the source and key
key String 1–200 the source's own key; immutable
source Source
title { <language tag>: String 1–64 } x-default required
description { <language tag>: String 1–200 } | null
in "files" | "photos" which web app lists it
filter FileNode filter | null as FileNode/query takes it, checked by the same parser; names no account
sort FileNode comparators | null
layout "list" | "grid" default grid
columns String[] ≤ 4 the source's shown fields, for the list layout
  • AnnotationView/set (drive:annotate), at most 64 objects per call: create needs key, title and in; a key the source used before updates that view and answers it as created ({ id, source }). At most 50 views per source (overQuota). A source updates and destroys its own views only (forbidden). With drive:write, a credential may destroy another source's view: the person removed it, and its key is refused from then on (removed). A filter or sort FileNode/query would refuse is invalidProperties naming filter or sort.
  • AnnotationView/get { accountId, ids } (ids: null for all) and /changes; push type AnnotationView.

7. FileNode/facets #

{ accountId, filter, facets: [{ source?, name, limit? }] } → { accountId, facets: [{ source?, name, values: [{ value, count }] }] }: for each facet, its values among the nodes the filter matches, the most frequent first, then by value.

  • 1–10 facets; limit 1–100, default 20.
  • Without source, a built-in facet: labels (every source's labels), person (face names) or type (media type).
  • With source, that source's field, which must be facet: true (invalidArguments otherwise). Strings group without case and show as written; numbers, booleans and dates as their values.

8. Faces and people #

8.1 PhotoFaceRegion (photo.faces) #

Field Type
id String 1–32 of A–Z a–z 0–9 _ -, stable within the picture
area { x, y, w, h } centre and size as fractions of the picture as displayed (after orientation); x, y 0–1, w, h above 0 and at most 1
name String | null the person's name
nameBy "person" | SourceId | null who named it
source SourceId | null who found it; null when drawn by the person or read from the file's own metadata
confidence 0–1 | null the finding source's
hasEmbedding Boolean a current embedding is kept; false to whoever lacks drive:faces

Regions are kept in the picture's XMP sidecar as MWG regions, so other programs (Lightroom, digiKam) show them; faces those programs wrote are read too (source: null). A person is a name: two faces with the same name are the same person. At most 500 regions per picture.

8.2 The person's faces: FileNode/set photo/faces #

With drive:write, the whole list as it should be, in the displayed frame: an entry without id is drawn ({ area, name? }), one with an id is moved (area) or named (name, null unnames), and a region left out is removed. Server-set fields of an entry are ignored. Every region so written is the person's: nameBy: "person" (or null unnamed), and no application changes or removes it again.

8.3 An application's faces: FileAnnotation/set faces #

faces: [{ id?, area?, name?, confidence?, embedding? }], at most 500, on an analysed picture, with the update's blobId guarding the content:

  • The list replaces the regions this source found that no person touched. An entry without id is a new region (area required); one with the id of the source's own region is that region again (its id kept; area and confidence may change); one with the id of another region only names it, and only while it is unnamed and no person touched it.
  • A new region overlapping one the source does not replace (intersection over union ≥ 0.5) is refused as the same face.
  • A source naming a face sets nameBy to itself.
  • embedding: { model, vector }: model 1–100 characters naming the model and its version; vector base64 of little-endian float32, 64–1,024 dimensions, not all zero, normalised to unit length by the server. Needs drive:faces (else the whole update is forbidden).

The answer for the node is { faces: [<faceId> | null], facesRefused: [{ at, type, description }] }: per entry, the region it is now or null, and for each refusal at (the entry's id, or its index) and type:

type
touchedByPerson the person named, moved or drew it
alreadyNamed only an unnamed region may be named
sameFace a region already marks this face
notFound no such region on this picture
invalid an area or confidence out of bounds, or a new region without an area

An embedding is kept for the region's content; a content change drops it. Embeddings are never in the sidecar, a file, a ZIP, WebDAV or a share.

8.4 PhotoFace #

A region as an object of its own, id <nodeId>/<faceId>:

{ id, nodeId, faceId, area, name, nameBy, source, confidence, hasEmbedding, model, embedding? } — model the current embedding's model or null; embedding (base64) only when asked for in properties and only under drive:faces.

  • PhotoFace/query { accountId, filter, sort, position, limit } → ids, total; limit default 256, answered as limit when the request gives none. Conditions: name (exact, without case), isNamed, source, inAncestorId, updatedAfter (a picture written, or an embedding written, at or after it), similarTo: { id, minSimilarity } (needs drive:faces; the given face must hold an embedding; default 0.6; only faces of the same model; sorted most similar first). sort is [{ property: "similarity" }] under similarTo only. canCalculateChanges is false.
  • PhotoFace/get, ≤ 500 ids.
  • PhotoFace/changes: over a journal of the last face changes; below its floor cannotCalculateChanges. A picture that leaves the drive is told by FileNode/changes. Push type PhotoFace.
  • PhotoFace/set { accountId, destroyEmbeddings: true | { source }, limit }: removes embeddings (every source's, or one source's), limit 1–1,000 (default 1,000) per call; answers { accountId, destroyed, remaining }; call again until remaining is 0. The drive's owner, or drive:faces.

8.5 People #

  • PhotoPeople/get { accountId } → { accountId, state, list: [{ name, count, coverId }] }: the distinct names of named faces, the most pictures first; coverId is one of their faces.
  • PhotoPeople/set { accountId, rename: { <old>: <new> | null } } (drive:write): every face named <old> (without case) is renamed as the person's write (null unnames); a rename onto an existing name merges the two. At most 500 pictures per call: { accountId, renamed: { <old>: n }, notRenamed, remaining }; call again until remaining is 0.
  • PhotoTags/get { accountId, inAncestorId?, notInAncestorIds? } → { accountId, state, list: [{ name, count, coverId }] }: the distinct keywords of the read pictures (photo.keywords, without case) below the folder inAncestorId and outside each folder of notInAncestorIds (at most 16), the most pictures first; coverId is the most recently taken picture carrying it (a node id); state is FileNode's. A tag's pictures: FileNode/query with keyword.

8.6 Names in a shared album #

Each rule of an album's share keeps or removes face names, as it does location: album.faceNamesKeep lists the rules that keep them (a principal id for a person, group or guest, a password rule's id, a link's own id), default none, written by whoever may share the album (forbidden otherwise). A reader reached through any keeping rule sees names; the owner and their delegates always do. To a share's reader whose rules all remove them:

  • photo.faces answers every region with name: null and nameBy: null; the boxes stay (hasFaces still finds them).
  • personName, the person facet and the face-name part of body and text find nothing by a name in that album.
  • A photo/faces write that names a face is forbidden; moving, drawing (unnamed) and removing boxes are taken, and a name already there is kept.
  • PhotoFace/*, PhotoPeople/* and PhotoTags/get are a drive's own: a share's account is accountNotSupportedByMethod for every reader.
  • The picture's bytes and sidecar are served without names (and without location when that is removed too): the ETag carries -nonames (after -nogps when both go).

9. FileVector: vectors of content #

9.1 The object #

One source's vectors of one file, id <nodeId>/<sourceId>:

{ id, nodeId, source, blobId, updated, vectors: [{ model, kind, vector }] }

  • model: 1–100 characters naming the model and its version (clip-vit-b-32@1). Vectors of different models are never compared.
  • kind: "image" or "text".
  • vector: base64 of little-endian float32, 64–2,048 dimensions, not all zero, normalised to unit length by the server (answered normalised).

Vectors are not biometric: every reader of the file reads them. A content change lapses them.

9.2 Methods #

  • FileVector/set { accountId, update: { <nodeId>: { blobId, vectors } }, destroy: [<nodeId> | "<nodeId>/<sourceId>"] } (drive:annotate), ≤ 256 per call: an update replaces the caller's vectors of the node (≤ 4); blobId guards as in §4.1 (contentChanged). A destroy takes the caller's vectors, or the named source's. No create.
  • FileVector/get { accountId, ids }, ≤ 500 ids.
  • FileVector/query { accountId, filter: { model, source, inAncestorId, updatedAfter }, position, limit } → ids sorted by id, total; limit default 256, answered as limit when the request gives none; canCalculateChanges false. In a drive's own account.
  • FileVector/changes: ids whose vectors changed (updated) or are all gone (destroyed). Push type FileVector.

9.3 Similarity search: similarTo #

FileNode/query condition similarTo: { model, vector, minSimilarity }: the files with a current vector of that model and dimension whose cosine with vector is at least minSimilarity (−1–1, default 0.2), a file's best vector counting. It combines with every other condition; sort by similarity to get the nearest first. The query vector is the application's: the server embeds nothing.

10. AlbumSuggestion #

Property Type
id Id server-set, from the source and key
key String 1–200 the source's own key; immutable
source Source
name String ≤ 255 a valid folder name
reason String ≤ 500 | null
photoIds Id[] 1–5,000 pictures of the library (under Pictures, not in Albums); ones no longer there are left out as read
coverId Id | null one of photoIds
state pending | accepted | dismissed
albumId Id | null the album made of it, once accepted
created, updated UTCDate
  • AlbumSuggestion/set (≤ 256 per call). By the source (drive:annotate): create (key, name, photoIds, optional reason, coverId); a key it used before updates that suggestion and answers it as created; update and destroy of its own pending ones. A dismissed key refuses create and update with dismissed, an accepted one with accepted: it is never offered again. At most 200 pending per source (overQuota). By the person (drive:write): an update of state alone (with albumId, a folder at Pictures/Albums/<name>, when accepting), on any source's suggestion. A pending suggestion left with no picture is removed by the next write.
  • AlbumSuggestion/get (≤ 500 ids, null for all), /changes, /query { filter: { state, source, updatedAfter }, sort: [{ property: "created" | "updated", isAscending }] } (default newest created first), /queryChanges (answers only "no change"; else cannotCalculateChanges). Push type AlbumSuggestion.

11. AnnotationSource #

The sources that wrote into a drive: { id, actor, app, firstWrite, lastWrite, counts: { annotations, faces, embeddings, vectors, pendingSuggestions, views, schema } }. The counts are what the source holds now. AnnotationSource/get { accountId, ids } (null for all) and /changes: every source that wrote into the drive, to its owner and to a delegate alike. Every source in a drive is its owner's: an application, an app password and a delegate all act as the owner, marked only by the application. Push type AnnotationSource.

12. AppData/destroy #

{ accountId, source, limit? } → { accountId, source, destroyed, remaining }: one page of what the source added — its annotations (≤ limit, 1–256, default 256), then its pending suggestions, its views, its faces no person touched, its embeddings and its vectors. Call again until remaining is 0. A source removes its own; the drive owner's web session, or the source's own actor's, removes any source's.

13. Method errors #

type When
unknownMethod the method's capability is not in using, or the credential's app password does not open it
invalidArguments an argument out of its rules; the description says which
unsupportedFilter, unsupportedSort an unknown condition or comparator, or one used without what it needs
forbidden the credential lacks the right (description names it)
accountNotFound no such account for this credential
accountNotSupportedByMethod a share view for a drive-only method; a method outside the Pictures confinement
requestTooLarge over a per-call limit
stateMismatch ifInState is not the current state
cannotCalculateChanges /changes or /queryChanges cannot answer from that state
anchorNotFound a query's anchor is not in its results
serverUnavailable the install keeps no such data

14. Types and formats #

  • Id: an opaque string; compare it, never parse or build it. The ids of faces and vectors are two ids joined by / (<nodeId>/<faceId>, <nodeId>/<sourceId>), which an application may split at the first /.
  • UTCDate: 2026-10-26T09:12:03Z, optional fractional seconds.
  • LocalDate (photo.taken, photoTakenAfter): 2026-10-14T09:12:03.
  • A field of type localDate is a calendar date 2026-11-15.
  • Vectors: standard base64 (with padding) of little-endian IEEE 754 float32, dimensions × 4 bytes.