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.
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 |
GET /api/v1/realtime |
WebSocket, bearer token: presence, outfits, and the room channel. 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} |
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 |
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} |