Zum Hauptinhalt springen

Bestands- und Lager-API

Für Entwickler: Bestände, Artikel, Lagerplätze und Lagerdokumente wie Wareneingänge, Bestellungen, Picklisten, Inventuren und Umlagerungen über die API lesen und ändern, mit Scopes, Idempotency-Key, Cursor und Fehlercodes.

H
Verfasst von Hayko

Bestands- und Lager-API

Mit der Bestands- und Lager-API liest und ändert Ihr eigenes System Ihr QrX-Lager: Bestände, Artikel, Lagerplätze, Bewegungen, Wareneingänge, Bestellungen, Picklisten, Inventuren, Ladungsträger und Teamaufgaben. Dieser Artikel richtet sich an Entwickler. Die Basis-URL ist https://api.qr-x.net, und jeder Aufruf benötigt Authorization: Bearer <accessToken>; zum Token siehe "Schnellstart für Entwickler: QrX Public API und Webhooks". Die vollständige Referenz finden Sie unter qrx.eu/api. Es werden nie Preise, Kosten oder Bestandswerte zurückgegeben.

Zugriff und Scopes

  • Bestände, Artikel, Lagerplätze, Bewegungen, Inventuren und Ladungsträger brauchen stock.read; Artikel und Lagerplätze anlegen oder ändern und eine Korrektur buchen brauchen stock.write. Wareneingänge brauchen inbound.read und inbound.write. Bestellungen brauchen purchasing.read und purchasing.write. Picklisten brauchen fulfilment.read und Teamaufgaben tasks.read.

  • Ein eingeschränkter Schlüssel erhält die neuen Bereiche nie automatisch. Haken Sie unter API-Zugriff Einkauf, Fulfillment oder Aufgaben an, wenn der Schlüssel sie braucht. Wareneingangspositionen zeigen unitPrice und currency nur, wenn der Schlüssel auch Bestellungen lesen darf.

  • Ein API-Schlüssel, der nicht eingeschränkt ist, hat vollen Zugriff und kann die Bestands-API sofort nutzen.

  • Um einen Schlüssel einzuschränken, öffnen Sie Settings > Business > Merchant Channels, öffnen den Vertriebskanal, Registerkarte Settings, Block API-Zugriff, und haken Bestände, Artikel, Lagerplätze, Bewegungen, Inventuren und Ladungsträger lesen und Bestand anpassen sowie Artikel und Lagerplätze anlegen oder ändern nach Bedarf an. Siehe "Einen API-Schlüssel einschränken (API-Zugriff)".

  • In Ihrem Konto muss der QrX-Lagerbestand eingeschaltet sein. Ohne ihn antwortet die API mit 403 ACCESS_DENIED.

GET /api/v1/stock/levels

Eine Zeile pro Bestandsartikel und Lager. Query: stockItemId, sku, warehouseId, locationId, updatedSince (ISO 8601), cursor, limit (1 bis 200, Standard 50) und scope.

{
  "items": [{
    "stockItemId": 345, "sku": "TSHIRT-M", "ean": "8712345678906",
    "ownerCustomerId": null, "partnerCustomerId": null,
    "warehouseId": 1, "warehouseCode": "MAIN",
    "onHand": 42, "sellable": 40, "blocked": 2, "reserved": 5,
    "incoming": 0, "backorder": 0, "available": 35,
    "locationQuantity": null,
    "lastSequence": 98231, "updatedAt": "2026-10-11T09:12:03Z"
  }],
  "nextCursor": "1-345"
}

  • reserved ist alles, was offene Bestellungen halten. available ist sellable minus reserved.

  • lastSequence ist die Nummer der letzten Bestandsbewegung dieses Artikels in diesem Lager. Es ist dieselbe Nummer wie sequence im Webhook stock/adjusted.

  • sku muss exakt übereinstimmen; Groß- und Kleinschreibung sowie Leerzeichen am Rand werden ignoriert.

  • Mit locationId erhalten Sie nur Artikel, die an diesem Lagerplatz Bestand haben, und locationQuantity enthält die Einheiten dort.

GET /api/v1/stock/items

Ihre Bestandsartikel, die neuesten zuerst. Query: sku, ean, updatedSince, cursor, limit (1 bis 200, Standard 50) und scope.

