4.8 KiB
API Endpoint Versioning Scope (Design in Progress)
Status: decision deferred. The /api/ prefix has already been changed to /api/v1/ for every
route under that prefix (inventory items, storage locations, search, friends, friend requests,
groups, group invites, id mapping, tags/properties/categories/availability policies/info,
files/item files/staged files, and account export/import/delete). Whether that blanket approach is
the right scope, or whether versioning should be narrower, is the open question below.
Problem
A versioned URL prefix matters most where two independently-deployed servers need to agree on a
wire contract: federation. An endpoint that's only ever called by a server's own frontend against
its own backend is deployed in lockstep with that backend, so it has no independent version to
track. Applying a uniform prefix everywhere is simpler, but it also prefixes a large number of
routes that never cross a domain boundary, and skips one non-/api/ route
(/auth/user/<handle>/, used to fetch a friend's profile/avatar) and the file/thumbnail serving
routes (/media/...), both of which do cross a domain boundary and arguably belong in the
versioned surface more than some of the routes currently in it.
One exception either way: the version/capability discovery route (GET /api/version/) has to stay
unprefixed no matter which option is chosen — it's how a caller finds out which versions a server
speaks in the first place, so it can't itself live behind a version segment.
Options
Option A: version every real endpoint uniformly.
Every route under /api/, /auth/, /admin/, and /media/ gets the /api/v1/ prefix (except
/api/version/), regardless of whether it's ever called across a domain boundary. Simple to state
and apply; doesn't require maintaining a classification of which routes are federation-facing.
Option B: version only routes that actually cross a domain boundary.
Only routes a server's own frontend calls against a different server (directly, or that another
server calls against this one) get the /api/v1/ prefix. Everything else keeps its existing,
unprefixed path. Smaller versioned surface, but requires keeping the classification below current
as routes change, and means a single URL path can't ever serve both a same-domain and
cross-domain purpose without the whole path being versioned.
Current classification
Verified against actual call sites (which routes are dispatched against a friend's/foreign server rather than the caller's own home server), not just against which routes are technically reachable by a non-local caller — several routes accept a broader signature-based authentication than they actually need, and one route family enforces "local caller only" itself even though its authentication layer would allow a remote identity through.
Crosses a domain boundary today:
POST /friendrequests/(also called against the recipient's own server, not just the sender's)GET/POST /groupinvites/(invite delivery is posted to the invitee's own server)POST /group_invites/accept/(posted to the group's own home server)GET /groups/<handle>/,DELETE /groups/<handle>/members/<id>/,POST /groups/<handle>/invites/* /inventory_items/<handle>/...,* /storage_locations/<handle>/...(group-owned or friend-owned items/locations)GET /search/(fans out to every known server)GET /resolve_short_id/<kind>/<owner_id>/<local_id>/GET /auth/user/<handle>/(friend profile/avatar lookup — not currently under/api/)GET /media/...,GET /thumbnail/<size>/...(image bytes fetched from the owning server — not currently under/api/)
Never observed leaving the caller's own home server:
GET/POST /friends/,DELETE /friends/<id>/,DELETE /friendrequests/<id>/GET /groups/(the collection route, as opposed to/groups/<handle>/)DELETE /groupinvites/<id>/,POST /groupinvites/<id>/accept/,GET /groupmemberships/GET /idmap/(distinct from/resolve_short_id/..., which does cross domains)* /workflows/...GET /tags/,GET /properties/,GET /categories/,GET /availability_policies/,GET /info/,GET /domains/(also not currently called from the frontend at all)POST /import/,GET /export/,DELETE /account_data/,DELETE /account/— these accept the same broad authentication as the federation-facing routes, but reject any non-local caller themselvesGET /files/,* /item_files/...,* /staged_files/...GET/PATCH /auth/user/,POST /auth/register/,POST /auth/token/,GET /auth/preferences/,GET/PUT /auth/self/preferences/,DELETE /auth/self/preferences/<key>/,* /auth/users/...(admin management)- everything under
/admin/(domain/category/property/tag administration)
Exception regardless of which option is chosen:
GET /api/version/