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.23.0 — 2026-10-07

Vacation mode no longer blocks listing writes.

Changed

0.22.0 — 2026-10-05

Set, change, and remove listing prices in bulk.

Changed

Added

  • listing on the items of POST /v1/inventory/bulk/update — { "price": { "amount": 42.5, "currency": "EUR" } } puts a line up for sale, or changes the price of a line that is already for sale; null takes the line off sale. With count, only those cards take the new listing. See Updating lines in bulk.
  • CURRENCY_MISMATCH and SELLER_ACCOUNT_NOT_ACTIVE per-item error codes on the same endpoint — returned when a listing price is not in your seller currency (with the provided and expected currencies in data), and when an item puts a line up for sale while your seller account cannot list cards for sale.

0.21.0 — 2026-10-03

Remove and reorder a line's photos without uploading them again.

Breaking changes

PATCH /v1/inventory/{inventoryId} rejects fields it does not list. A request carrying a field other than the documented ones returns 400 BAD_REQUEST. Until now such fields were ignored.

Added

0.20.0 — 2026-10-01

Mark untracked sales shipped without a tracking number, and read which shipping the buyer paid for on each order.

Breaking changes

shipping.trackingNumber and shipping.type can be null. An untracked sale shipped without a tracking number has both set to null on GET /v1/sales/{orderNumber}, GET /v1/purchases/{orderNumber}, and every sales write that returns the sale. Sales and purchases with a tracking number are unchanged.

Added

  • shippingService on your sales and purchases — tracked or untracked, the shipping the buyer paid for. An untracked order ships as a letter, which may have no tracking number. On GET /v1/sales, GET /v1/sales/{orderNumber}, every sales write that returns the sale, GET /v1/purchases, and GET /v1/purchases/{orderNumber}.
  • untrackedCloseAt on the same responses — when a shipped untracked order closes on its own: it completes and the seller is paid, unless a dispute is opened before then. null on tracked orders and before an untracked order ships.
  • data.shippingService and data.untrackedCloseAt on the order.status.changed webhook, with the same values as on the order. See Webhooks.
  • 409 TRACKING_NUMBER_REQUIRED on POST /v1/sales/{orderNumber}/mark-shipped — returned when you leave trackingNumber out on a tracked sale.

Changed

  • POST /v1/sales/{orderNumber}/mark-shipped — trackingNumber is optional on an untracked sale. Leave it out to ship the sale as a letter without a tracking number; untrackedCloseAt is set on the sale. See Shipping untracked sales.
  • Cart — seller.shipping on GET /v1/cart and every endpoint that returns your cart is the untracked letter rate when that seller's items ship as an untracked letter, the same amount checkout charges. The response shape is unchanged.

0.19.0 — 2026-09-30

See whether CardNexus manages shipping on each order and on your new sales, and get notified when that setting changes.

Added

  • shippingManagedByCardNexus on your sales and purchases — true when CardNexus manages shipping on the order (the buyer paid CardNexus for the shipping, and the seller ships with a label generated on CardNexus), false when the seller ships it themselves. It is set when the order is placed and never changes afterwards. On GET /v1/sales, GET /v1/sales/{orderNumber}, every sales write that returns the sale, GET /v1/purchases, and GET /v1/purchases/{orderNumber}.
  • data.shippingManagedByCardNexus on the order.created and order.status.changed webhooks, with the same value as on the order. See Webhooks.
  • seller.shippingManagedByCardNexus on GET /v1/account/me — whether CardNexus manages shipping on your new sales. A change only applies to orders placed afterwards.
  • account.updated webhook — fires when a setting on your account changes. data.changedFields lists what changed (seller.shippingManagedByCardNexus) and data.seller carries the new value. Integrations require account:read. See Webhooks.

0.18.0 — 2026-09-29

Ship sales as USPS Intelligent Mail barcode letters, and send the mail and carrier scans of the labels your application prints.