{
  "items": [{
    "id": 345, "sku": "TSHIRT-M", "name": "T-shirt M", "ean": "8712345678906",
    "unit": "piece", "kind": "simple", "trackingMode": "none", "isActive": true,
    "ownerCustomerId": null, "partnerCustomerId": null, "weightG": 180,
    "updatedAt": "2026-10-01T08:00:00Z"
  }],
  "nextCursor": 344
}

GET /api/v1/stock/locations

Die Lagerplätze, die Bestand führen können, die neuesten zuerst. Query: warehouseId, cursor, limit (1 bis 200, Standard 50).

{
  "items": [{ "id": 77, "warehouseId": 1, "warehouseCode": "MAIN", "code": "A-01-02",
    "kind": "bin", "zone": "A", "isActive": true }],
  "nextCursor": 76
}

POST /api/v1/stock/adjust

Bucht eine Bestandskorrektur. Der Header Idempotency-Key ist erforderlich.

POST /api/v1/stock/adjust
Idempotency-Key: erp-count-2026-10-11-0001{ "stockItemId": 345, "sku": null, "warehouseId": null, "locationId": 77, "delta": -3,
  "reasonCode": "correction", "note": "Cycle count by ERP", "lotCode": null, "expiryDate": null }

  • Geben Sie stockItemId oder sku an. Es muss genau ein Artikel passen.

  • Geben Sie locationId an. Ein Konto ohne Lagerplätze darf stattdessen warehouseId angeben, oder nichts für das Standardlager.

  • delta ist die Änderung in Einheiten: positiv bucht zu, negativ bucht ab. Der Wert darf nicht 0 sein.

  • reasonCode ist erforderlich: correction, found, damaged, expired, defective, lost, theft, sample oder other.

  • Geben Sie für einen Artikel mit Chargen lotCode und, wenn der Artikel es benötigt, expiryDate an.

Die Antwort enthält den neuen Stand des Artikels in diesem Lager:

{ "movementId": 98231, "stockItemId": 345, "warehouseId": 1, "locationId": 77, "delta": -3,
  "onHand": 42, "sellable": 40, "available": 35, "sequence": 98231, "replayed": false }

Lagerressourcen

Alle Listen verwenden cursor und limit (1 bis 100, Standard 50) und antworten mit items und nextCursor, die neuesten zuerst. Diese Ressourcen sind nur Ihre eigenen Lagerdaten: scope=partner antwortet mit 422. Alle Felder von Anfrage und Antwort stehen in der Referenz unter qrx.eu/api.

  • Wareneingänge: GET /api/v1/receipts (Filter state, warehouseId, q), GET /api/v1/receipts/{id}, POST /api/v1/receipts (mit announce: true wird der Wareneingang angekündigt), PATCH /api/v1/receipts/{id}, POST .../{id}/lines, POST .../{id}/complete und POST .../{id}/cancel.

  • Bestellungen: GET /api/v1/purchase-orders, GET /api/v1/purchase-orders/{id}, POST /api/v1/purchase-orders (ein Entwurf), PATCH .../{id}, POST .../{id}/lines, POST .../{id}/send, POST .../{id}/close und POST .../{id}/cancel. Senden funktioniert genau wie in PortalX: Mit email: true sendet QrX das PDF an den Lieferanten (oder an to), höchstens 5-mal pro Bestellung und 30-mal pro Konto und Stunde (429 RATE_LIMIT_EXCEEDED).

  • Lagerartikel und Lagerplätze: POST und PATCH /api/v1/stock/items und /api/v1/stock/locations. Lagerplätze erfordern Full WMS mit Lagerfächern. Die Zeilen von GET items und GET locations haben jetzt version.

  • Lesen: GET /api/v1/stock/movements, GET /api/v1/picklists, GET /api/v1/counts (mit /{id} und /{id}/lines), GET /api/v1/containers und GET /api/v1/tasks (mit /{id}). Teamaufgaben sind nur lesbar.

  • Eine Änderung eines bestehenden Datensatzes braucht die version, die Sie zuletzt gelesen haben. Hat jemand den Datensatz inzwischen geändert, erhalten Sie 409: Lesen Sie ihn neu und versuchen Sie es erneut.

  • Lager (Scope stock.read, Vollständiges WMS): GET /api/v1/warehouses mit Code, Name, Standard, Zeitzone, Adresse, Kontakt, Anlieferhinweisen, Laderampen und GLN; GET /api/v1/warehouses/{id}, .../{id}/hours (Wareneingangs- und Versandzeiten pro Wochentag) und .../{id}/closures (mit from und cursor).

  • Umlagerungen (Scope stock.read, Vollständiges WMS mit mehreren Lagern und Umlagerungen): GET /api/v1/transfers (Filter state, warehouseId, direction) und GET /api/v1/transfers/{id} mit den Positionen und den shipping-Daten. Nur lesend.

  • Bestellungen: Anlegen und PATCH akzeptieren die Kopffelder reference, supplierReference, buyerUserId, internalNote, incoterm, incotermPlace, paymentTermsDays, shippingCost, shippingVatRate, pricesIncludeVat, deliveryAddress und partialDeliveryAllowed, und Positionen akzeptieren discountPct, vatRate, note, confirmedQty und confirmedOn. Bestellungen liefern die von QrX berechneten Summen (netto, MwSt. pro Satz, brutto). Die Liste filtert nach reference, expectedFrom und expectedTo. Die Konditionen sind nach dem Senden der Bestellung festgeschrieben.

  • Wareneingänge: Anlegen und PATCH akzeptieren supplierId, um einen Wareneingang mit einem Lieferanten zu verknüpfen; Zeilen liefern es zurück.

  • Lagerartikel und Lagerplätze: Schreibzugriffe auf Lagerplätze akzeptieren zone oder zoneId und die Felder für Maße und Kapazität; Schreibzugriffe auf Artikel akzeptieren storage, dangerousGoods, imageUrl und abcClassOverride. POST /api/v1/stock/adjust akzeptiert Ihre eigenen Grundcodes.

