Changelog

Notable changes to the CardNexus Public API. The most recent release is listed first.

Versions follow semantic versioning. The API is in preview, so versions are 0.x: the minor number changes on any new release — including a breaking change, such as an endpoint or field being removed, renamed, or made stricter — and the patch number on documentation-only fixes. Breaking changes are always called out in the release notes below. The version here matches info.version in the API Reference.

0.8.0 — 2026-07-19

Search your inventory server-side — by product name, tag, location, and more — and export exactly the lines a search matches.

Breaking changes

filters on POST /v1/inventory/bulk/export has a new shape. It is now the same filter set POST /v1/inventory/search accepts, so a search can be turned into an export unchanged.

Exports without filters are unaffected — if you never pass filters, there is nothing to change.

If you do pass filters, update each field:

Field Before Now
game "game": "mtg" "gameFilters": { "game": "mtg" }
productId "productId": [50212] "productIds": [50212]
condition "condition": "NM" "condition": { "op": "or", "values": ["NM"] }
language "language": "en" "language": { "op": "or", "values": ["en"] }
finish "finish": "Foil" "finish": { "op": "or", "values": ["Foil"] }
graded, forSale "forSale": true unchanged
customId, customIdPrefix, customIdContains "customIdPrefix": "LOC-" unchanged shape; matching is now case-insensitive
commentContains "commentContains": "binder" unchanged shape; matches whole words in order, not substrings inside a word

The same export, before and after:

{ "format": "csv", "filters": { "game": "mtg", "condition": "NM", "language": "en", "forSale": true } }
{
  "format": "csv",
  "filters": {
    "gameFilters": { "game": "mtg" },
    "condition": { "op": "or", "values": ["NM"] },
    "language": { "op": "or", "values": ["en"] },
    "forSale": true
  }
}

Two behaviour notes that come with the new shape:

  • Filtered exports match lines the same way the search endpoint does, so the selection can lag your latest changes by a few seconds. Exports without filters always reflect your latest changes, as before.
  • Every other search filter now works on exports too: tags, location, name, quantity, listingPrice, productType, productCategory, and expansionId — export a single location, every untagged line, or every line matching a name.

Added

  • LinesPOST /v1/inventory/search searches your inventory lines. Filter by free-text name (ranked by relevance), tags and location by label name with { "op": "and" | "or", "values": [...] } semantics — a null value matches lines with no tag or no location — customId / customIdPrefix / customIdContains (case-insensitive), commentContains (whole words in order), condition, language, finish, productType, graded, forSale, quantity and listingPrice ranges, productIds, expansionId, and the same gameFilters object POST /v1/products/search accepts. Sort with sortBy and sortDirection. Results are offset-paginated over the first 10,000 matches and use the same line shape as GET /v1/inventory; they can lag your latest changes by a few seconds, where GET /v1/inventory always reflects them. Requires inventory:read, with a dedicated inventory-search rate limit of 600 per hour. See Tags & locations.
  • LinesGET /v1/inventory takes two new filters: location (one location name) and tags (repeatable — matches lines carrying any of the named tags). Names match case-insensitively.
  • Listings — the same location and tags filters on GET /v1/listings.
  • Tags / LocationsPOST /v1/inventory/tags and POST /v1/inventory/locations accept "upsert": true: when a label with that name already exists, the call returns it — updated with any color or icon you sent — instead of failing with CONFLICT. One call then guarantees the label exists before you reference it on inventory lines.

Fixed

  • Lines / ListingsGET /v1/inventory and GET /v1/listings now accept a single productId, e.g. ?productId=10000. Passing one value previously returned 400; passing two or more (?productId=10000&productId=10001) already worked. Both forms now behave the same.
  • Sales / Purchases — the same fix for the status filter on GET /v1/sales and GET /v1/purchases: a single ?status=… is now accepted, matching the repeated form.
  • Products — examples across the reference and guides showed yugioh as the Yu-Gi-Oh! game slug; the actual slug is ygo. Example-only fix; the endpoints are unchanged.

0.7.0 — 2026-07-18

Adding or updating a card in a list with a language, finish, or game the product doesn't exist in now fails with a clear validation error instead of a server error.

Changed

  • List itemsPOST /v1/lists/{listId}/items now returns a 422 naming what's wrong with a card instead of a 500: LANGUAGE_NOT_AVAILABLE when the product doesn't exist in the language you sent (the response lists the languages it does exist in), FINISH_NOT_AVAILABLE when it doesn't exist in the finish you sent (the response lists the finishes it does), PRODUCT_GAME_MISMATCH when the product belongs to a different game than the list, and ITEM_NOT_FOUND when the itemId you sent is not in the list.

0.6.0 — 2026-07-17

Every sold item names the inventory line it came from, cart items say what you're buying — condition, finish, language, grading — and each optimizer package names its seller.

Added

