Writing an application for an ordner.fyi install #
This guide is for someone who writes an application against an ordner.fyi install and has never read anything else about it: a desktop photo manager, a phone app, an AI agent on a server, a script. It says everything such an application needs: how it finds the install, registers, signs the person in, reads their files and pictures, writes back what it found, keeps a copy in step, and what it owes the person whose data it reads.
An install is one organisation's self-hosted suite (mail, calendar, files,
photos and more) on one origin, here https://ordner.example. Its files and
pictures are served over JMAP (RFC 8620),
the person signs in with OAuth 2.0 and OpenID Connect, and an application
registers itself without anyone's help
(RFC 7591).
Chapters #
| Chapter | What it says |
|---|---|
| Concepts | Annotators and front-ends, sources, what the server never does, the person's control |
| Discovery | From the origin to the session and the authorization server: _jmap._tcp, /.well-known/jmap, RFC 9728, RFC 8414 |
| Registration | RFC 7591 registration with every field and rule, RFC 7592 management, limits |
| Signing in | The loopback code flow with PKCE, the device grant, what the person sees per scope, tokens, refresh, revocation, withdrawn lines; app passwords; acting as a delegate; how rights combine |
| JMAP for this API | Sessions, requests, /get /set /query /changes, back-references, upload and download, capabilities and limits |
| Reference | Every type, property, method, argument, filter condition, comparator, facet, error and limit |
| Keeping up and keeping a copy | The annotator's queue, /changes, changedAfter and updatedAfter, cannotCalculateChanges, push |
| Recipes | An annotator's loop, defining fields and views, faces and embeddings, semantic search, album suggestions, acting for someone |
| Privacy duties | Biometric data, what the person sees, Remove what it added, text the web apps treat as untrusted |
| Errors and rate limits | HTTP errors, method errors, SetErrors, 429 |
| Compatibility | How the API is versioned and what may change |
| The reference client | scripts/annotate-example.ts, a complete application in Node built-ins, walked through |
JSON Schema for every type is in
schema/, generated from the very constants the server checks
with.
The examples are executed #
Every request and answer in this guide is a file under
examples/. The install's own tests send each one to the real
server, in order, and compare its answer with the file; a change of the
server that changes an answer fails them. The guide quotes the files as they
are.
Values the server makes, which differ every time (ids, blob ids, states, tokens, dates), are written as placeholders:
${name}is a value the server made and the example names: the first time a name appears in an answer it stands for whatever the server answered there, and every later appearance, in a request or an answer, means the same value.${picture1}is the id of a picture,${picture1Blob}the blob id of its content,${accessToken}the access token of the credential that sends the request.${*}is a value the example does not name: a state, a date, a token nobody uses again.
A few names are given before the first example, by the setup the examples run in:
| Name | What it is |
|---|---|
${accountId} |
Pip's account, the drive the examples work in |
${emmaAccountId} |
Emma's account; Pip has delegated drive:read and drive:annotate on Pip's drive to Emma |
${pictures}, ${documents} |
Pip's folders Pictures and Documents |
${picture1}, ${picture2} |
Pictures/IMG_0001.JPG and IMG_0002.JPG, both read by the photo worker |
${invoice}, ${notes} |
Documents/invoice-2026-10.pdf and Documents/notes.txt |
${faceVector1}, ${faceVector2}, ${imageVector1}, ${imageVector2}, ${textQuery} |
64-dimension vectors, base64 of little-endian float32 |
The applications in the examples are registered relying parties: "Photo
Sorter" (client id rp-7Kq2mWv9XbTa), an annotator; "Library"
(rp-L1brAry0x9Qz), a front-end; and "Frame" (rp-Fr4m3sPh0t0s), granted
the Pictures folder only. The person is Pip (pip@ordner.example); Emma
(emma@ordner.example) is Pip's delegate. Ids such as account ids are
opaque: an application never builds one from a name, it reads them from the
session.
What is not executed: the person's own steps on the install's web pages
(signing in, ticking consent lines, approving a device code) happen in the
tests through the server's own functions, not over HTTP; a JMAP answer of
401 with its WWW-Authenticate header is served by the install's origin
in front of the files service and is described, not executed.