Added

  • POST /v1/tracking/events — for applications with the tracking privilege: send the scans of up to 100 shipments per request, matched by tracking number to the sales of sellers who connected your application with sales:write. Scans are recorded once per eventId, and a delivered scan marks the sale delivered; the response reports matchedSales, accepted and duplicates per shipment. Other applications get 403 FORBIDDEN. See Sending tracking scans.
  • trackingUrl on POST /v1/sales/{orderNumber}/mark-shipped — an https link to your own tracking page, shown to the buyer.
  • shipping.type on your sales and purchases — parcel or letter — on GET /v1/sales/{orderNumber}, GET /v1/purchases/{orderNumber}, and every sales write that returns the sale.
  • source on each shipping.history scan — cardnexus when CardNexus received it from the carrier, application when an application connected to the seller sent it.

Changed

  • POST /v1/sales/{orderNumber}/mark-shipped — a USPS Intelligent Mail barcode (IMb) number now ships the sale as a letter: shipping.type is letter, the carrier is usps, and shipping.trackingUrl is null unless you pass trackingUrl.

0.17.0 — 2026-09-25

You can have up to 200 lists.

Changed

  • POST /v1/lists — you can have up to 200 lists. Once you have 200, creating another returns 409 LIST_LIMIT_REACHED with the limit in max. Delete a list to create a new one.

0.16.0 — 2026-09-24

Refund part of a sale immediately, and read the total refunded on each of your sales.

Added

  • POST /v1/sales/{orderNumber}/refunds — refund part of one of your sales to the buyer. The refund is immediate and cannot be undone. It is taken from the order payment CardNexus holds and lowers your payout for the sale; nothing is debited from your account. The sale must be pending_shipment, cancellation_requested, shipped, or delivered. If the buyer used a coupon, the refund is split in the same proportion as their payment between their payment method and their coupon. You can refund a sale more than once, but all refunds together must stay below the sale's total (items plus the shipping you charged): a larger amount returns 409 AMOUNT_TOO_HIGH with the most you can still refund in maxAllowed. To refund in full, cancel the sale. Requires sales:write.
  • refunded on your sales — the total you refunded so far, in your selling currency — on GET /v1/sales, GET /v1/sales/{orderNumber}, and every sales write that returns the sale.

0.15.0 — 2026-09-11

Send and manage offers directly from listings, with proposal history and events for both participants. Managed accounts now go live from a hosted flow the seller completes in their own browser: applications create them with the seller's full details, push listings into them right away, and read on GET /v1/account/me whether the listings are shown.

Breaking changes

sellerType is required on POST /v1/accounts. Pass "pro" for a registered company or "individual" for a private person. Requests without it are rejected with 400.

username on POST /v1/accounts is now 2 to 32 lowercase letters, digits or underscores. Uppercase letters are lowercased; any other character is rejected with 400. Reserved handles fail with 400 USERNAME_RESERVED.

Added

Offers

  • List your offers and read an offer, including chronological proposals and currently available actions. Item productId is null when its catalogue reference is unavailable; offer history remains readable.
  • Create an offer from listing IDs and quantities with one total amount or percentage discount, without changing your cart. Requires offers:write; creation is limited to five requests per hour per account across API credentials.
  • Counter, accept, and decline a proposal using proposalId. Either participant can cancel an open offer.
  • Restore an accepted offer to your cart with offers:write and cart:write. Acceptance reserves stock for 48 hours and leaves your cart unchanged.
  • offer.created and offer.updated webhooks for both participants. Connected applications require offers:read. See the Offers guide.

Managed accounts

  • POST /v1/accounts — optional firstName, lastName, language (en or fr), phone, and for pro sellers a business object (companyName, registrationNumber, vatNumber, taxId, notVatRegistered, address). Whatever you leave out, the seller fills in during onboarding. The endpoint accepts an Idempotency-Key header like the other writes.
  • seller.status on GET /v1/account/me — staged while the account's listings are hidden from the marketplace, live once they are shown. Poll it to know when a managed account has completed onboarding.

