Skip to main content
A card list is an explicit set of specific cards — a binder, a shoebox, the stack going to a convention. Where a saved search re-evaluates against whatever matches today, a list is fixed membership: these exact cards. Lists compose with the rest of the API through the 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

Returns each list with membership counts and the search handle that reads it:
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

The query 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:
Add 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

Returns the created 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

Rename only — the slug does not change. Pricing rules written against the list name (list:"Old Name") re-resolve so their matches empty out instead of silently repricing a stale set.

Delete a list

Removes the list and its memberships ({ "deleted": true }). The cards themselves are untouched.

Add cards to a list

The body accepts the same filter contract as GET /api/cards, in precedence order:
The 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:
Returns { "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

The target ids travel in the request body. Returns { "list": { … }, "removed": 1 }; removing a non-member is a no-op.

Over the MCP

The hosted MCP wraps every endpoint above in a typed hoard.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. 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.
Reading a list’s members goes through the 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.