Scope
Collectibles APIs include pack browsing, purchase lifecycle, opening packs, user inventory views, featured showcase slots, binders, and public card provenance.Endpoint map
- Packs:
/api/packs,/api/packs/{id},POST /api/packs/{id}/purchase,/api/packs/{id}/open,GET /api/packs/{id}/odds - User inventory:
/api/user/collectibles,/api/user/packs,/api/user/packs/opened - Featured showcase:
GET/PUT /api/user/featured-collectibles - Binders:
/api/binders* - Provenance:
GET /api/v1/cards/{id}/provenance
Featured collectibles
GET /api/user/featured-collectiblesis public and returns the profile showcase.PUT /api/user/featured-collectiblesreplaces the authenticated user’s featured set (max 12).- Clients should refresh this after inventory mutations that can change ownership.
Binders
GET/POST /api/binders— list and create (optional?walletAddress=)GET/PATCH/DELETE /api/binders/{id}— contents, rename/cover, deletePUT /api/binders/{id}/cards— assign/transfer cards (1–20 items, idempotency key)PUT /api/binders/{id}/card-order— persist display orderPOST /api/binders/{id}/transfer-card— move a card to another binderPOST /api/binders/{id}/verify-pin— binder PIN (not the wallet PIN and notPOST /api/v1/user/verify-pin)
POST /api/upload/binder-cover. Binder-cover library is GET /api/user/binder-covers.
Binder PIN gate
PIN-protected binders do not return shelves or card payloads until you prove the binder PIN.POST /api/binders/{id}/verify-pinwith{ pin }. Success returns{ granted, token, expiresIn: 300 }.- Send that
tokenas thex-binder-pin-tokenheader onGET /api/binders/{id}. - Do not put the token in
Authorization. Clerk bearer auth still proves who you are; the binder token only proves this binder’s PIN.
403 with code PIN_REQUIRED and binder metadata only (hasPin: true, no shelves). Binders with no PIN return full contents after the ownership check. Ownership 403 is unchanged.
The generated OpenAPI still describes verify-pin as { granted } only. Treat the token + header contract above as the live behavior.
Card league labels on binder contents come from Team.leagueRelation, not a client-side league map.
Card provenance
GET /api/v1/cards/{id}/provenanceis public.- Response is
{ card, currentOwners[], transfers[], mints[] }assembled fromUserCollectible,MarketSale, andMintedNFT. - There is no dedicated provenance table. Unknown or empty ids return
400; missing cards return404.
Integration notes
- Pack purchase and open are separate phases.
POST /api/packs/{id}/purchasestarts a purchase ({ paymentMethod: 'crypto' | 'dsports-cash' }) and returns{ purchaseId }. PUT /api/packs/{id}/purchaseis removed. It always returns410with codeGONE. Confirm crypto purchases withPOST /api/checkout/crypto/verify.- Free packs are limited to 3 purchases per user per UTC day (resets 00:00 UTC). Over the limit returns
400. GET /api/user/packsinstance status is authoritative (pending|processing|opened|completed). Do not treat pack-templateisOpenableas per-purchase state.- Inventory endpoints should be refreshed after successful open/purchase mutations.
- Odds-disclosure values must come from backend pack payloads, not client constants.