Changed

  • POST /v1/accounts — a new account has its seller profile from creation, so POST /v1/listings and the other listing writes work immediately instead of returning SELLER_ACCOUNT_NOT_ACTIVE. The listings stay hidden until the seller completes the hosted onboarding flow from POST /v1/account/onboarding-link: they accept the CardNexus terms and Stripe's terms and, as an individual, give Stripe their name and date of birth. A pro seller goes live on acceptance; business verification is collected afterwards and payouts are held until it passes. See Building an integration.
  • POST /v1/accounts — the address now receives an email naming the application that created the account, listing what it can do, and linking to the hosted flow to claim it. A recipient who did not ask for the account replies to that email to have it deleted.
  • seller on GET /v1/account/me is present as soon as the account has a seller profile, so a managed account reports its declared type, country and currency before the seller has completed onboarding.

0.14.0 — 2026-08-27

Photos on Marketplace listings and on your own inventory lines, and repeatable condition, language, and finish filters when reading a product's listings.

Added

Changed

  • GET /v1/products/{productId}/listings — condition, language, and finish accept several values: repeat the parameter, e.g. ?condition=NM&condition=LP, to match any of them. A single value works as before.
  • GET /v1/products/{productId}/listings — seller.shipping is now documented as null whenever the request has no deliveryCountry. Pass deliveryCountry to get each seller's shipping charge; listings from sellers who don't ship there are then left out.

0.13.0 — 2026-08-13

Search a card across several expansions at once. Carry your own references and fulfillment states on your sales, and filter your sales by them. Read your account id from your own account, and re-approving an application now changes what it can do.

Breaking changes

expansionId takes a list. On POST /v1/products/search and POST /v1/inventory/search, expansionId now takes an array of up to 200 expansion ids instead of a single id, and matches any of them. Wrap the id you send today in an array: "expansionId": 42 becomes "expansionId": [42].

The expansion game filter is gone. gameFilters.filters.expansion no longer exists on POST /v1/products/search, POST /v1/inventory/search and POST /v1/inventory/bulk/export. It matched on ids that are not the expansion ids these endpoints return, so no value you could read from the API ever matched. Send expansionId instead. A request that still carries the filter is accepted and the filter is ignored, so results widen to every expansion.

Added

Sales metadata — every sale has a metadata object of your own key/value pairs. CardNexus never reads or changes them, and the buyer never sees them.

  • PATCH /v1/sales/{orderNumber}/metadata writes them. The write merges: keys you send are set, keys you send as null are removed, keys you leave out keep their value — so two of your systems can each own their keys on the same sale. Works in any status, including on completed and cancelled sales.
  • metadata on GET /v1/sales filters your sales on exact key/value pairs — ?metadata[fulfillment_stage]=packed, repeatable for up to 10 keys, AND-combined.
  • POST /v1/sales/{orderNumber}/mark-shipped takes the same metadata object, so a sale can be shipped and stamped in one call. The two either both apply or neither does.
  • metadata is returned on every sales response, and on the seller's copy of the order.status.changed webhook under data.metadata — null on the buyer's copy.

Keys start with a letter and may contain letters, digits, _, . and -, up to 64 characters. Values are strings of up to 500 characters. A sale holds at most 50 keys; a write that would go past that stores nothing and returns 422 METADATA_LIMIT_EXCEEDED. See Track sales in your own system.

Fixed

  • Sending gameFilters with any filter in it returned 500 on POST /v1/products/search. Filtering on a game's own attributes — rarity, colour, finish — now works.
  • When an account goes through the connect flow again, the permissions it approves now replace the ones it granted before. Previously the second approval was ignored and the account kept its original permissions, so an application whose permissions had been widened never received the new ones even after the account re-approved. POST /v1/connect/exchange returns the permissions currently in force. See Building an integration.

0.10.0 — 2026-08-07

The fixed 10-unit-per-listing cart cap is gone — each seller now sets their own per-listing limit, and most set none.

Changed

  • Cart — quantity on POST /v1/cart/items and PATCH /v1/cart/items/{listingId} no longer has a fixed maximum of 10. A cart line can hold any quantity the listing has for sale, up to any per-listing limit the seller sets.
  • Cart — the QUANTITY_LIMIT_EXCEEDED rejection code on POST /v1/cart/items now means the seller's per-listing limit was exceeded, and data.max carries that seller's limit instead of the former fixed 10. Sellers without a limit never return it.

