# 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](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](signing-in.md) §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`** | `{ : 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](keeping-up.md) §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[]`. | 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` | `{ : 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: { : Update }, destroy: [ | "/"] }`. 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`: `{ : 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/"]`. - `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[]` is `null`, or, when the update held `faces`, `{ faces: [ | 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: { : 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 `""` removes the caller's own annotation of the node (its text included); `"/"` 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. ```json { "id": "", "source": Source, "updated": UTCDate, "fields": { "": FieldDefinition } } ``` | FieldDefinition | Type | Default | | |---|---|---|---| | `type` | `string` \| `strings` \| `number` \| `boolean` \| `date` \| `localDate` | — | | | `title` | `{ : 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, } }`, 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` | `{ : String 1–64 }` | `x-default` required | | `description` | `{ : 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: [ | 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 `/`: `{ 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: { : | null } }`** (`drive:write`): every face named `` (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: { : 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 `/`: `{ 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: { : { blobId, vectors } }, destroy: [ | "/"] }`** (`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/`, 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 `/` (`/`, `/`), 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.