Verkäufer-API
Angebote und Bestand synchronisieren, Bestellungen abrufen, als verschickt markieren und Webhooks empfangen: eine REST-API für alle Spiele auf Binder.
Die technische Doku ist auf Englisch, wie bei den meisten APIs.
Erste Schritte
The Seller API is a JSON REST API at https://www.binder.market/api/v1. It works the same for every game on Binder (Pokémon, One Piece and the ones that follow): products are identified by their catalogue productId, and you can keep your own sku on every listing. Money is always an integer in euro cents (1999 = €19.99), timestamps are ISO 8601 in UTC, and ids are strings.
Create a key under Account → API, then try it out. The snippets below expect it in $KH_KEY.
export KH_KEY="kh_live_ab12cd34_…"
curl https://www.binder.market/api/v1/listings?limit=1 -H "Authorization: Bearer $KH_KEY"Authentifizierung
Every request carries the key as a Bearer token: Authorization: Bearer kh_live_…. A key belongs to one store and can only see and change that store’s data. Keys look like kh_live_<8 characters>_<secret>; the first part is shown in your account so you can tell keys apart. We store only a SHA-256 fingerprint, so a lost key can’t be shown again: revoke it and create a new one. Never put a key in a browser or app that customers can download.
Rechte
Each key has a list of scopes. A request without the scope it needs fails with 403.
listings:read | GET /listings, GET /listings/{id} |
listings:write | POST, PATCH, DELETE /listings, PATCH, DELETE /listings/{id}, PUT /stock |
orders:read | GET /orders, GET /orders/{id} |
orders:write | POST /orders/{id}/ship |
webhooks:write | GET, POST /webhooks, DELETE /webhooks/{id}, POST /webhooks/{id}/test |
| any key | GET /products |
Fehler
Errors always have the same shape. code is stable and meant for your program; message is for people; field is set when one input is at fault. Bulk calls are all-or-nothing: if one item is invalid nothing is written, and the message starts with its index, e.g. items[3]: ….
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"code": "validation_failed",
"message": "items[3]: priceCents must be an integer between 1 and 100000000",
"field": "priceCents"
}
}| 400 | invalid_request | Malformed JSON, a bad query parameter or cursor, too many items. |
| 401 | unauthorized | Missing, malformed or revoked API key. |
| 403 | insufficient_scope | The key lacks the scope this endpoint needs. |
| 404 | not_found | No such listing, order or endpoint in your store (bulk calls: one unknown id or sku). |
| 409 | idempotency_conflict | Idempotency-Key reused with a different request, or the first one is still running. |
| 422 | validation_failed | A value is invalid; `field` names it and the message names the item index. |
| 422 | invalid_state | The order is not in a state that allows this (e.g. shipping an unpaid order). |
| 429 | rate_limited | Too many requests; wait for Retry-After seconds. |
| 503 | database_not_configured | The marketplace backend is not available yet. |
Idempotenz
Send an Idempotency-Key header (any unique string up to 255 characters, e.g. a UUID) with POST, PUT, PATCH and DELETE requests. If the connection drops, retry with the same key and the same body: you get the original response again, with the header Idempotent-Replayed: true, and nothing is created twice. The same key with a different body returns 409 idempotency_conflict. Keys are remembered for 24 hours. Server errors (5xx) are not stored, so a retry runs the request again.
curl -X POST https://www.binder.market/api/v1/listings -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c1d3e-6a54-4d7e-9a43-1f0f6c3c2b10" \
-d '{"items":[{"productId":"sv151-199","condition":"NM","language":"EN","priceCents":11900,"quantity":2}]}'Seitenweise abrufen
Lists return { "data": [...], "nextCursor": "…" }. Pass limit (default 100, max 500) and, for the next page, cursor=<nextCursor> unchanged. When nextCursor is null you have everything. Cursors are keyset-based, so pages stay stable while rows are added: nothing is skipped or repeated. Listings come oldest first, orders newest first.
curl "https://www.binder.market/api/v1/listings?limit=500&cursor=WyIyMDI2LTEwLTE5IDA4OjEyOjAzLjUxMjM0KzAwIiwiTF9YYjJmIl0" -H "Authorization: Bearer $KH_KEY"Rate-Limits
Each key may make 120 requests per minute (short bursts are fine). Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; over the limit you get 429 with Retry-After. Use the bulk endpoints: one call can create, change or restock up to 500 listings.
Produkte
GET/productsany key
Look up catalogue products to map your own articles to a productId. q matches name (any language we know), set and number; game (pokemon, onepiece, …) and set (e.g. OP01) narrow it down. Give q or set. limit up to 100 (default 20).
curl "https://www.binder.market/api/v1/products?q=charizard%20199&game=pokemon" -H "Authorization: Bearer $KH_KEY"{
"data": [
{
"id": "sv151-199",
"game": "pokemon",
"set": "SV151",
"kind": "single",
"sealedKind": null,
"name": "Charizard ex",
"names": { "de": "Glurak-ex" },
"number": "199/165",
"rarity": "Special Illustration Rare",
"imageUrl": "/cards/sv151-199.webp",
"referencePriceCents": 11900
}
]
}Angebote
A listing is one offer: a product in one condition and language at one price, with a quantity. condition is one of MT NM EX GD LP PL PO (leave it out for sealed product and graded cards); language one of EN DE JP FR IT ES PT KO ZH; graded is { "company": "PSA", "grade": 10 } (PSA, CGC, BGS, SGC, TAG, ACE) and then needs the slab's certNumber. A sku must be unique among your active listings. A listing with quantity 0 stays active and comes back when you restock.
GET/listingslistings:read
Filters: productId, sku, status (active, paused, deleted; deleted ones are left out unless asked for), updatedSince (ISO time; includes deletions, for incremental sync).
curl "https://www.binder.market/api/v1/listings?status=active&updatedSince=2026-10-19T00:00:00Z" -H "Authorization: Bearer $KH_KEY"{ "data": [{
"id": "L_Xb2fQ9mR1kTz0aVc",
"productId": "sv151-199",
"condition": "NM",
"language": "EN",
"priceCents": 11900,
"quantity": 2,
"reverseHolo": false,
"firstEdition": false,
"signed": false,
"altered": false,
"graded": null,
"sku": "CHAR-151-NM-EN",
"note": null,
"status": "active",
"source": "api",
"version": 1,
"createdAt": "2026-10-19T08:12:03.512Z",
"updatedAt": "2026-10-19T08:12:03.512Z"
}], "nextCursor": null }POST/listingslistings:write
Creates up to 500 listings in one call, all or nothing.
curl -X POST https://www.binder.market/api/v1/listings -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"items": [
{ "productId": "sv151-199", "condition": "NM", "language": "EN",
"priceCents": 11900, "quantity": 2, "sku": "CHAR-151-NM-EN" },
{ "productId": "op01-120", "condition": "NM", "language": "JP",
"priceCents": 4500, "quantity": 1, "sku": "OP01-120-JP" }
]
}'HTTP/1.1 201 Created
{ "data": [{
"id": "L_Xb2fQ9mR1kTz0aVc",
"productId": "sv151-199",
"condition": "NM",
"language": "EN",
"priceCents": 11900,
"quantity": 2,
"reverseHolo": false,
"firstEdition": false,
"signed": false,
"altered": false,
"graded": null,
"sku": "CHAR-151-NM-EN",
"note": null,
"status": "active",
"source": "api",
"version": 1,
"createdAt": "2026-10-19T08:12:03.512Z",
"updatedAt": "2026-10-19T08:12:03.512Z"
}, …] }PATCH/listingslistings:write
Changes up to 500 listings, each found by id or else by sku. Fields: priceCents, quantity, status (active or paused), note, and sku (only when the item is identified by id). Every change bumps version.
curl -X PATCH https://www.binder.market/api/v1/listings -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-d '{ "items": [
{ "sku": "CHAR-151-NM-EN", "priceCents": 11500 },
{ "id": "L_Xb2fQ9mR1kTz0aVc", "status": "paused" }
] }'{ "data": [ { "id": "L_…", "sku": "CHAR-151-NM-EN", "priceCents": 11500, "version": 2, … }, … ] }DELETE/listingslistings:write
Removes listings by ids or skus (up to 500). Past orders keep their copy of the listing.
curl -X DELETE https://www.binder.market/api/v1/listings -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-d '{ "skus": ["CHAR-151-NM-EN", "OP01-120-JP"] }'{ "deleted": 2, "ids": ["L_Xb2fQ9mR1kTz0aVc", "L_8fK2…"] }GET · PATCH · DELETE/listings/{id}listings:read / listings:write
Read, change or remove one listing. PATCH takes the same fields as the bulk call.
curl -X PATCH https://www.binder.market/api/v1/listings/L_Xb2fQ9mR1kTz0aVc -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-d '{ "priceCents": 10900, "quantity": 3 }'{
"id": "L_Xb2fQ9mR1kTz0aVc",
"productId": "sv151-199",
"condition": "NM",
"language": "EN",
"priceCents": 10900,
"quantity": 3,
"reverseHolo": false,
"firstEdition": false,
"signed": false,
"altered": false,
"graded": null,
"sku": "CHAR-151-NM-EN",
"note": null,
"status": "active",
"source": "api",
"version": 2,
"createdAt": "2026-10-19T08:12:03.512Z",
"updatedAt": "2026-10-19T08:12:03.512Z"
}Bestand
PUT/stocklistings:write
Sets absolute quantities for up to 500 listings (by id or sku), all or nothing. This is the call for syncing stock from your own system, e.g. after a sale in your shop. Items sitting in a buyer’s cart are reserved separately and never oversold.
curl -X PUT https://www.binder.market/api/v1/stock -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: stock-sync-2026-10-19T10:00" \
-d '{ "items": [
{ "sku": "CHAR-151-NM-EN", "quantity": 0 },
{ "sku": "OP01-120-JP", "quantity": 4 }
] }'{
"data": [
{ "id": "L_Xb2fQ9mR1kTz0aVc", "sku": "CHAR-151-NM-EN", "quantity": 0, "version": 3, "updatedAt": "…" },
{ "id": "L_8fK2…", "sku": "OP01-120-JP", "quantity": 4, "version": 2, "updatedAt": "…" }
]
}Bestellungen
One order is one parcel from your store. Buyers pay Binder; we hold the money until the buyer confirms receipt (or automatically after the confirmation period), then pay you out. payoutStatus is none, held, on_hold (a problem was reported), scheduled or paid_out. Status flow: pending_payment → paid → shipped → delivered → confirmed → paid_out (or cancelled / disputed).
GET/ordersorders:read
Newest first. Filters: status (comma-separated, e.g. paid,shipped), updatedSince. Items carry your sku and a snapshot of the listing at the time of sale.
curl "https://www.binder.market/api/v1/orders?status=paid" -H "Authorization: Bearer $KH_KEY"{ "data": [{
"id": "O_q8Zr2LkM0pXs",
"number": "K-100123",
"checkoutId": "CO_7hT1xQ",
"storeId": "S_4kd9Lm",
"status": "paid",
"currency": "EUR",
"subtotalCents": 11900,
"shippingCents": 495,
"totalCents": 12395,
"shippingMethod": "tracked",
"shipTo": { "name": "Mia Weber", "street": "Hauptstr. 5", "postcode": "10115", "city": "Berlin", "country": "DE" },
"carrier": null,
"trackingNumber": null,
"items": [
{ "listingId": "L_Xb2fQ9mR1kTz0aVc", "productId": "sv151-199", "sku": "CHAR-151-NM-EN",
"qty": 1, "unitPriceCents": 11900, "condition": "NM", "language": "EN", "reverseHolo": false,
"graded": null, "cert": null }
],
"payoutStatus": "held",
"paidAt": "2026-10-19T09:01:44.000Z",
"shippedAt": null,
"deliveredAt": null,
"confirmedAt": null,
"paidOutAt": null,
"cancelledAt": null,
"createdAt": "2026-10-19T09:00:12.000Z",
"updatedAt": "2026-10-19T09:01:44.000Z"
}], "nextCursor": null }GET/orders/{id}orders:read
One order with its items.
curl https://www.binder.market/api/v1/orders/O_q8Zr2LkM0pXs -H "Authorization: Bearer $KH_KEY"{
"id": "O_q8Zr2LkM0pXs",
"number": "K-100123",
"checkoutId": "CO_7hT1xQ",
"storeId": "S_4kd9Lm",
"status": "paid",
"currency": "EUR",
"subtotalCents": 11900,
"shippingCents": 495,
"totalCents": 12395,
"shippingMethod": "tracked",
"shipTo": { "name": "Mia Weber", "street": "Hauptstr. 5", "postcode": "10115", "city": "Berlin", "country": "DE" },
"carrier": null,
"trackingNumber": null,
"items": [
{ "listingId": "L_Xb2fQ9mR1kTz0aVc", "productId": "sv151-199", "sku": "CHAR-151-NM-EN",
"qty": 1, "unitPriceCents": 11900, "condition": "NM", "language": "EN", "reverseHolo": false,
"graded": null, "cert": null }
],
"payoutStatus": "held",
"paidAt": "2026-10-19T09:01:44.000Z",
"shippedAt": null,
"deliveredAt": null,
"confirmedAt": null,
"paidOutAt": null,
"cancelledAt": null,
"createdAt": "2026-10-19T09:00:12.000Z",
"updatedAt": "2026-10-19T09:01:44.000Z"
}POST/orders/{id}/shiporders:write
Marks a paid order as shipped and sends the tracking number to the buyer. Any other status returns 422 invalid_state. Emits order.shipped.
curl -X POST https://www.binder.market/api/v1/orders/O_q8Zr2LkM0pXs/ship -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-d '{ "carrier": "DHL", "trackingNumber": "00340434161094042557" }'{ "id": "O_q8Zr2LkM0pXs", "status": "shipped", "carrier": "DHL",
"trackingNumber": "00340434161094042557", "shippedAt": "2026-10-19T14:20:00.000Z", … }Webhooks
Add endpoints under Account → API or with the API below. We send each event as a POST with a JSON body { "id", "type", "createdAt", "data" } and these headers: Binder-Event-Id, Binder-Event-Type and Binder-Signature: t=<unix time>,v1=<hex>. Answer with any 2xx within 10 seconds. Otherwise we retry after 1 min, 5 min, 30 min, 2 h and 12 h, then give up. Deliveries can arrive twice or out of order: use the event id to skip duplicates. URLs must use https.
order.created | A buyer checked out an order with your items (payment pending). |
order.paid | Payment succeeded. Ship now; the money is held until the buyer confirms. |
order.shipped | The order was marked shipped (by you, in the app or via the API). |
order.delivered | The carrier reported delivery. |
order.confirmed | The buyer confirmed receipt (or it was confirmed automatically). Payout is scheduled. |
order.cancelled | The order was cancelled; reserved stock went back to the listings. |
listing.sold_out | A sale brought a listing's quantity to 0. |
payout.sent | We sent a payout to your bank account. |
Verify every delivery: compute HMAC-SHA256 with your endpoint’s whsec_… secret over <t>.<raw body> and compare it to v1; reject timestamps older than five minutes. Use the raw body exactly as received, before any JSON parsing.
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.BINDER_WEBHOOK_SECRET; // whsec_…
function verify(header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1 ?? "", "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/binder/webhook", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verify(req.get("Binder-Signature") ?? "", raw)) return res.sendStatus(400);
const event = JSON.parse(raw);
if (event.type === "order.paid") {
// pick and pack event.data.order …
}
res.sendStatus(200);
});GET · POST/webhookswebhooks:write
List your endpoints or add one (up to 10). The signing secret is returned once, on creation.
curl -X POST https://www.binder.market/api/v1/webhooks -H "Authorization: Bearer $KH_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://shop.example.com/binder/webhook",
"events": ["order.paid", "order.cancelled"] }'HTTP/1.1 201 Created
{
"id": "we_3nV0aK1qPz7m",
"url": "https://shop.example.com/binder/webhook",
"events": ["order.paid", "order.cancelled"],
"active": true,
"createdAt": "2026-10-19T08:00:00.000Z",
"secret": "whsec_0Jk2…"
}DELETE/webhooks/{id}webhooks:write
Removes an endpoint; pending deliveries to it are dropped.
curl -X DELETE https://www.binder.market/api/v1/webhooks/we_3nV0aK1qPz7m -H "Authorization: Bearer $KH_KEY"{ "deleted": true, "id": "we_3nV0aK1qPz7m" }POST/webhooks/{id}/testwebhooks:write
Sends a signed webhook.test event right away and reports what your server answered. Nothing is retried.
curl -X POST https://www.binder.market/api/v1/webhooks/we_3nV0aK1qPz7m/test -H "Authorization: Bearer $KH_KEY"{ "delivered": true, "statusCode": 200, "error": null }