API voor verkopers

Verkoop je LEGO via meerdere kanalen? Met de BrickVillage-API synchroniseer je advertenties, voorraad en bestellingen automatisch met je eigen webshop, zodat je nooit iets dubbel verkoopt.

  • • API-toegang is op aanvraag voor zakelijke verkopers. Mail naar help@brickvillage.eu.
  • • Kopers betalen bovenop je prijs kopersbescherming; jij ontvangt de volledige prijs.
  • • De technische referentie hieronder is in het Engels.

Authentication

API access is granted on request (help@brickvillage.eu). Once enabled, create keys under My account → API. A key looks like bv_live_… and is shown only once. Send it as a bearer token over HTTPS:

curl https://brickvillage.eu/api/v1/listings \
  -H "Authorization: Bearer bv_live_xxxxxxxx"

All requests and responses are JSON (UTF-8). Amounts are integers in euro cents.

Rate limits & errors

120 requests per minute per key. Exceeding it returns 429 with a Retry-After header (seconds).

Errors always use this shape:

{ "error": { "code": "validation_failed", "message": "One or more fields are invalid",
             "fields": { "price_cents": "Number must be greater than or equal to 50" } } }
400bad_requestMalformed JSON or query parameter
401unauthorizedMissing, invalid or revoked key
403forbiddenAPI access disabled or account closed
404not_foundResource doesn't exist or isn't yours
409conflictDuplicate external_ref, image limit reached, sold item
422validation_failedField validation failed (see fields)
429rate_limitedToo many requests

Listings

Use your own SKU as external_ref and upsert by reference — the recommended path for sync systems.

PUT/listings/by-ref/{external_ref}

Create or update. Returns 201 when created, 200 when updated.
curl -X PUT https://brickvillage.eu/api/v1/listings/by-ref/SKU-10297 \
  -H "Authorization: Bearer $BV_KEY" -H "Content-Type: application/json" \
  -d '{ "title": "LEGO Icons 10297 Boutique Hotel", "price_cents": 18999,
        "quantity": 2, "condition": "new", "category": "setjes",
        "set_number": "10297", "images": ["https://cdn.example.com/10297.jpg"] }'
{ "id": 1234, "slug": "lego-icons-10297-boutique-hotel",
  "url": "https://brickvillage.eu/product/1234/lego-icons-10297-boutique-hotel",
  "status": "active", "quantity": 2, "price_cents": 18999,
  "images": ["https://…/product-images/…jpg"], "external_ref": "SKU-10297",
  "source": "api", "created_at": "…", "updated_at": "…" }

Fields: title (3–120), description (≤4000), price_cents (50–10,000,000), quantity (0–999), condition (new, good, used_visible, incomplete, parts), category (populair, onderdelen, setjes, minifigures, fantasie, films), set_number, piece_count, year, theme, age_recommendation, lego_category, weight_grams, color, external_ref, images (≤12 https URLs). title, price_cents and category are required on create.

GET/listings?status=active|sold|offline&external_ref=&limit=50&cursor=

Cursor pagination: pass next_cursor back as cursor.

POST/listings

Create without a reference (or with one; duplicates → 409).

GET · PATCH · DELETE/listings/{id}

PATCH is partial. DELETE takes the listing offline; it is never hard-deleted.

GET · PATCH · DELETE/listings/by-ref/{external_ref}

curl -X PATCH https://brickvillage.eu/api/v1/listings/by-ref/SKU-10297 \
  -H "Authorization: Bearer $BV_KEY" -H "Content-Type: application/json" \
  -d '{ "price_cents": 17499, "quantity": 1 }'

API listings don't expire and don't trigger listing e-mails.

Stock logic

  • quantity: 0 takes the listing offline (status offline, not sold).
  • quantity > 0 on an offline API listing brings it back online.
  • When a buyer pays, stock is deducted atomically. If your stock update lowered the quantity during checkout, the payment is refunded automatically — nothing is ever sold twice.
  • When the last item sells the listing becomes sold and you receive listing.sold_out.

Images

JPEG, PNG or WebP, max 10 MB, max 12 per listing. URLs must be public https://; internal/private addresses are rejected.

POST/listings/{id}/images

Multipart field file, or JSON { "url": "https://…" }.
curl -X POST https://brickvillage.eu/api/v1/listings/1234/images -H "Authorization: Bearer $BV_KEY" -F "file=@photo.jpg"

DELETE/listings/{id}/images/{index}

Zero-based index.

PUT/listings/{id}/images/order

Body { "order": [2, 0, 1] } — a permutation of current indexes.

Orders

GET/orders?since=2026-09-01T00:00:00Z&status=paid|shipped|delivered|refunded&limit=&cursor=

Only your own sales, filtered by last update.

GET/orders/{id}

{ "id": "0b6c…", "status": "paid", "currency": "EUR",
  "amounts": { "subtotal_cents": 18999, "shipping_cents": 695,
               "buyer_protection_cents": 1075, "total_cents": 20769 },
  "items": [ { "listing_id": 1234, "external_ref": "SKU-10297",
               "title": "LEGO Icons 10297", "qty": 1, "price_cents": 18999 } ],
  "shipping": { "method": "postnl:standard", "carrier": "postnl", "kind": "home",
                "address": { … }, "service_point": null,
                "tracking_number": null, "tracking_url": null },
  "paid_at": "2026-09-28T10:00:00Z" }

Buyer protection is paid by the buyer on top of your price.

Webhooks

Set a webhook URL and secret under My account → API. Events: order.paid, order.refunded, listing.stock_changed, listing.sold_out (and test).

POST https://your-shop.example/webhooks/brickvillage
X-BrickVillage-Signature: t=1790000000,v1=5f2c…

{ "id": "evt uuid", "type": "listing.stock_changed", "created_at": "…",
  "data": { "id": 1234, "external_ref": "SKU-10297", "quantity": 0 } }

Respond with any 2xx within 10 seconds. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h. Use the event id to de-duplicate.

Verify the signature: HMAC-SHA256 of t + "." + rawBody with your secret.

import crypto from "node:crypto";

export function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const sig = parts.v1 ?? "";
  return fresh && sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

OpenAPI

Machine-readable spec (OpenAPI 3.1): /api/v1/openapi.json