Skip to content

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.

1 / 5
4:4713
trading hub
active trades: 3
Night Shifttrade · wants: First Pour
open trade
Garden Series piece 1 of 5gift
open gift

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_gift is 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 ​

StageActionAPI callAuth
—The vault, with locked cards shownGET /api/v1/brand/mobile/collectibles — the catalogue, each row carrying what you own of itCustomer
—The vault, for picking a card to offerGET /api/v1/mobile/user/collectibles — owned rows onlyCustomer
1Hub loads, "listed / sent"GET /api/v1/brand/mobile/trade-logs — participant=sender&type=active, both requiredCustomer
1"trades completed" counterThe same endpoint at participant=both&type=completed. No counter endpoint existsCustomer
2Find a personGET /api/v1/user/brand/mobile/customers — search, plus limit+offset as a pairCustomer
2Open their collectionGET /api/v1/mobile/open/user/collectible/:user_idBrand
2a–3bCreate any of the fourPOST /api/v1/brand/mobile/trade — type, uid, user_collectible_id, then desired_collectible_ids or receiverUserIdCustomer
4The boardGET /api/v1/brand/mobile/trades — search, limit+offset as a pairCustomer
4One listingGET /api/v1/brand/mobile/trade/:trade_idCustomer
5–7Claim, offer, accept or rejectPOST /api/v1/brand/mobile/trade/replyCustomer
—Take your listing downDELETE /api/v1/brand/mobile/trade/:trade_id — owner only, open onlyCustomer
—The two hub switchesPUT /api/v1/mobile/details/update — open_to_trade, show_collections_for_tradeCustomer
—LeaderboardsGET /api/v1/user/mobile/user/collectibles/leaderboards — optional campaignIdCustomer

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 sendServer findsResult
type: open_gift—completes; the card transfers
reject_trade: true—rejected, and the other side is notified
a card that is on the wanted lista matchcompletes; both cards transfer
a card that is notno matchinprogress — 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: true completes only when the caller owns the listing. Sent by anyone else it falls through and records another counter.
  • Rejecting flips the trade itself to rejected only for a direct_trade. Reject an offer on an open_trade and 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:

SwitchGuardsFailure
open_to_tradeyou, on create and on reply"User Not Open To Trade"
show_collections_for_tradeothers, 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?
  • uid is 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. expiration is 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.6 throws rather than returning [], and the throw arrives as HTTP 200 with success: false. Should the profile say "collections hidden" instead of "No Collections"?
  • Is a direct_gift reversible? It completes at creation and DELETE refuses it. Nothing in the mobile set takes it back.
  • Who can see a direct_trade before replying? It is not on the board, and no endpoint lists trades addressed to you except trade-logs with participant=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 /trade requires, and are marked as such in the player.
  • special-assets (§8.5) has no screen in this flow. What surfaces it?

Batch platform, Shopify app and API documentation