Changed

  • Optimizer (experimental) — each package in a run's result embeds its seller — id, username, country, type, and rating — replacing the bare sellerId, on GET /v1/optimizer/runs/{id}.
  • Optimizer (experimental) — each option in a run's result carries modes, the list of objectives it satisfies (lowest_price, fewest_sellers, balanced), replacing the single kind. When two modes produce the same solution, the run returns one option listing both — a run requesting lowest_price and fewest_sellers that converges on one cart now says so explicitly. The same shape applies to the in-progress options returned while a run is solving and to the options summary on the optimizer.run.completed webhook event.
  • Optimizer (experimental)POST /v1/optimizer/runs/{id}/apply takes the option to apply as mode (was kind). Any mode listed on an option selects that option.

0.5.1 — 2026-07-16

Buy and sell through the API: mark sales shipped or cancel them, browse a product's Marketplace listings, and fill a shopping cart — item by item or straight from an optimizer run.

Added

  • Sales — act on the orders you're selling. POST /v1/sales/{orderNumber}/mark-shipped marks an order shipped and registers its tracking number (the carrier is detected automatically); allowed while the order is awaiting dispatch or has an open cancellation request. POST /v1/sales/{orderNumber}/cancel cancels an order awaiting dispatch, with a reason code and a note to the buyer — the buyer is refunded in full and the cards return to your inventory. Both return the updated sale and require the sales:write scope.
  • ProductsGET /v1/products/{productId}/listings returns a product's Marketplace listings: each listing's price, quantity for sale, condition, language, finish, grading, seller note, and the seller behind it. Sorted cheapest first, cursor-paginated, and filterable by condition, language, finish, and region; pass deliveryCountry to keep only sellers who ship to you, with each seller's shipping charge. Any valid API key works — no scope; requests count against a dedicated product-listings rate limit of 120 per hour, raisable per account through support.
  • Cart — a shopping cart, under the new cart:read and cart:write scopes. GET /v1/cart returns it grouped by seller with per-seller shipping. POST /v1/cart/items adds up to 1000 listings by listingId in one call, each item succeeding or failing on its own; the deliveryCountry you send applies on every call. PATCH /v1/cart/items/{listingId} sets an item's quantity (0 removes it), DELETE /v1/cart/items/{listingId} removes an item, and DELETE /v1/cart clears the cart. Every call returns the updated cart.
  • Optimizer (experimental)POST /v1/optimizer/runs/{id}/apply replaces your cart with one of a finished run's options in one step. To add a run's items to your cart instead, pass their listingId and quantity to POST /v1/cart/items.

Fixed

0.5.0 — 2026-07-13

One price shape everywhere: every price is a cardmarket, tcgplayer, or cardnexus block, under a map named pricesByFinish.

Added

Changed

  • Products — a card's pricesByFinish and a sealed product's prices are now keyed by source — cardmarket (EUR), tcgplayer (USD), cardnexus — with the currency as a field inside each block, instead of by currency (eur / usd). Every pricing surface now uses this one block shape.
  • Pricing — the response map of GET /v1/products/{productId}/prices is named pricesByFinish (was finishes). finishes now always means one thing: the list of finishes a product exists in.
  • Prices feed — the same rename on every line: the price map is pricesByFinish (was finishes). See the prices feed guide.
  • Pricing — the blocks on GET /v1/products/{productId}/prices and on product responses no longer carry a date — they reflect the moment of the request. The prices feed keeps date on its cardmarket / tcgplayer blocks (the calendar day of that marketplace snapshot), and price history keeps its per-day date.
  • Catalog feedattributes now carries exactly the game's gameplay fields, the same keys and values the Products endpoints return. The card-database ids that used to sit there moved into externalIds.

0.4.0 — 2026-07-13

Feed records now use the same field names as the API endpoints, and an experimental cart optimizer arrives.

Added

  • Cart optimizer (experimental) — optimize a basket of cards into the cheapest, fewest-seller, or balanced ways to buy it across the marketplace: POST /v1/optimizer/runs starts a run in the background and GET /v1/optimizer/runs/{id} polls it; the optimizer.run.completed and optimizer.run.failed webhook events fire when a run finishes. These endpoints are experimental, even by this preview API's standards — expect their request and response shapes to change more than the rest of the API.
  • Catalog feed — each product line now carries rarity, imageBackUrl, productCategory, and externalIds (the product's Cardmarket and TCGplayer ids, one per finish). See the catalog feed guide.
  • Prices feed — the marketplace price blocks now carry the full daily snapshot: low, mid, and high tiers, currency, and the snapshot date, alongside marketValue and the trend percentages. See the prices feed guide.
  • Catalog changelog — with includeChanges=true, GET /v1/feeds/{gameId}/changelog entries now identify each changed product and expansion by id as well as slug: productId on product entries, expansionId on expansion entries.

Changed

0.3.0 — 2026-07-08

Match cards to every printing by name slug.

Added

0.2.0 — 2026-07-08

Build and manage your lists — decks, want lists, and for-sale lists.

Added

0.1.0 — 2026-07-07

Organise your inventory with tags and locations.

Added

Changed