Appearance
Collectibles and trading flow
A collectible arrives from a scan or a campaign and lands in the customer's vault. Everything on this page is what happens next: putting one on the board, claiming somebody else's, and the four ways a reply can end.
Press Play. The five paths are the five journeys the hub supports — the phone follows each one while the steps name the call underneath it.
4:4713
open-trade board
my trading hub
search trades...
active trades: 3
filter
Night Shifttrade · wants: First Pour
Garden Series piece 1 of 5gift
The one idea worth holding on to
A trade is not a message. It is a row with a status, and almost everything confusing about this API follows from that:
- A gift and a trade use the same create and reply endpoints. What separates them is whether the listing asks for a card back.
- Whether a reply completes a trade or merely offers is decided by the server, by comparing what you offered against what was asked for. You do not choose it.
- One of the four types never becomes a row you can act on at all: a
direct_giftis finished the moment it is created.
Statuses are open, inprogress, completed, expired, rejected and cancelled. A client never sends one.
Stages
API calls by stage
| Stage | Action | API call | Auth |
|---|---|---|---|
| — | The vault, with locked cards shown | GET /api/v1/brand/mobile/collectibles — the catalogue, each row carrying what you own of it | Customer |
| — | The vault, for picking a card to offer | GET /api/v1/mobile/user/collectibles — owned rows only | Customer |
| 1 | Hub loads, "listed / sent" | GET /api/v1/brand/mobile/trade-logs — participant=sender&type=active, both required | Customer |
| 1 | "trades completed" counter | The same endpoint at participant=both&type=completed. No counter endpoint exists | Customer |
| 2 | Find a person | GET /api/v1/user/brand/mobile/customers — search, plus limit+offset as a pair | Customer |
| 2 | Open their collection | GET /api/v1/mobile/open/user/collectible/:user_id | Brand |
| 2a–3b | Create any of the four | POST /api/v1/brand/mobile/trade — type, uid, user_collectible_id, then desired_collectible_ids or receiverUserId | Customer |
| 4 | The board | GET /api/v1/brand/mobile/trades — search, limit+offset as a pair | Customer |
| 4 | One listing | GET /api/v1/brand/mobile/trade/:trade_id | Customer |
| 5–7 | Claim, offer, accept or reject | POST /api/v1/brand/mobile/trade/reply | Customer |
| — | Take your listing down | DELETE /api/v1/brand/mobile/trade/:trade_id — owner only, open only | Customer |
| — | The two hub switches | PUT /api/v1/mobile/details/update — open_to_trade, show_collections_for_trade | Customer |
| — | Leaderboards | GET /api/v1/user/mobile/user/collectibles/leaderboards — optional campaignId | Customer |
Reading the board expires things
GET /brand/mobile/trades sweeps every open listing past its expiration into expired before it selects anything, in the same transaction. Nothing else in the mobile set does this, so a listing lapses because somebody opened the board — on a quiet brand, expired rows sit there until the next read.
The board also returns open_trade and open_gift only. Direct trades never appear on it; they reach their recipient through the trade log.
The type filter has no endpoint behind it
The board's "open trade / open gift / all" chips are client-side. The validator takes search, limit and offset and rejects anything else, so filtering narrows the page already fetched. With limit/offset in play that hides rows rather than finding them, and the count beside the filter counts what was fetched, not what exists.
Two ids that look alike
user_collectible_id is a row you own. desired_collectible_ids[] holds definitions, each { name, collectible_id, asset } — all three required.
The server matches a reply by comparing the offered row's collectible_id against that list, so any copy of a wanted collectible satisfies it. This is also why the picker has to read the owned list and not the catalogue: the catalogue has no user_collectible_id to send for a card you do not hold.
What a reply actually does
One endpoint, four endings, and the client picks none of them:
| You send | Server finds | Result |
|---|---|---|
type: open_gift | — | completes; the card transfers |
reject_trade: true | — | rejected, and the other side is notified |
| a card that is on the wanted list | a match | completes; both cards transfer |
| a card that is not | no match | inprogress — a counter offer; nothing moves |
The response's current_trade_log_status is the only way to tell an instant swap from an offer now waiting. Two details that bite:
accept_offer: truecompletes only when the caller owns the listing. Sent by anyone else it falls through and records another counter.- Rejecting flips the trade itself to
rejectedonly for adirect_trade. Reject an offer on anopen_tradeand your listing stays open for the next person — which is the behaviour you want, and not what the field name suggests.
Rejected trades are not in "history"
trade-logs takes a type that maps to a set of statuses, and then filters again: under history it drops every rejected row that is not a direct_gift. A rejected open_trade appears under active instead.
A "past trades" screen built on history alone will silently omit every rejection.
The two switches are profile fields
There is no trading-settings endpoint. Manage your hub writes open_to_trade and show_collections_for_trade through the ordinary account update, and they guard different things:
| Switch | Guards | Failure |
|---|---|---|
open_to_trade | you, on create and on reply | "User Not Open To Trade" |
show_collections_for_trade | others, opening your collection | "User not allowed to show collectibles for trade" |
Note which way round the first one works: it is read off the caller, so turning it off stops you from trading rather than hiding you from other people's boards.
One endpoint, two leaderboards
The hub shows an all-time board and a collection-scoped one. Both are collectibles/leaderboards; campaignId is what changes between them.
Without it, the count is every collectible row you hold, duplicates included. With it, the campaign's steps name a set of collectibles, the count becomes distinct ones within that set, and the set's size comes back as total_collectibles_count — the 5 in 3 / 5.
Open questions
- Nothing polls. A counter offer notifies through push, SMS and email, but no endpoint reports "your trade changed" — the hub only knows by re-reading
trade-logs. How often should it? uidis client-generated and required, with no documented format and no uniqueness check on the mobile side. What should a client put in it, and what happens when two clients pick the same one?- Expiry has no floor or ceiling.
expirationis any ISO date, and omitting it means the listing never lapses. The compose screen offers fixed durations; which, and are they enforced anywhere but the client? - A hidden collection reads as an empty one.
8.6throws rather than returning[], and the throw arrives as HTTP 200 withsuccess: false. Should the profile say "collections hidden" instead of "No Collections"? - Is a
direct_giftreversible? It completes at creation andDELETErefuses it. Nothing in the mobile set takes it back. - Who can see a
direct_tradebefore replying? It is not on the board, and no endpoint lists trades addressed to you excepttrade-logswithparticipant=receiver. Confirm that is the intended inbox. - The compose screen is not designed. Choosing the card, picking up to five wanted ones and setting an expiry are drawn here from what
POST /traderequires, and are marked as such in the player. special-assets(§8.5) has no screen in this flow. What surfaces it?
Related pages
- Collectibles, trades and ownership
- Scan to claim — where a collectible comes from
- Earn and spend — the other way to earn one
- Template pages
- Auth, account and addresses
- Enum reference
- Collectibles