Moderators (up to 20 IMVU accounts, set by ID or name) and the owner account boot and ban over realtime (room.boot); bans are listed and lifted over REST. Private rooms are unlisted and joined by the owner, moderators and holders of an accepted invite (room.invite / room.invited / room.invite_reply / room.invite_answer). Room images (PNG/JPEG by magic bytes, up to 512 KB) are stored in the database and served with cache headers. Per-person favorites, ratings and recent rooms. room.watch pushes room.occupancy; participants carry their IMVU name, and GET .../participants lists them. Contract in docs/hosted-rooms.md, "Phase 2". Co-Authored-By: Claude Opus 5.5 <[email protected]>
8.4 KiB
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 <session token>.
Errors are {"error": "<code>", "message": "<text for people>"} 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 GETs also signed out.
POST /api/v1/hosted-rooms |
{name, description?, roomPid, rating, maxUsers?, imvuId, privacy?} → 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): public ones, and private ones the person owns or moderates; adult ones for adults only. ?mine=1: the person's own; ?favorites=1: their favorites; ?recent=1: the last 20 they joined. |
GET /api/v1/hosted-rooms/:n |
{room, scene: {contents, hash}, myRating}; 404, or 410 closed |
PATCH /api/v1/hosted-rooms/:n |
(owner) {name?, description?, rating?, maxUsers?, roomPid?, privacy?, moderators?} → {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 |
GET .../:n/participants, GET .../:n/bans, DELETE .../:n/bans/:imvu, GET/PUT/DELETE .../:n/image, PUT/DELETE .../:n/favorite, PUT/DELETE .../:n/rating |
Phase 2: docs/hosted-rooms.md, "Phase 2" |
A room is {n, instanceId, chatId, name, description, roomPid, rating, maxUsers, ownerImvuId, ownerName, occupancy, privacy, moderators, moderatorNames, image, favorite, score, ratings, createdAt}; instanceId is <owner IMVU ID>-<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,
<owner's customer ID>-<n> (^[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} |