Sync inventory
A worked example: keeping your CardNexus inventory in sync with an external system — a POS, an e-commerce backend, or your own database.
The pattern: put your own identifier on every CardNexus line as its customId, then sync by sending your full stock as one file with POST /v1/inventory/bulk/import in replace mode. The file becomes your inventory — no per-line diffing, no create/update/delete bookkeeping on your side. Between full syncs, apply point changes with POST /v1/inventory/bulk/update and learn about changes that start on CardNexus (sales) from inventory webhooks.
1. Key every line with customId
customId is your own stable identifier for a line — typically your POS row id or SKU. It must be unique across your live lines. A row in an import file that carries a customId matching one of your lines updates that line absolutely: the row's quantity is the new total, not an increment. A customId that matches nothing creates a new line.
That makes your system the source of truth: you never need to read CardNexus state to compute what to send.
2. Full sync: one replace import
Export your stock to a CSV (or JSON) file. Columns are matched by name, in any order; columns you don't need can be left out:
productId,finish,condition,language,quantity,customId,comment,listingPrice,listingCurrency
50212,Standard,NM,en,4,pos-841,,42.00,EUR
50212,Foil,LP,de,1,pos-842,Small whitening on back corner,79.90,EUR
61873,Standard,MP,en,12,pos-901,,3.50,EUR
quantityis the line's total count.0is valid — it keeps the line known but out of stock.listingPrice/listingCurrencypublish the line to the Marketplace; leave both empty for stock you hold but don't sell.- Graded cards use the
gradedGrade/gradedCertification/gradedGradingServicecolumns instead ofcondition.
Send it as one multipart request:
curl -X POST https://public-api.cardnexus.com/v1/inventory/bulk/import \
-H "Authorization: Bearer cnk_live_4f2d8c91e5b34a7f9e6c2d1a8b5f7e3c" \
-H "Idempotency-Key: 9b4e7c20-5d1a-4f8e-b3c6-a7d9e1f2c4b8" \
-F "file=@inventory.csv;type=text/csv" \
-F "mode=replace"
mode=replace is what makes this a sync rather than a merge: after all rows apply, every live line the file does not mention is set to a quantity of zero. Whatever the file says, CardNexus now says.
The Idempotency-Key makes retries safe — resending the same request returns the same job instead of importing twice. Files up to 256 MB (and up to 5,000,000 rows) are accepted, and may be gzip-compressed.
3. Follow the job
The call returns 202 with a job right away:
{
"job": {
"id": "6848a3f0c8d4e9f7a6b5c4e1",
"kind": "import",
"status": "pending",
"mode": "replace",
"progress": 0
}
}
Poll GET /v1/inventory/bulk/jobs/{jobId} until status is completed (or failed — see errorMessage), or subscribe to the inventory.import.completed webhook instead of polling. The finished job carries row tallies:
{
"job": {
"id": "6848a3f0c8d4e9f7a6b5c4e1",
"kind": "import",
"status": "completed",
"counts": { "total": 1842, "succeeded": 1839, "failed": 3 },
"progress": 100,
"errorReportUrl": "https://cardnexus-files.s3.eu-west-1.amazonaws.com/inventory-bulk-jobs/665f3a2b1c8d4e9f7a6b5c4d/6848a3f0c8d4e9f7a6b5c4e1/error-report.json?X-Amz-Expires=900&X-Amz-Signature=4e9a..."
}
}
A failed row — unknown product, invalid value — doesn't stop the import. Failed rows are listed, with reasons, in the JSON report at errorReportUrl. Treat a non-empty error report as part of your sync loop: fix the rows in your source data and let the next run pick them up.
4. Point changes between full syncs
For events that shouldn't wait for the next full sync — a card sells at the counter, a price check — use POST /v1/inventory/bulk/update: up to 200 targeted changes in one call, each addressed by your customId:
curl -X POST https://public-api.cardnexus.com/v1/inventory/bulk/update \
-H "Authorization: Bearer cnk_live_4f2d8c91e5b34a7f9e6c2d1a8b5f7e3c" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "customId": "pos-841", "quantity": { "adjust": -1 } },
{ "customId": "pos-901", "quantity": { "set": 10 } }
]
}'
adjust is race-free: it adds to whatever the quantity is now, so an in-store sale and a CardNexus sale landing at the same moment both count. set writes an absolute total. The response reports success or an error per item. A change that would leave zero or fewer cards is rejected — remove the line with DELETE /v1/inventory/{inventoryId}, or let the next full sync zero it.
Price changes ride the import file (listingPrice/listingCurrency), or PATCH /v1/inventory/{inventoryId}/listing for a single line.
5. Changes that start on CardNexus
Sales on the CardNexus Marketplace reduce your stock there before your next sync runs. Subscribe to the inventory.quantity.changed webhook — its data.reason field tells you why a quantity moved (order for a sale, order_cancelled for stock coming back), and data.changes carries the before/after quantity per line. Apply those to your source system so the next full sync doesn't resurrect sold stock. See Inventory webhooks.
First run on an existing inventory
If you already have lines on CardNexus without customIds, run the same replace import: your file's rows create their customId-keyed lines, and the old un-keyed lines are zeroed in the same job. After that first run, every live line is one your file controls.
What to schedule
Drive the steady state with webhooks and bulk/update, and run the replace import as your reconciliation pass — it's one request regardless of inventory size, so the rate limit is not a factor in how often you can run it.
Related
- Bulk operations — the full import/export/update reference
- Inventory webhooks
- Authentication
- Error handling