Files
t5de-hub/docs/products.md
T
dhkatzandClaude Opus 5.5 b4f6fce9df
CI / test (push) Successful in 43s
Product registry: catalogs, revisions, product info and libraries
The hub registers catalogs (hidden until a moderator approves them), hands out upload tokens, and records the
revisions catalogs register after validating uploads: numbered, never changed, signed by the hub (its own
Ed25519 key, published at /.well-known/t5de-keys.json). Creators edit product info (descriptions cleaned by an
allowlist) and can retract revisions. People get products into their library, optionally staying on a
revision; the proxy asks which revision to load and where. Contract in docs/products.md.

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

6.3 KiB

Products, catalogs and the library: the contract

How the hub (registry), catalogs (t5de-catalog, file hosting) and T5DE's proxy work together. The design and the reasons are in design.md ("Products and catalogs", "Revisions", "In the client's shop and inventory"); this is what each side sends.

Words

  • Product: a T5 product, owned by its creator (a hub person). ID: 10 characters of a-z2-7 (random), e.g. k3mq7xa2pd. Valid as a pack ID, so the proxy can store a downloaded product as a pack.
  • Revision: a product's files, numbered 1, 2, 3... Never changes. Its file list is [{name, sha256, size}], sorted by name; sha256 is lowercase hex.
  • Catalog: a server storing files by sha256 and serving them. Registered on the hub; hidden until a hub moderator approves it.
  • Library: the products a person "got" (free), per person (all their IMVU accounts), optionally staying on a revision.

Tokens

The hub signs tokens with its own Ed25519 key (EdDSA JWTs, compact form), published as a JWKS at GET /.well-known/t5de-keys.json (kid in each token's header). Catalogs fetch it (and again when they see an unknown kid). Claims always include iss (the hub's API URL), typ (below) and iat.

typ Other claims Lifetime Use
t5de-upload sub (person ID), catalog (catalog ID), product (optional: a new revision of this product) 30 min the creator's browser uploads to the catalog
t5de-access sub (person ID), adult (bool) 1 h the proxy fetches adult files from catalogs
t5de-revision product, revision, rating, files (the file list), parent (IMVU product ID or 0) none (exp absent) the hub vouches for a revision's files

Catalog API (t5de-catalog)

Served at the catalog's public URL. CORS: the hub website's origin may call POST /api/v1/uploads.

GET /health {ok: true}
POST /api/v1/uploads Authorization: Bearer <t5de-upload token>; multipart form: file (the product as a zip: Create Mode's .chkn, files at the root) and notes (text, required for a new revision of an existing product, optional for the first). The catalog validates the files (t5de-api pack rules: names, limits, index.xml with ProductRating GA/AP), stores each file by sha256, then registers the revision with the hub (below) and answers the hub's answer: 201 {product, revision}. Errors: {error, message}, 400 for bad files, 401 for a bad token, 502 if the hub refused or failed.
GET /api/v1/files/<sha256> the file's bytes, Cache-Control: public, max-age=31536000, immutable and ETag = the hash. A file used only by adult revisions needs Authorization: Bearer <t5de-access token> with adult: true (else 401/403, and Cache-Control: private). 404 if the catalog doesn't have it.
GET /api/v1/revisions/<product>/<revision> {product, revision, rating, files}, for revisions the catalog hosts

Configuration (environment): CATALOG_ADDR (listen address, default 127.0.0.1:4102), CATALOG_DATA (data folder: SQLite database and files), CATALOG_PUBLIC_URL, HUB_URL (the hub's API), CATALOG_ID and CATALOG_KEY (from registering on the hub), WEB_ORIGIN (the hub website, for CORS).

Hub API

For catalogs (server to server)

Authorization: Catalog <catalog ID>:<key>. Only approved catalogs.

POST /api/v1/catalog/revisions {uploadToken, files, rating ("general" or "adult"), parent (IMVU product ID or 0), name (from index.xml, for a new product), notes} → 201 {product, revision}. The upload token must be for this catalog. Without product in the token, a new product is made (owner: the token's person, name from name). With it, a new revision of that product (the person must own it; notes required). A file list identical to the latest revision's is refused (409 unchanged).

For people (website, signed in; the proxy with its bearer token)

POST /api/v1/catalogs register a catalog: {name, url, contact, visibility ("public"/"private"), membership ("open"/"approval"/"invite")} → 201 {catalog, key} (the key is shown once)
GET /api/v1/catalogs (anyone) approved public catalogs; the person's own, any status
POST /api/v1/products/upload {catalog, product?} → {token, url}: an upload token and the catalog's upload URL
GET /api/v1/products?q=&creator= (anyone) products, newest first; adult ones only for people with an adult account
GET /api/v1/products/:id (anyone, adult rule as above) {product, revisions, inLibrary?, mine?}
PATCH /api/v1/products/:id (creator) {name?, description?, rating?}; description is HTML, cleaned by the allowlist; rating may go general → adult
POST /api/v1/products/:id/revisions/:n/retract (creator)
GET /api/v1/library {library: [{product, revision, stayOn}]}: the person's products, each with the revision to use
PUT /api/v1/library/:id get a product (add it)
DELETE /api/v1/library/:id remove it
PATCH /api/v1/library/:id {stayOn: n or null}
GET /api/v1/products/:id/files for the proxy: {product, revision, rating, parent, name, files, catalogs: [url], token} for the revision this person uses (their pin, else the latest that isn't retracted); token is the t5de-revision token
POST /api/v1/access for the proxy: {token}, a t5de-access token (adult if any of the person's IMVU accounts counts as adult)

The realtime connection also tells the proxy {"type": "library"} when the person's library changes.

Console (moderators)

GET /api/v1/console/catalogs, POST /api/v1/console/catalogs/:id/approve, .../reject.

The proxy

  • Fetches the library at connect and on library messages; for each product, GET /api/v1/products/:id/files, downloads missing files from a listed catalog (GET <catalog>/api/v1/files/<sha256>, with an access token for adult revisions), checks each sha256, and writes the product as a pack in its cache (%APPDATA%\T5DE\proxy\hub-products\<id>\: t5de-product.json with the hub's name and rating, and files/). From there it's served like any pack.
  • Products others wear in a room that it doesn't have are fetched the same way, then given to the client.
  • Library products appear in the client's inventory (see the proxy's README).