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. Chaos sort shelf tokens compose the same way: box:5 (one box), loc:5-234 (one slot), is:unshelved (on hand, no slot) and is:location-drift (shelf count exceeds what is on hand). Each card row carries locations[], location_code and unshelved_quantity, and sort=location walks box then slot.

Narrow a filtered selection

Raw HTTP requests to GET /api/cards and POST or DELETE /api/lists/{id}/cards can pass selected_ids as a comma-separated string of inventory row IDs. This intersects the selected rows with the caller-owned search/filter scope. An empty or malformed string matches nothing; another seller’s rows are excluded. These are inventory row IDs, not TCGplayer seller SKUs, so custom listings are supported. Existing card_ids and tcgplayer_ids membership forms keep their current behavior.

Member stock completion

Browser staff sessions may read/create lists and add their own private contributions or shared live stock. Explicit IDs, searches, membership flags and counts omit other members’ private contributions, including published rows still awaiting reconciliation. List objects include add_cards_url. Rename, delete and remove remain owner actions. Existing bearer read/write scopes and store-wide inventory access are unchanged. Shelving and verified publish recovery are browser-session actions, not new MCP grants. List additions serialize selector resolution and insertion with inventory promotion. Selecting a retired published contribution directly returns HTTP 409 stock_promoted_reload; reload and choose the current live listing. Normal search and selected_ids filters resolve after promotion and may report zero matches; an explicit gone-stock search selecting a retired contribution returns the same 409 response. No partial membership insert occurs on this conflict. Existing list caps and retry repair of pricing-rule membership remain unchanged.