# 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//`, 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//`, `DELETE /groups//members//`, `POST /groups//invites/` - `* /inventory_items//...`, `* /storage_locations//...` (group-owned or friend-owned items/locations) - `GET /search/` (fans out to every known server) - `GET /resolve_short_id////` - `GET /auth/user//` (friend profile/avatar lookup — not currently under `/api/`) - `GET /media/...`, `GET /thumbnail//...` (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//`, `DELETE /friendrequests//` - `GET /groups/` (the collection route, as opposed to `/groups//`) - `DELETE /groupinvites//`, `POST /groupinvites//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 themselves - `GET /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//`, `* /auth/users/...` (admin management) - everything under `/admin/` (domain/category/property/tag administration) **Exception regardless of which option is chosen:** - `GET /api/version/`