list:<slug> search token, so pricing
rules, label printing, and inventory/order filters all work on a list with no
extra machinery.
All of these endpoints are mounted at /api/lists (not /api/v1/lists) and
support Bearer authentication (Authorization: Bearer YOUR_API_KEY) for AI
assistants and integrations. Reading needs mcp:read; every write needs
mcp:write.
List your lists
active_count is members still active in inventory; gone_count is members
that have since sold or been delisted (a sold card stays on the list and is
reachable via list:con-box is:gone). Pass ?tcgplayer_id= or ?card_id= to
add a member boolean to each row indicating whether that card is on the list.
Read a list’s members
Thequery field is the search token — note it goes in the search param
(an unrecognized param name is ignored and returns your whole inventory, not an
error). Hand it to any search-aware endpoint:
is:gone to see only the tombstoned members — cards that have sold
through or been delisted but that the list still remembers
(search=list:con-box is:gone). Plain list:<slug> returns the full
membership; everything else hides gone rows.
Create a list
201). The slug is minted once at creation and is
stable across renames, so list:<slug> references stay attached. A seller
may have up to 100 lists; a duplicate name returns 422.
Rename a list
list:"Old Name") re-resolve so their matches empty out instead of
silently repricing a stale set.
Delete a list
{ "deleted": true }). The cards
themselves are untouched.
Add cards to a list
GET /api/cards, in precedence
order:
| Field | Meaning |
|---|---|
tcgplayer_ids | Seller SKU ids — the dashboard’s lingua franca. Custom (photo/Collectr) listings carry a negative synthetic SKU id and are addable here too. |
card_ids | Inventory-card primary keys — the id field on each card row from GET /api/cards. Use this when you already hold the row id. |
search + game/product_line/status/… | The Cards-tab filter — adds exactly the cards you’ve filtered to, resolved server-side |
search form accepts the full inventory DSL, including price and quantity
predicates — so “add every Magic card worth $20+” is one server-side call, no
client-side paging:
{ "list": { … }, "added": 2, "requested": 2, "truncated": false }.
requested is how many of your cards the payload resolved to, so you can tell
a true no-op (added: 0, requested: 0 — nothing matched) from “already on the
list” (added: 0, requested: 2). Adds are idempotent. The per-list cap is
max_cards_per_list (1000); an over-cap add returns truncated: true, and an
already-full list returns 422 { "code": "list_full" }. Ids that belong to
another seller are silently skipped.
Remove cards from a list
{ "list": { … }, "removed": 1 }; removing a non-member is a no-op.
Over the MCP
The hosted MCP wraps every endpoint above in a typedhoard.lists.* binding, so
an AI assistant manages lists without composing raw HTTP. Reads run in
hoard_read; the mutations run in hoard_write (they throw in read mode). Run
hoard.describe('lists') to print these signatures live.
| Binding | Effect | Maps to |
|---|---|---|
hoard.lists.list() | read | GET /api/lists |
hoard.lists.create(name) | write | POST /api/lists |
hoard.lists.rename(id, name) | write | PATCH /api/lists/{id} |
hoard.lists.delete(id) | write | DELETE /api/lists/{id} |
hoard.lists.addCards(id, payload) | write | POST /api/lists/{id}/cards |
hoard.lists.removeCards(id, payload) | write | DELETE /api/lists/{id}/cards |
payload is the same filter contract as the REST add/remove body
({ tcgplayer_ids } | { card_ids } | { search, game, … }). name must be a
string — passing an object throws rather than creating a junk-named list.
list:<slug> token (the query field
on each list row), which any search-aware surface accepts —
hoard.inventory.listCards({ search: "list:<slug>" }) or
GET /api/cards?search=list:<slug>. Compose with is:gone to audit what has
sold or delisted off a list.