Files
t5de-hub/docs/api.md
T
dhkatzandClaude Opus 5.5 6ba4d83317
CI / test (push) Successful in 6m38s
Room channel and rooms list
The realtime connection now carries the IMVU chats a client is in: the hub tells each proxy in a chat which
other T5DE users are there and what community products they wear (adult products only to adult accounts), on
joining and on every change. Rooms T5DE users enter are looked up on IMVU; those that are public, listed and
whose owner turned on "List my public rooms" appear on the new Rooms page, with the T5DE users in them who
show they're online. AP rooms are shown to adults only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 18:14:43 -07:00

4.3 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.

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}