# The hub's API `apps/api` (Elysia on Bun). The route definitions in `apps/api/src/app.ts` are the reference: their schemas validate requests and type the website's client (Eden). This is the overview. - `/api/auth/*`: Better Auth (accounts, sessions, device sign-in, JWT keys). - `/api/v1/*`: the hub's own routes. The website reaches the API through its own origin (Next.js rewrites `/api` to it), so sessions are first-party cookies. T5DE's proxy calls it directly with `Authorization: Bearer `. Errors are `{"error": "", "message": ""}` with a 4xx/5xx status; `message` can be shown as is. Codes: `invalid`, `signed_out`, `forbidden`, `not_found`, `no_such_imvu_account`, `already_linked`, `code_missing`, `expired`, `link_cap`, `rate_limited`, `imvu_unavailable`, `unknown_product`, `hosted_room`. ## Accounts (Better Auth) | | | | --- | --- | | `POST /api/auth/sign-up/email` | `{email, password, name, username}`. Usernames 3-32 of `A-Za-z0-9_`; passwords 10-256 characters. The first account is an admin. | | `POST /api/auth/sign-in/username` | `{username, password}` | | `POST /api/auth/sign-out` | | | `GET /api/v1/me` | `{person}` | ## Proxies | | | | --- | --- | | `POST /api/auth/device/code` | `{client_id: "t5de-proxy"}` → `{device_code, user_code, verification_uri, verification_uri_complete, interval, expires_in}` (RFC 8628). `verification_uri` is the website's `/device`. | | `POST /api/auth/device/token` | `{grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code, client_id}` → `{access_token}` (a session token, used as a bearer token) or `{error: authorization_pending \| slow_down \| expired_token \| access_denied}` | | `POST /api/v1/device/approve` | (website, signed in) `{code}`: signs the waiting proxy in as this person | | `POST /api/v1/badges` | (bearer or website session) `{imvuIds}` (at most 200 IMVU account IDs) → `{badges}`: those linked to a T5DE account that turned on `showBadge` for them and isn't banned. Nothing else about them. 120 a minute per person. | | `GET /api/v1/realtime` | WebSocket, bearer token: presence, outfits, the room channel, decoration notices (`decor`) and hosted rooms (`room.*`, docs/hosted-rooms.md). Protocol in `apps/api/src/realtime.ts`. | | `GET /.well-known/jwks.json` | the Ed25519 keys JWTs are signed with | ## Website routes (signed in unless noted) | | | | --- | --- | | `GET /api/v1/home` | (anyone) `{onlineCount, links?}` | | `GET /api/v1/online` | (anyone) `{online}`: accounts showing they're online (moderators see all, with `hidden`) | | `GET /api/v1/online/events` | (anyone) server-sent events: `online` with `{online}` now and on each change | | `GET /api/v1/rooms` | (anyone) `{rooms}`: listed IMVU rooms of T5DE users, with the T5DE users in each now (AP rooms for adults only) | | `GET /api/v1/account` | `{person, links, challenges, computers, canLink}` | | `POST /api/v1/account/links` | `{username}` → 201 `{challenge}` (the tagline code) | | `POST /api/v1/account/links/:imvu/check` | `{link, moved}` | | `DELETE /api/v1/account/challenges/:imvu` | cancel linking | | `PATCH /api/v1/account/links/:imvu` | `{showBadge?, showPresence?, listRooms?}` → `{link}` (each off by default; `showBadge`: others see the T5 badge on this account in the client) | | `POST /api/v1/account/links/:imvu/recheck` | `{link}` (age checked with IMVU again) | | `DELETE /api/v1/account/links/:imvu` | unlink | | `POST /api/v1/account/signout-computers` | signs T5DE out on all their computers | ## Hosted rooms Rooms the hub hosts (docs/hosted-rooms.md, which has the details and the realtime part). Bearer or website session; the `GET`s also signed out. | | | | --- | --- | | `POST /api/v1/hosted-rooms` | `{name, description?, roomPid, rating, maxUsers?, imvuId}` → 201 `{room}`. `imvuId`: one of the person's linked accounts, shown as owner. Name 1-64 characters, description up to 512 (plain text), `maxUsers` 2-30 (default 10), `rating` `general` or `adult` (adult people only). Up to 10 open rooms a person (`room_cap`), 20 new ones an hour. | | `GET /api/v1/hosted-rooms` | `{rooms}`: open rooms, newest first (at most 200); adult ones for adults only. `?mine=1`: the person's own. | | `GET /api/v1/hosted-rooms/:n` | `{room, scene: {contents, hash}}`; 404, or 410 `closed` | | `PATCH /api/v1/hosted-rooms/:n` | (owner) `{name?, description?, rating?, maxUsers?, roomPid?}` → `{room}`. Made adult: non-adults in it are sent out. | | `PUT /api/v1/hosted-rooms/:n/scene` | (owner) `{contents, roomPid?}` → `{room, scene}`. `contents`: up to 500 `[furniture ID, product ID (integer or hub ID), placement (up to 200 characters)]`. 60 a minute. | | `DELETE /api/v1/hosted-rooms/:n` | (owner or moderator) closes it for good; everyone in it gets `room.closed` | A room is `{n, instanceId, chatId, name, description, roomPid, rating, maxUsers, ownerImvuId, ownerName, occupancy, createdAt}`; `instanceId` is `-<2100000000 + n>`, `chatId` 2100000000 + n, `occupancy` the people in it now. ## Decorations T5 products placed in ordinary IMVU rooms (docs/products.md, "Decorations"). `instance` is IMVU's room instance, `-` (`^[1-9][0-9]{0,11}-[1-9][0-9]{0,9}$`); hosted rooms' (room number 2100000000 and up) are refused (400 `hosted_room`: they have their own scene). A room's decorations are `{instance, contents, hash}`: `contents` is a list of `[furniture instance ID (integer), hub product ID, placement (up to 200 characters)]`, `hash` the sha256 (hex) of `contents` as JSON. | | | | --- | --- | | `GET /api/v1/decorations/:instance` | (anyone, also signed out) the room's decorations as the viewer sees them: only products with a revision that isn't retracted, adult ones only for people with an adult account. A room without any: `contents: []`. | | `GET /api/v1/decorations?instances=a,b` | (anyone) `{rooms: [{instance, contents, hash}]}`, up to 20 rooms, in the order asked | | `PUT /api/v1/decorations/:instance` | (bearer or website session) `{contents}` → the room's decorations as the person sees them. Only if one of the person's linked IMVU accounts is the room's owner (else 403 `forbidden`). Up to 300 items, each furniture ID once; every product must be registered (400 `unknown_product`). An empty list removes them. Everyone with a chat in the room gets `{"type": "decor", "room": instance}` on the realtime connection. 60 a minute. | ## Products, catalogs and libraries The routes and tokens are in docs/products.md. Products are `{id, name, description, rating, latest, creator, creatorUsername, parent, image, categories, createdAt, updatedAt}`; `parent`, `image` and `categories` come from the latest revision (its IMVU parent's public info). `GET /api/v1/products` takes `q`, `creator`, `category` (an IMVU category ID) and `limit` (1-200, default 60). ## Age verification Vendor-neutral (docs/design.md, "Age"): IMVU's answer is recorded when an account is linked or checked again; person-level providers (phone wallets, document checks) are started here. | | | | --- | --- | | `GET /api/v1/age` | `{providers: [{id, label, scope}], checks}`: the providers in use and the person's own checks | | `POST /api/v1/age/:provider/start` | `{returnURL}` → `{kind: "done"}`, `{kind: "redirect", url, ref}` (send the person there) or `{kind: "browser", request, ref}` (hand `request` to the browser's Digital Credentials API) | | `POST /api/v1/age/:provider/complete` | `{ref, response}`: the browser's answer, for wallet providers | | `POST /api/v1/age/webhook/:provider` | the provider's webhook; the provider checks its signature | ## Console (moderators; roles: admins) | | | | --- | --- | | `GET /api/v1/console` | `{counts, connections, online, audit}` | | `GET /api/v1/console/people?q=` | `{people}` | | `GET /api/v1/console/people/:id` | `{person, links, computers}` | | `POST /api/v1/console/people/:id/link-cap` | `{linkCap}` (0-1000) | | `POST /api/v1/console/people/:id/ban` | `{reason}`; only admins ban moderators | | `POST /api/v1/console/people/:id/unban` | | | `POST /api/v1/console/people/:id/role` | (admins) `{role}` |