Idempotency-Key

  • Senden Sie pro Korrektur einen eigenen eindeutigen Text. Wiederholen Sie dieselbe Anfrage mit demselben Schlüssel, erhalten Sie die erste Antwort erneut mit replayed: true, und nichts wird doppelt gebucht.

  • Derselbe Schlüssel mit einem anderen Body wird mit 409 DUPLICATE_IDEMPOTENCY_KEY abgelehnt.

  • Ein Schlüssel darf nicht mit einem QrX-Systempräfix wie mcp- beginnen und nicht auf einen Doppelpunkt gefolgt von Ziffern enden (422).

  • Jeder POST und PATCH der Lagerressourcen braucht den Header Idempotency-Key (1 bis 140 Zeichen). Derselbe Schlüssel mit demselben Body auf derselben Route liefert die erste Antwort erneut, mit dem Header Idempotent-Replayed: true. Ein wiederholtes Anlegen eines Wareneingangs oder einer Bestellung liefert den Datensatz, wie er jetzt ist.

  • Antworten werden 30 Tage aufbewahrt. Ein fehlgeschlagener Aufruf (4xx oder 5xx) wird nicht aufbewahrt, Sie dürfen denselben Schlüssel also erneut senden.

  • Solange der erste Aufruf mit einem Schlüssel noch läuft, erhalten Sie 409 CONCURRENCY_CONFLICT mit Retry-After. Ein Aufruf, der mittendrin abgebrochen ist, gibt seinen Schlüssel nach 15 Minuten frei.

  • Das Senden einer Bestellung mit email: true mailt den Lieferanten höchstens einmal pro Schlüssel. Schlägt ein Aufruf fehl, nachdem die Bestellung gesendet wurde, aber vor der E-Mail, folgt für diesen Schlüssel keine E-Mail: Prüfen Sie den E-Mail-Verlauf der Bestellung in PortalX und senden Sie mit einem neuen Schlüssel erneut.