0.9.0 — 2026-08-07

Third-party applications can act on accounts on their owners' behalf. Search by name and print number together, in another language, and find the cheapest copy that can actually reach you. Trace an exported row back to the line it came from. See exactly which fields a rejected request failed on.

Breaking changes

Some list parameters now have a maximum length. Sending more entries than the maximum returns 400. Requests within the limits are unaffected.

Endpoint Parameter Maximum
POST /v1/products/search productIds 200
GET /v1/inventory, GET /v1/listings productId 200
GET /v1/inventory, GET /v1/listings tags 50
POST /v1/inventory/search language.values 50
POST /v1/inventory/bulk/export filters.language.values 50
POST /v1/optimizer/runs sellers.include, sellers.exclude 200
POST /v1/optimizer/runs sellers.countries 250

To look up more products than one call takes, split the ids across several calls. The maximum for each parameter is listed on its field in the reference.

Added

Integrations — an approved application can act on an account on its owner's behalf: it authenticates with its own credential and names the account in the CardNexus-Account header. Every endpoint works unchanged, bounded by the permissions that account granted. See Building an integration.

  • POST /v1/connect/exchange — exchange the one-time code from the connect redirect for the account id and its granted permissions.
  • POST /v1/accounts — create an account for a seller who isn't on CardNexus yet, staged until they finish onboarding. Requires the managed-account privilege. Capped per day; past the cap you get QUOTA_EXCEEDED with limit and resetAt.
  • POST /v1/account/onboarding-link — issue a hosted-onboarding link so a managed account's seller can accept the terms, set up payouts, and go live.
  • Webhooks carry an account field, so one endpoint can serve every account you manage.

Products — POST /v1/products/search takes a listings object and answers with what you can actually buy. Every result gains an availability block: listingCount, plus cheapest with its price, quantity, condition, language, seller, and a listingId for POST /v1/cart/items. Filter with deliveryCountry, condition, language, finish, and inStock. deliveryCountry applies the checkout rule, so anything returned can be added to a cart. See Listing-aware search.

Search filters — printNumber and nameSlug on both POST /v1/products/search and POST /v1/inventory/search, and in the filters of POST /v1/inventory/bulk/export. nameSlug returns one card across every printing and expansion.

Accept-Language (en, fr, it, es, de) on product search, inventory search, and bulk export — Blizzaroi with Accept-Language: fr finds Abomasnow. Product and expansion names come back translated where one exists.

Bulk operations — exported rows carry inventoryId and updatedAt. inventoryId is the line's own id, the same one GET /v1/inventory and GET /v1/listings return, so a row traces back to its line and can be passed to PATCH /v1/inventory/{inventoryId}. productId names the card, but several of your lines can share one. Both columns are ignored on import.

Sales — every item of GET /v1/sales carries shippingAddress, so a batch of parcels can be prepared from one call.

Lists — bannerUrl on the list endpoints, or null when a list has no banner.

Validation errors — a 400 names each rejected field under data.issues: field is a dotted path into your body (lines.3.quantity), message says what's wrong. Up to 100 per response. See Error handling.

Changed

  • Products / Lines — name free-text understands a print number before or after the name (sephiroth 44), with or without padding and prefixes (38 finds 038, 44 finds ST02-44), and an expansion code plus print number (msh-54, msh 54, msh54). On product search an expansion code alone (msh) surfaces that expansion after any name matches.
  • Products — nameSlug is matched ignoring case. A slug differing only in case used to return nothing.
  • Bulk operations — Bulk import & export now states that a json export leaves out any field a line doesn't set, so a line with no tags has no tags key. A csv export always writes every column.

Removed

  • The products:read, pricing:read, and marketplace:read scopes. Catalogue, pricing, and feed endpoints need no scope — any valid key reads them — so these granted nothing. They no longer appear when creating a key; existing keys carrying them work exactly as before.

Fixed

  • All endpoints — every error response carries data, as documented. Some errors previously left it out.

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

  • Lines — POST /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.
  • Lines — GET /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 / Locations — POST /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 / Listings — GET /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 items — POST /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.
  • Products — GET /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 feed — attributes 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