Cursor und updatedSince

  • Jede Liste gibt items und nextCursor zurück. Senden Sie nextCursor als cursor für die nächste Seite. Null bedeutet das Ende. Es gibt keine Gesamtzahl.

  • Behandeln Sie einen Cursor als undurchsichtigen Wert.

  • Mit updatedSince erhalten Sie nur Zeilen, die zu diesem Zeitpunkt oder danach geändert wurden. Der Server betrachtet bis zu 1.000 Zeilen pro Aufruf, eine Seite kann also weniger Zeilen als limit enthalten, sogar keine, während nextCursor gesetzt ist. Rufen Sie weiter auf, bis nextCursor null ist.

Bestand bei einem Fulfillment-Partner: scope=partner

Lagert ein Fulfillment-Partner Ihren Bestand, fügen Sie scope=partner zu GET levels und GET items hinzu, um Ihren eigenen Bestand bei Ihren Partnern zu lesen. Ohne erhalten Sie den Standard, scope=warehouse: den Bestand in Ihren eigenen Lagern.

  • Jede Zeile hat partnerCustomerId (das Partnerlager) und Ihre eigene Id in ownerCustomerId.

  • Verwenden Sie partnerCustomerId in der Query, um einen Partner zu behalten, und warehouseId für eines seiner Lager.

  • Lagerplätze werden nie zurückgegeben: locationId, GET locations und POST adjust antworten in diesem Scope mit 422.

  • Sie benötigen mindestens einen Partner mit aktivem Zugriff; in Ihrem eigenen Konto muss der Lagerbestand nicht eingeschaltet sein.

Fehlercodes

  • 401 UNAUTHORIZED: kein gültiges Zugriffstoken.

  • 403 STOCK_SCOPE_MISSING: Der Schlüssel ist eingeschränkt und hat stock.read oder stock.write nicht.

  • 403 ACCESS_DENIED: Der Lagerbestand ist für das Konto nicht eingeschaltet, oder, mit scope=partner, kein Partner hat aktiven Zugriff.

  • 404 STOCK_NOT_FOUND: Der Artikel, das Lager oder der Lagerplatz gehört nicht Ihnen.

  • 400 IDEMPOTENCY_KEY_REQUIRED: ein POST ohne den Header.

  • 422 VALIDATION_FAILED: ungültige Eingabe, zum Beispiel delta 0, ein unbekannter Grundcode, ein ungültiger Cursor oder mehr als 50 Artikel mit derselben SKU oder EAN (nach stockItemId filtern).

  • 409 DUPLICATE_IDEMPOTENCY_KEY: derselbe Schlüssel mit einem anderen Body.

  • 409 STOCK_CONFLICT: nicht genug Bestand, Bestand, der einer Pickliste zugeteilt ist, eine SKU, die zu mehreren Artikeln passt, oder ein inaktiver Artikel.

  • 409 CONCURRENCY_CONFLICT: Senden Sie dieselbe Anfrage mit demselben Schlüssel erneut.

  • 429: zu viele Anfragen; warten Sie Retry-After ab.

  • 403 INBOUND_SCOPE_MISSING, PURCHASING_SCOPE_MISSING, FULFILMENT_SCOPE_MISSING oder TASKS_SCOPE_MISSING: Der Schlüssel ist eingeschränkt und es fehlt dieser Bereich.

  • 404 INBOUND_NOT_FOUND, PURCHASE_ORDER_NOT_FOUND oder NOT_FOUND: Der Datensatz gehört nicht Ihnen.

  • 409 INBOUND_CONFLICT oder PURCHASING_CONFLICT: Version oder Status erlauben die Änderung nicht.

  • 429 RATE_LIMIT_EXCEEDED: zu viele E-Mails an den Lieferanten für eine Bestellung.

  • 400 ZONE_UNKNOWN: Die Zone ist keine aktive Zone dieses Lagers. 400 DG_CLASS_UNKNOWN: Die Gefahrgutklasse ist keine sichtbare Klasse Ihres Kontos. Ein Grund, der Fotos verlangt, kann über die API nicht verwendet werden.

Wann Sie unser Support-Team kontaktieren sollten

Kontaktieren Sie den QrX-Support, wenn die API ACCESS_DENIED antwortet, obwohl der Lagerbestand eingeschaltet ist, oder wenn ein Stand von PortalX abweicht. Nennen Sie die requestId der Antwort, die SKU und den Zeitpunkt. Senden Sie nie den API-Schlüssel oder das Token mit.

Hat dies deine Frage beantwortet?