{
  "info": {
    "name": "Shirtee Merchant API",
    "description": "Händler-API von Shirtee. Setze die Variable apiKey auf deinen API-Schlüssel. Doku: https://developers.shirtee.cloud/ (Es gibt keine Sandbox: schreibende Aufrufe wirken live.)",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://gateway.shirtee.cloud"
    },
    {
      "key": "apiKey",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "Complaints",
      "item": [
        {
          "name": "List complaints",
          "request": {
            "method": "GET",
            "description": "Listet die Reklamationen des Händlers, neueste zuerst (nach Anlagezeitpunkt), seitenweise mit `limit`/`offset`; `total_count` ist die Gesamtzahl der Treffer aller Seiten.\n\nOhne `status`-Filter fehlen Entwürfe; `status=DRAFT` zeigt sie. Alle Filter sind optional und werden mit UND verknüpft. Es erscheinen nur eigene Reklamationen. Die Listenzeile enthält keine Fotos; Details liefert `GET /api/complaints/{complaint_id}`. `404`, solange die Reklamationsfunktion für Händler nicht freigeschaltet ist.\n\nLists the merchant's complaints, newest first (by creation time), paginated with `limit`/`offset`; `total_count` is the total number of matches across all pages.\n\nWithout a `status` filter drafts are omitted; `status=DRAFT` shows them. All filters are optional and combined with AND. Only the merchant's own complaints appear. A list row carries no photos; use `GET /api/complaints/{complaint_id}` for details. `404` while the complaint feature is not enabled for merchants.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/complaints",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints"
              ],
              "variable": [],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl Einträge pro Seite, 1 bis 100, Standard 20. / Entries per page, 1 to 100, default 20."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl zu überspringender Einträge, mindestens 0, Standard 0. / Entries to skip, minimum 0, default 0."
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Reklamationen in diesem Status; ohne Angabe alle außer Entwürfen. / Only complaints in this status; if omitted, all except drafts."
                },
                {
                  "key": "reason",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Reklamationen, die mindestens eine Position mit diesem Grund enthalten. / Only complaints that contain at least one item with this reason."
                },
                {
                  "key": "customer_id",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf die Kunden-ID des Händlers; Händler sehen ohnehin nur eigene Reklamationen, ein fremder Wert ergibt eine leere Liste. / Filter on the merchant customer ID; merchants only see their own complaints anyway, a foreign value returns an empty list."
                },
                {
                  "key": "order_id",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Reklamationen zu dieser Bestell-ID (numerisch, mindestens 1). / Only complaints for this numeric order ID (minimum 1)."
                },
                {
                  "key": "order_reference",
                  "value": "",
                  "disabled": true,
                  "description": "Trifft externe Referenz oder Kennung der Bestellung oder die Reklamationsnummer (`RK-…`), ohne Beachtung der Groß-/Kleinschreibung, 1 bis 100 Zeichen. / Matches the order external reference or order identifier, or the complaint reference (`RK-…`), case-insensitive, 1 to 100 characters."
                },
                {
                  "key": "date_from",
                  "value": "",
                  "disabled": true,
                  "description": "Anlagedatum ab (einschließlich), Format `YYYY-MM-DD`, Tagesgrenze nach Berliner Zeit. / Created on or after this date (inclusive), format `YYYY-MM-DD`, day boundary in Berlin time."
                },
                {
                  "key": "date_to",
                  "value": "",
                  "disabled": true,
                  "description": "Anlagedatum bis (einschließlich), Format `YYYY-MM-DD`, Tagesgrenze nach Berliner Zeit. / Created on or before this date (inclusive), format `YYYY-MM-DD`, day boundary in Berlin time."
                },
                {
                  "key": "without_ticket",
                  "value": "",
                  "disabled": true,
                  "description": "`true`: nur Reklamationen, für die noch kein Support-Vorgang angelegt wurde (Standard `false`, kein Filter). / `true`: only complaints without a support case yet (default `false`, no filter)."
                }
              ]
            }
          }
        },
        {
          "name": "Create a complaint draft",
          "request": {
            "method": "POST",
            "description": "Legt eine Reklamation als **Entwurf** (`DRAFT`) an. Der Entwurf sperrt die Positionen noch nicht; die Sperre gegen Doppelmeldungen greift erst beim Einreichen (`POST /api/complaints/{complaint_id}/submit`). Pro Position ist ein Grund (`reason`) anzugeben; jede Position darf nur einmal vorkommen.\n\nVoraussetzungen: die Bestellung gehört zum Händler und jede gewählte Position ist reklamierbar (siehe `GET /api/orders/{order_id}/complaint-eligibility`). Danach Fotos je Position hochladen und den Entwurf einreichen. Ein Entwurf, der nicht eingereicht wird, wird nach einer Frist (derzeit 24 Stunden) automatisch verworfen; seine Fotos werden gelöscht.\n\nFehler: `400` mit `extra.error = ITEM_NOT_IN_ORDER`, wenn eine Position nicht zur Bestellung gehört (`extra.order_item_ids`); `400` ohne Code bei ungültigem Body (leere Positionsliste, doppelte `order_item_id`, unbekannter Grund); `409`, wenn eine Position nicht reklamierbar ist (`extra.error` = Sperrgrund der Bestellung, `ITEM_ALREADY_REPORTED` oder `ITEM_NOT_ELIGIBLE`; `extra.items` ordnet jeder `order_item_id` ihren Code zu); `404` bei fremder oder unbekannter Bestellung.\n\nCreates a complaint as a **draft** (`DRAFT`). A draft does not lock the items yet; the lock against duplicate reports takes effect on submit (`POST /api/complaints/{complaint_id}/submit`). Give one `reason` per item; each item may appear only once.\n\nRequirements: the order belongs to the merchant and every selected item is eligible (see `GET /api/orders/{order_id}/complaint-eligibility`). Then upload photos per item and submit the draft. A draft that is not submitted is discarded automatically after a time limit (currently 24 hours) and its photos are deleted.\n\nErrors: `400` with `extra.error = ITEM_NOT_IN_ORDER` (`extra.order_item_ids`) or a body validation error without code; `409` when an item is not eligible (`extra.error` is the order-level block, `ITEM_ALREADY_REPORTED` or `ITEM_NOT_ELIGIBLE`, `extra.items` maps each `order_item_id` to its code); `404` for an unknown or foreign order.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/complaints",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints"
              ],
              "variable": [],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_id\": 123456,\n  \"items\": [\n    {\n      \"order_item_id\": 90001,\n      \"reason\": \"STAINS\"\n    }\n  ],\n  \"comment\": \"Flecken auf der Vorderseite / Stains on the front\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get a complaint",
          "request": {
            "method": "GET",
            "description": "Liefert eine Reklamation mit allen Positionen, Fotos und Druckvorschau. Statusverlauf:\n\n| Status | Bedeutung |\n|---|---|\n| `DRAFT` | Entwurf, noch nicht eingereicht |\n| `SUBMITTED` | eingereicht, Positionen gesperrt |\n| `IN_REVIEW` | in Prüfung beim Support |\n| `WAITING_CUSTOMER` | Rückfrage des Supports offen; Antwort über `supplement` |\n| `RESOLVING` | Abschluss läuft |\n| `RESOLVED` | abgeschlossen, Entscheidungen sichtbar |\n| `CANCELLED` | storniert, Positionen wieder frei |\n\nHändler lösen die Wechsel `DRAFT` → `SUBMITTED` (`submit`), `WAITING_CUSTOMER` → `IN_REVIEW` (`supplement`) und `SUBMITTED`/`IN_REVIEW`/`WAITING_CUSTOMER` → `CANCELLED` (`cancel`) aus; alle anderen Wechsel macht der Support. Nach `WAITING_CUSTOMER` nennt `info_requested_at` den Zeitpunkt der Rückfrage; bleibt sie standardmäßig 14 Tage unbeantwortet, wird die Reklamation automatisch storniert (`cancelled_by_role = SYSTEM`).\n\n**Entscheidungen** (`decision`, `decision_note`, `replace_variant_id`) und `resolution_order_id` sind für Händler erst im Status `RESOLVED` sichtbar; vorher steht `decision` auf `NONE`. Foto-`url` und Druckvorschau (`preview_urls`) gelten je Abruf; die Foto-Links laufen nach 15 Minuten ab. `404`, wenn die Reklamation fehlt oder einem anderen Händler gehört.\n\nReturns a complaint with all items, photos and print previews. Status lifecycle: `DRAFT` (not yet submitted) → `SUBMITTED` (items locked) → `IN_REVIEW` (support is reviewing) ↔ `WAITING_CUSTOMER` (follow-up question open) → `RESOLVING` (resolution running) → `RESOLVED`; `CANCELLED` releases the items.\n\nMerchants trigger `DRAFT` → `SUBMITTED` (`submit`), `WAITING_CUSTOMER` → `IN_REVIEW` (`supplement`) and `SUBMITTED`/`IN_REVIEW`/`WAITING_CUSTOMER` → `CANCELLED` (`cancel`); all other transitions are made by support. If a follow-up question stays unanswered for 14 days (default), the complaint is cancelled automatically (`cancelled_by_role = SYSTEM`).\n\n**Decisions** (`decision`, `decision_note`, `replace_variant_id`) and `resolution_order_id` are visible to merchants only in status `RESOLVED`; before that `decision` is `NONE`. Photo `url` values are generated per request and expire after 15 minutes. `404` if the complaint does not exist or belongs to another merchant.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/complaints/:complaint_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints",
                ":complaint_id"
              ],
              "variable": [
                {
                  "key": "complaint_id",
                  "value": "",
                  "description": "ID der Reklamation (`id` aus Anlegen oder Liste). / ID of the complaint (`id` from create or list)."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Remove a photo from a complaint draft",
          "request": {
            "method": "DELETE",
            "description": "Entfernt ein Foto aus einem Entwurf. Nur in `DRAFT` möglich; nach dem Einreichen oder bei einer Rückfrage (`WAITING_CUSTOMER`) lassen sich Fotos nicht mehr entfernen, nur ergänzen. Das entfernte Foto zählt nicht mehr zur Höchstzahl von 5 je Position und erscheint nicht mehr in der Reklamation.\n\nAntwort `204` ohne Body. `409` mit `extra.error = STATUS_CONFLICT`, wenn die Reklamation kein Entwurf mehr ist. `404`, wenn Reklamation oder aktives Foto fehlen oder fremd sind; ein bereits entferntes Foto ergibt ebenfalls `404`.\n\nRemoves a photo from a draft. Possible only in `DRAFT`; once the complaint is submitted, or while a follow-up question is open (`WAITING_CUSTOMER`), photos can no longer be removed, only added. A removed photo no longer counts toward the limit of 5 per item and no longer appears in the complaint.\n\nResponse `204` without body. `409` with `extra.error = STATUS_CONFLICT` if the complaint is no longer a draft. `404` if the complaint or an active photo does not exist or belongs to another merchant; an already removed photo also returns `404`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/complaints/:complaint_id/attachments/:attachment_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints",
                ":complaint_id",
                "attachments",
                ":attachment_id"
              ],
              "variable": [
                {
                  "key": "complaint_id",
                  "value": "",
                  "description": "ID der Reklamation, aus der das Foto entfernt wird. / ID of the complaint the photo is removed from."
                },
                {
                  "key": "attachment_id",
                  "value": "",
                  "description": "ID des Fotos (`attachments[].id` aus Upload-Antwort oder Reklamation). / ID of the photo (`attachments[].id` from the upload response or the complaint)."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Withdraw a complaint",
          "request": {
            "method": "POST",
            "description": "Zieht eine Reklamation zurück (Status `CANCELLED`). Möglich in `SUBMITTED`, `IN_REVIEW` und `WAITING_CUSTOMER`. Alle Positionen werden sofort wieder freigegeben und können erneut reklamiert werden. Eine Wiedereröffnung gibt es nicht. Ein Entwurf wird nicht zurückgezogen, er verfällt von selbst.\n\n`reason` ist optional (höchstens 1000 Zeichen) und wird für den Support festgehalten; der Body darf `{}` sein. In der Antwort steht die Reklamation im Status `CANCELLED`.\n\n`409` mit `extra.error = STATUS_CONFLICT` für einen Entwurf, ab `RESOLVING` (Abschluss läuft oder ist erfolgt) und für eine bereits stornierte Reklamation. `404` bei fremder oder unbekannter Reklamation.\n\nWithdraws a complaint (status `CANCELLED`). Possible in `SUBMITTED`, `IN_REVIEW` and `WAITING_CUSTOMER`. All items are released immediately and can be reported again. A complaint cannot be reopened. A draft is not withdrawn; it expires on its own.\n\n`reason` is optional (up to 1000 characters) and recorded for support; the body may be `{}`. The response shows the complaint in status `CANCELLED`.\n\n`409` with `extra.error = STATUS_CONFLICT` for a draft, from `RESOLVING` on (resolution running or done) and for an already cancelled complaint. `404` for an unknown or foreign complaint.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/complaints/:complaint_id/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints",
                ":complaint_id",
                "cancel"
              ],
              "variable": [
                {
                  "key": "complaint_id",
                  "value": "",
                  "description": "ID der zurückzuziehenden Reklamation. / ID of the complaint to withdraw."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Problem wurde anderweitig gelöst. / The problem was solved otherwise.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Attach a photo to a complaint item",
          "request": {
            "method": "POST",
            "description": "Hängt **ein** Foto an eine Position der Reklamation. Request als `multipart/form-data` mit dem Feld `file`; genau eine Datei je Aufruf.\n\n- Erlaubte Typen: JPEG, PNG, WebP, HEIC. Der Typ wird am Dateiinhalt geprüft, nicht am Dateinamen.\n- Höchstgröße 10 MB je Datei; höchstens 5 aktive Fotos je Position.\n- Statusabhängig: nur in `DRAFT` und `WAITING_CUSTOMER` (Rückfrage des Supports). Ein Upload in `WAITING_CUSTOMER` ändert den Status nicht; erst `POST /api/complaints/{complaint_id}/supplement` beantwortet die Rückfrage.\n- Das Foto wird privat abgelegt; abrufbare Links liefert `GET /api/complaints/{complaint_id}`.\n\nFehler: `400` mit `extra.error` = `FILE_TOO_LARGE`, `TOO_MANY_ATTACHMENTS` oder `INVALID_FILE_TYPE`; `409` mit `STATUS_CONFLICT` in jedem anderen Status; `404`, wenn Reklamation oder Position fehlen, fremd sind oder die Position nicht zu dieser Reklamation gehört.\n\nAttaches **one** photo to an item of the complaint. Send `multipart/form-data` with the field `file`; exactly one file per request.\n\nAllowed types: JPEG, PNG, WebP, HEIC, checked on the file content, not the file name. Maximum 10 MB per file and 5 active photos per item. Allowed only in `DRAFT` and `WAITING_CUSTOMER` (follow-up question from support); an upload in `WAITING_CUSTOMER` does not change the status, only `POST /api/complaints/{complaint_id}/supplement` answers the question. The photo is stored privately; time-limited links are returned by `GET /api/complaints/{complaint_id}`.\n\nErrors: `400` with `extra.error` = `FILE_TOO_LARGE`, `TOO_MANY_ATTACHMENTS` or `INVALID_FILE_TYPE`; `409` `STATUS_CONFLICT` in any other status; `404` if the complaint or item does not exist, belongs to another merchant, or the item is not part of this complaint.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/complaints/:complaint_id/items/:item_id/attachments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints",
                ":complaint_id",
                "items",
                ":item_id",
                "attachments"
              ],
              "variable": [
                {
                  "key": "complaint_id",
                  "value": "",
                  "description": "ID der Reklamation (`id` aus der Antwort von `POST /api/complaints`). / ID of the complaint (`id` from the response of `POST /api/complaints`)."
                },
                {
                  "key": "item_id",
                  "value": "",
                  "description": "ID der Reklamationsposition (`items[].id` der Reklamation), nicht die `order_item_id`. / ID of the complaint item (`items[].id` of the complaint), not the `order_item_id`."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Submit a complaint",
          "request": {
            "method": "POST",
            "description": "Reicht einen Entwurf ein (`DRAFT` → `SUBMITTED`). Beim Einreichen gilt:\n\n- **Fotopflicht:** jede Position braucht mindestens ein Foto, außer ihr Grund ist `PRODUCT_MISSING`. Sonst `400` mit `extra.error = PHOTO_REQUIRED` und den betroffenen `extra.complaint_item_ids`.\n- **Erneute Prüfung der Reklamierbarkeit** wie beim Anlegen; ist inzwischen eine Position gesperrt, kommt `409` mit dem Sperrgrund und `extra.items`.\n- **Sperre:** die Positionen sind danach für weitere Reklamationen gesperrt (`ITEM_ALREADY_REPORTED`), (die Sperre endet beim Stornieren, siehe `POST /api/complaints/{complaint_id}/cancel`). Meldet parallel eine andere Reklamation dieselbe Position, scheitert eine der beiden mit `409` `ITEM_ALREADY_REPORTED`.\n- Der Support-Vorgang zur Reklamation wird erst nach dem Einreichen asynchron angelegt.\n\nWeitere Fehler: `409` mit `STATUS_CONFLICT`, wenn die Reklamation kein Entwurf (mehr) ist, also auch bei erneutem Einreichen; `404` bei fremder oder unbekannter Reklamation. Entscheidungen des Supports erscheinen in der Antwort erst im Status `RESOLVED`.\n\nSubmits a draft (`DRAFT` → `SUBMITTED`). On submit: every item needs at least one photo unless its reason is `PRODUCT_MISSING` (otherwise `400` `PHOTO_REQUIRED` with `extra.complaint_item_ids`); eligibility is checked again (`409` with the block code and `extra.items` if an item became ineligible); the items are then locked against further complaints (the lock ends when the complaint is cancelled). If another complaint reports the same item concurrently, one of the two fails with `409` `ITEM_ALREADY_REPORTED`. The support case for the complaint is created asynchronously after submission.\n\nOther errors: `409` `STATUS_CONFLICT` if the complaint is not a draft (including a repeated submit); `404` for an unknown or foreign complaint. Decisions of support appear in the response only in status `RESOLVED`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/complaints/:complaint_id/submit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints",
                ":complaint_id",
                "submit"
              ],
              "variable": [
                {
                  "key": "complaint_id",
                  "value": "",
                  "description": "ID des einzureichenden Entwurfs. / ID of the draft to submit."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Answer a complaint follow-up question",
          "request": {
            "method": "POST",
            "description": "Beantwortet eine Rückfrage des Supports: `WAITING_CUSTOMER` → `IN_REVIEW`. Fotos vorher über den Upload-Endpunkt hochladen; der Upload allein ändert den Status nicht. Der Text ist optional (höchstens 5000 Zeichen) und geht an den Support; der Body darf `{}` sein.\n\n`409` mit `extra.error = STATUS_CONFLICT` in jedem anderen Status als `WAITING_CUSTOMER`, auch bei einer zweiten Antwort. `404` bei fremder oder unbekannter Reklamation. Entscheidungen des Supports erscheinen in der Antwort erst im Status `RESOLVED`.\n\nAnswers a follow-up question from support: `WAITING_CUSTOMER` → `IN_REVIEW`. Upload photos first via the upload endpoint; an upload alone does not change the status. The text is optional (up to 5000 characters) and is passed to support; the body may be `{}`.\n\n`409` with `extra.error = STATUS_CONFLICT` in any status other than `WAITING_CUSTOMER`, including a second answer. `404` for an unknown or foreign complaint. Decisions of support appear in the response only in status `RESOLVED`.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/complaints/:complaint_id/supplement",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "complaints",
                ":complaint_id",
                "supplement"
              ],
              "variable": [
                {
                  "key": "complaint_id",
                  "value": "",
                  "description": "ID der Reklamation, die auf eine Rückfrage wartet. / ID of the complaint awaiting an answer."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"text\": \"Weitere Fotos vom Etikett wurden hochgeladen. / Further photos of the label were uploaded.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Complaint eligibility of an order",
          "request": {
            "method": "GET",
            "description": "Prüft, ob und welche Positionen einer Bestellung reklamiert werden können. Der Aufruf liest nur und ändert nichts; dieselben Regeln gelten beim Anlegen und beim Einreichen einer Reklamation.\n\n- Sperrgründe der **Bestellung** (`code`, gelten für jede Position): `ORDER_CANCELED`, `ORDER_WITHOUT_CUSTOMER`, `DEADLINE_EXCEEDED`. Die Frist beträgt standardmäßig 150 Tage ab Bestelldatum (Datum in Berlin, Tag 150 eingeschlossen).\n- Sperrgründe je **Position** (`items[].code`): `ITEM_INACTIVE` (Position in der Bestellung nicht mehr aktiv), `ITEM_ALREADY_REPORTED` (Position steckt in einer eingereichten, nicht stornierten Reklamation), `NOT_SHIPPED` (Position ist noch nicht vollständig versendet).\n- `eligible` auf oberster Ebene ist `true`, sobald mindestens eine Position reklamierbar ist. `items[].eligible` ist `false`, wenn die Bestellung oder die Position gesperrt ist; `items[].code` nennt dabei nur den Grund der Position, den der Bestellung nennt `code`.\n\nEine fremde oder unbekannte Bestellung liefert `404`. `404` kommt auch, solange die Reklamationsfunktion für Händler nicht freigeschaltet ist.\n\nChecks whether and which items of an order can be reported as a complaint. The call is read-only; the same rules apply when a complaint is created and when it is submitted.\n\nOrder-level blocks (`code`) apply to every item: `ORDER_CANCELED`, `ORDER_WITHOUT_CUSTOMER`, `DEADLINE_EXCEEDED` (default window: 150 days from the order date, Berlin date, day 150 included). Item-level blocks (`items[].code`): `ITEM_INACTIVE`, `ITEM_ALREADY_REPORTED`, `NOT_SHIPPED`. Top-level `eligible` is `true` when at least one item can be reported; `items[].code` carries only the item's own reason, the order-level reason is in `code`.\n\nAn unknown or foreign order returns `404`, and so does every call while the complaint feature is not enabled for merchants.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/complaint-eligibility",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "complaint-eligibility"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Numerische ID der Bestellung, wie sie die Bestell-Endpunkte liefern; fremde IDs ergeben 404. / Numeric order ID as returned by the order endpoints; IDs of other merchants return 404."
                }
              ],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Invoices",
      "item": [
        {
          "name": "List invoices and credit notes of an order",
          "request": {
            "method": "GET",
            "description": "Liefert alle Rechnungen und Gutschriften einer Bestellung des Händlers als Liste. Die Bestellung muss dem Händler gehören; bei fremden oder unbekannten Bestellungen antwortet die API mit `404`, nie mit `403`.\n\nDie Liste ist nach Erstellungszeitpunkt absteigend sortiert (neueste zuerst), bei gleichem Zeitpunkt nach `id` absteigend. `limit` wird auf 1 bis 100 begrenzt, `offset` auf mindestens 0; Werte außerhalb werden angepasst und nicht abgelehnt. Die Antwort ist ein reines Array ohne Gesamtzahl; die Liste ist zu Ende, wenn weniger Einträge als `limit` zurückkommen. Eine Bestellung ohne Belege liefert ein leeres Array. Die PDF-Datei eines Belegs liefert `GET /api/invoices/user/{invoice_id}/pdf`.\n\nErforderlicher Scope: `merchant_invoice:read`.\n\nReturns all invoices and credit notes of one of the merchant's orders as an array. The order must belong to the merchant; for foreign or unknown orders the API responds with `404`, never `403`.\n\nThe list is sorted by creation time, newest first, with `id` descending as tie-breaker. `limit` is clamped to 1 to 100 and `offset` to at least 0; out-of-range values are adjusted, not rejected. The response is a plain array without a total count; the list is exhausted when fewer than `limit` entries come back. An order without documents returns an empty array. Use `GET /api/invoices/user/{invoice_id}/pdf` to obtain the PDF file of a document.\n\nRequired scope: `merchant_invoice:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/invoices/by-order/:order_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "invoices",
                "by-order",
                ":order_id"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "ID der Bestellung, deren Belege aufgelistet werden. Fremde oder unbekannte IDs liefern 404. / ID of the order whose documents are listed. Foreign or unknown IDs return 404."
                }
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Maximale Anzahl Einträge pro Seite, Standard 20; Werte werden auf 1 bis 100 begrenzt. / Maximum number of entries per page, default 20; values are clamped to 1 to 100."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl der zu überspringenden Einträge, Standard 0; negative Werte werden zu 0. / Number of entries to skip, default 0; negative values become 0."
                }
              ]
            }
          }
        },
        {
          "name": "List invoices and credit notes",
          "request": {
            "method": "GET",
            "description": "Liefert eine seitenweise Liste aller Rechnungen und Gutschriften des Händlers über alle Bestellungen hinweg. Der Händler ergibt sich ausschließlich aus dem Zugangstoken; ein Parameter dafür existiert nicht.\n\nSortierung: Erstellungszeitpunkt absteigend (neueste zuerst), bei gleichem Zeitpunkt `id` absteigend. `limit` wird auf 1 bis 100 begrenzt, `offset` auf mindestens 0; Werte außerhalb werden angepasst und nicht abgelehnt. `total_count` nennt die Gesamtzahl der Treffer nach Anwendung der Filter, unabhängig von `limit` und `offset`.\n\nFilter (alle optional, miteinander kombinierbar): `invoice_type` filtert nach Belegart, `date_from` und `date_to` begrenzen das Belegdatum (`invoice_date`) inklusive beider Grenzen, `amount_min` und `amount_max` begrenzen den Bruttobetrag (`gross_amount`) in Cent inklusive beider Grenzen. Gutschriften tragen negative Beträge. Ungültige Filterwerte liefern `400`.\n\nDie PDF-Datei eines Belegs liefert `GET /api/invoices/user/{invoice_id}/pdf`. Erforderlicher Scope: `merchant_invoice:read`.\n\nReturns a paginated list of all invoices and credit notes of the merchant across all orders. The merchant is derived solely from the access token; there is no parameter for it.\n\nSorting: creation time descending (newest first), with `id` descending as tie-breaker. `limit` is clamped to 1 to 100 and `offset` to at least 0; out-of-range values are adjusted, not rejected. `total_count` is the total number of matches after applying the filters, independent of `limit` and `offset`.\n\nFilters (all optional, combinable): `invoice_type` filters by document type, `date_from` and `date_to` bound the document date (`invoice_date`) inclusively, `amount_min` and `amount_max` bound the gross amount (`gross_amount`) in cents inclusively. Credit notes carry negative amounts. Invalid filter values return `400`.\n\nUse `GET /api/invoices/user/{invoice_id}/pdf` to obtain the PDF file of a document. Required scope: `merchant_invoice:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/invoices/user",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "invoices",
                "user"
              ],
              "variable": [],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Maximale Anzahl Einträge pro Seite, Standard 20; Werte werden auf 1 bis 100 begrenzt. / Maximum number of entries per page, default 20; values are clamped to 1 to 100."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl der zu überspringenden Einträge, Standard 0; negative Werte werden zu 0. / Number of entries to skip, default 0; negative values become 0."
                },
                {
                  "key": "invoice_type",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Belege dieser Art (`INVOICE`, `CREDIT_NOTE` oder `BONUS_CREDIT`); ohne Angabe alle Arten. / Only documents of this type (`INVOICE`, `CREDIT_NOTE` or `BONUS_CREDIT`); all types if omitted."
                },
                {
                  "key": "date_from",
                  "value": "",
                  "disabled": true,
                  "description": "Frühestes Belegdatum (`invoice_date`), inklusive, Format `YYYY-MM-DD`; ohne Angabe keine untere Grenze. / Earliest document date (`invoice_date`), inclusive, format `YYYY-MM-DD`; no lower bound if omitted."
                },
                {
                  "key": "date_to",
                  "value": "",
                  "disabled": true,
                  "description": "Spätestes Belegdatum (`invoice_date`), inklusive, Format `YYYY-MM-DD`; ohne Angabe keine obere Grenze. / Latest document date (`invoice_date`), inclusive, format `YYYY-MM-DD`; no upper bound if omitted."
                },
                {
                  "key": "amount_min",
                  "value": "",
                  "disabled": true,
                  "description": "Kleinster Bruttobetrag (`gross_amount`) in Cent, inklusive; Gutschriften sind negativ. Ganzzahl im 32-Bit-Bereich. / Smallest gross amount (`gross_amount`) in cents, inclusive; credit notes are negative. Integer within the 32-bit range."
                },
                {
                  "key": "amount_max",
                  "value": "",
                  "disabled": true,
                  "description": "Größter Bruttobetrag (`gross_amount`) in Cent, inklusive; Gutschriften sind negativ. Ganzzahl im 32-Bit-Bereich. / Largest gross amount (`gross_amount`) in cents, inclusive; credit notes are negative. Integer within the 32-bit range."
                }
              ]
            }
          }
        },
        {
          "name": "Get the PDF URL of an invoice",
          "request": {
            "method": "GET",
            "description": "Liefert die Download-URL der PDF eines Belegs des Händlers. Rechnungs-PDFs werden erst beim ersten Abruf im Hintergrund erzeugt; der Aufruf kann deshalb statt der URL einen Zwischenstand melden. Die Antwort unterscheidet drei Fälle über `status` und den HTTP-Statuscode:\n\n| Fall | HTTP | `status` | `pdf_url` |\n|---|---|---|---|\n| PDF liegt vor | `200` | `ready` | signierte URL |\n| PDF wird erzeugt | `202` | `generating` | `null` |\n| PDF kommt nicht mehr | `200` | `failed` | `null` |\n\n`failed` kommt mit `200`, nicht mit `202` und nicht als Fehler: Wer nur auf den HTTP-Statuscode prüft, muss diesen Fall zusätzlich über `status` erkennen.\n\n**Abrufverhalten (Polling):** Solange `202` mit `generating` kommt, denselben Aufruf wiederholen. Der erste Abruf stößt die Erzeugung an. Weitere Abrufe innerhalb von 5 Minuten nach einem Anstoß stoßen sie nicht erneut an. Nach Ablauf dieses Fensters ohne fertige PDF wird die Erzeugung erneut angestoßen, insgesamt höchstens 3-mal. `failed` meldet die API erst, wenn 3 Anstöße erfolgt sind und das Fenster des letzten Anstoßes abgelaufen ist, also frühestens etwa 15 Minuten nach dem ersten Abruf. Eine gerade angefragte Rechnung meldet `generating`, auch wenn sie am Ende scheitert. Bei `failed` stößt ein weiterer Abruf nichts mehr an. Eine bereits vorhandene PDF wird immer als `ready` geliefert, auch nach der dritten Erzeugung.\n\nDie URL in `pdf_url` ist signiert und 15 Minuten gültig. Nach Ablauf mit einem neuen Aufruf eine frische URL holen. Der Beleg muss dem Händler gehören; bei fremden oder unbekannten IDs antwortet die API mit `404`, nie mit `403`. Erforderlicher Scope: `merchant_invoice:read`.\n\nReturns the download URL of the PDF of one of the merchant's documents. Invoice PDFs are generated in the background on first request, so the call may report an intermediate state instead of the URL. The response distinguishes three cases via `status` and the HTTP status code (see the table above).\n\n`failed` is returned with `200`, not `202` and not as an error: clients that branch only on the HTTP status code must also detect this case through `status`.\n\n**Polling behavior:** While `202` with `generating` is returned, repeat the same call. The first request triggers generation. Further requests within 5 minutes of a trigger do not trigger it again. Once that window has passed without a finished PDF, generation is triggered again, at most 3 times in total. The API reports `failed` only after 3 triggers have happened and the window of the last trigger has elapsed, i.e. roughly 15 minutes after the first request at the earliest. A freshly requested invoice reports `generating` even if it will eventually fail. After `failed`, further requests trigger nothing. An existing PDF is always returned as `ready`, even after the third generation.\n\nThe URL in `pdf_url` is signed and valid for 15 minutes. After it expires, call again to get a fresh URL. The document must belong to the merchant; for foreign or unknown IDs the API responds with `404`, never `403`. Required scope: `merchant_invoice:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/invoices/user/:invoice_id/pdf",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "invoices",
                "user",
                ":invoice_id",
                "pdf"
              ],
              "variable": [
                {
                  "key": "invoice_id",
                  "value": "",
                  "description": "ID des Belegs (Rechnung oder Gutschrift), wie `id` in der Belegliste; Ganzzahl von 1 bis 2147483647, sonst 400. Fremde oder unbekannte IDs liefern 404. / ID of the document (invoice or credit note), as `id` in the document list; integer from 1 to 2147483647, otherwise 400. Foreign or unknown IDs return 404."
                }
              ],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Orders",
      "item": [
        {
          "name": "List orders",
          "request": {
            "method": "GET",
            "description": "Liefert die Bestellungen des authentifizierten Händlers seitenweise (Scope `merchant_order:read`). Fremde Bestellungen sind nie enthalten, auch nicht über `designer_id`. Ohne Filter werden alle Status geliefert, auch `CANCELED`. Standardsortierung: Eingangszeitpunkt absteigend (neueste zuerst), Standard-Seitengröße 50, maximal 200.\n\nAlle Filter gelten gleichzeitig (UND). Mehrwertige Filter (`status`, `retention_reason_ids`, `routing_causes`, `mockup_gap_reasons`) werden durch Wiederholen des Parameters angegeben, z. B. `status=CHECK&status=ERROR`; mehrere Werte desselben Parameters gelten als ODER. Ungültige Werte dieser Filter und ein ungültiges `sort` ergeben 400. Die Parameter `supplier_stock_issue`, `subscribed_by_me`, `backlog_bucket` und `express` haben für Händler keine Wirkung. Die Einträge enthalten die Kopfdaten der Bestellung ohne Positionen; Positionen, Sendungsverfolgung und Zahlungsangaben liefert `GET /api/orders/{order_id}`. `total_count` ist die Trefferzahl vor der Seitenbildung.\n\nReturns the orders of the authenticated merchant, paginated (scope `merchant_order:read`). Orders of other merchants are never included, not even via `designer_id`. Without filters, all statuses are returned, including `CANCELED`. Default sort: creation time descending (newest first), default page size 50, maximum 200.\n\nAll filters apply together (AND). Multi-value filters (`status`, `retention_reason_ids`, `routing_causes`, `mockup_gap_reasons`) are passed by repeating the parameter, e.g. `status=CHECK&status=ERROR`; several values of one parameter combine with OR. Invalid values of these filters and an invalid `sort` return 400. The parameters `supplier_stock_issue`, `subscribed_by_me`, `backlog_bucket` and `express` have no effect for merchants. Entries contain the order header without line items; line items, shipment tracking and payment data are returned by `GET /api/orders/{order_id}`. `total_count` is the number of matches before pagination.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders"
              ],
              "variable": [],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Seitengröße; Werte außerhalb von 1 bis 200 werden auf diesen Bereich begrenzt. Die Antwort nennt den angewendeten Wert. / Page size; values outside 1 to 200 are clamped to that range. The response reports the applied value."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl der zu überspringenden Treffer; negative Werte gelten als 0. / Number of matches to skip; negative values are treated as 0."
                },
                {
                  "key": "designer_id",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf die Händlerkennung der Bestellung (exakter Treffer). Für Händler ist das die eigene Kennung; jeder andere Wert liefert eine leere Liste. / Filter on the merchant identifier of the order (exact match). For merchants this is the own identifier; any other value returns an empty list."
                },
                {
                  "key": "platform_key",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf die Herkunftsplattform der Bestellung (exakter Treffer, Text). / Filter on the originating platform of the order (exact match, text)."
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf einen oder mehrere Bestellstatus (`OrderStatus`); Parameter wiederholen, mehrere Werte gelten als ODER. / Filter on one or more order statuses (`OrderStatus`); repeat the parameter, several values combine with OR."
                },
                {
                  "key": "retention_reason_id",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf einen Rückhaltegrund (ID aus `GET /api/orders/retention-reasons`); nur Bestellungen, die aus diesem Grund angehalten sind. / Filter on one retention reason (ID from `GET /api/orders/retention-reasons`); only orders held for that reason."
                },
                {
                  "key": "retention_reason_ids",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf mehrere Rückhaltegründe (IDs); Parameter wiederholen, mehrere Werte gelten als ODER. Zusammen mit `retention_reason_id` müssen beide Bedingungen erfüllt sein. / Filter on several retention reasons (IDs); repeat the parameter, several values combine with OR. Combined with `retention_reason_id`, both conditions must hold."
                },
                {
                  "key": "routing_causes",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf Ursachen, aus denen einer Bestellung noch kein Produktionspartner zugewiesen werden konnte (`RoutingCause`); wiederholbar, ODER. Wirkt nur auf Bestellungen im Status `CHECK` oder `ERROR`. / Filter on causes why no production partner could be assigned to an order yet (`RoutingCause`); repeatable, OR. Only matches orders in status `CHECK` or `ERROR`."
                },
                {
                  "key": "mockup_gap_reasons",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf Gründe, aus denen für mindestens eine Druckposition der Bestellung kein eigenes Vorschaubild (Mockup) entstanden ist (`MockupGapReason`); wiederholbar, ODER. / Filter on reasons why at least one print position of the order has no own preview image (mockup) (`MockupGapReason`); repeatable, OR."
                },
                {
                  "key": "manufacturer_id",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf Bestellungen, die mindestens einen Fertigungsauftrag bei dem Produktionspartner mit dieser ID haben. / Filter on orders with at least one manufacturing order at the production partner with this ID."
                },
                {
                  "key": "reordered_from_order_id",
                  "value": "",
                  "disabled": true,
                  "description": "Umkehrsuche zur Nachbestellung: nur Bestellungen, die per `POST /api/orders/{order_id}/reorder` aus der Bestellung mit dieser ID entstanden sind. / Reverse lookup for re-orders: only orders created via `POST /api/orders/{order_id}/reorder` from the order with this ID."
                },
                {
                  "key": "supplier_stock_issue",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; der Wert wird ignoriert. / Has no effect for merchants; the value is ignored."
                },
                {
                  "key": "subscribed_by_me",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; der Wert wird ignoriert. / Has no effect for merchants; the value is ignored."
                },
                {
                  "key": "backlog_bucket",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; der Wert wird ignoriert und nicht geprüft. / Has no effect for merchants; the value is ignored and not validated."
                },
                {
                  "key": "express",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; der Wert wird ignoriert. / Has no effect for merchants; the value is ignored."
                },
                {
                  "key": "sort",
                  "value": "",
                  "disabled": true,
                  "description": "Sortierung nach Eingangszeitpunkt: `created_at:asc` oder `created_at:desc` (Standard). Jeder andere Wert ergibt 400. Bei gleichem Zeitpunkt folgt die Bestell-ID in derselben Richtung. / Sort by creation time: `created_at:asc` or `created_at:desc` (default). Any other value returns 400. For equal timestamps, the order ID follows in the same direction."
                }
              ]
            }
          }
        },
        {
          "name": "Create an order",
          "request": {
            "method": "POST",
            "description": "Legt eine Bestellung für den authentifizierten Händler an (Scope `merchant_order:create` oder `merchant_order:write`). Erlaubt sind nur die im Request-Schema genannten Felder. Preise, Versandart, Hersteller-Wahl, Zahlungs- und Steuerangaben sind nicht Teil des Requests: Preise stammen aus dem Katalog, alles Übrige legt das System fest. Wird ein anderes Feld gesendet, auch verschachtelt in Positionen oder Veredelungen, antwortet die API mit 403 und nennt die Feldpfade; eine `designer_id` im Body muss der eigenen Kennung entsprechen. Ohne `external_reference` vergibt das System eine eigene. Eine Position mit `amount` n wird als n einzelne Stücke angelegt. Veredelungen werden nur bei veredelbaren Artikeln übernommen; ist für den Artikel genau ein Veredelungsverfahren hinterlegt und weicht die Angabe ab, wird das hinterlegte verwendet. Mit `POST /api/orders/preview` lässt sich der Entwurf vorab prüfen.\n\nDie Antwort (201) enthält die Bestellung im Status direkt nach der Anlage. Normalerweise ist das `PENDING`, die Verarbeitung (Zuweisung, Produktion) startet danach automatisch. Je nach Prüfergebnis kann der Status stattdessen `PAUSE` (kurze Wartezeit vor der Zuweisung), `CHECK` oder `ERROR` sein; dann nennt `retention` den Grund. Beispiele sind eine inaktive Variante oder kein verfügbarer Produktionspartner (`CHECK`) sowie fehlende Preise (`ERROR`). Diese Fälle sind kein HTTP-Fehler: die Bestellung existiert. Als Fehler antwortet die API mit 400 bei unbekannter oder mehrdeutiger SKU, nicht konfiguriertem Veredelungsverfahren und bei einer SKU in `additional_products`, die kein Zusatzprodukt ist; mit 422 bei ungültigem Body.\n\nMit dem optionalen Header `Idempotency-Key` ist der Aufruf wiederholbar: Derselbe Schlüssel mit demselben Body liefert die beim ersten Aufruf angelegte Bestellung erneut (201, gleicher Inhalt) und legt keine zweite an. Derselbe Schlüssel mit anderem Body ergibt 422 mit `extra.error=IDEMPOTENCY_KEY_REUSED`. Der Schlüssel gilt je Händler; parallele Wiederholungen warten auf den ersten Aufruf. Ohne Header legt jeder Aufruf eine neue Bestellung an.\n\nCreates an order for the authenticated merchant (scope `merchant_order:create` or `merchant_order:write`). Only the fields named in the request schema are allowed. Prices, shipping method, manufacturer choice, payment and tax data are not part of the request: prices come from the catalog, everything else is set by the system. If any other field is sent, also nested in items or processings, the API responds with 403 and names the field paths; a `designer_id` in the body must match the own identifier. Without `external_reference` the system assigns one. An item with `amount` n is created as n single pieces. Processings are only taken over for articles that can be finished; if the article has exactly one stored processing method and the value sent differs, the stored one is used. Use `POST /api/orders/preview` to check the draft beforehand.\n\nThe response (201) contains the order in the status right after creation. Normally this is `PENDING`, and processing (assignment, production) starts automatically afterwards. Depending on the checks, the status can instead be `PAUSE` (short wait before assignment), `CHECK` or `ERROR`; then `retention` names the reason. Examples are an inactive variant or no available production partner (`CHECK`) and missing prices (`ERROR`). These cases are not HTTP errors: the order exists. The API responds with 400 for an unknown or ambiguous SKU, an unconfigured processing method and a SKU in `additional_products` that is not an additional product; with 422 for an invalid body.\n\nWith the optional `Idempotency-Key` header the call is repeatable: the same key with the same body returns the order created by the first call again (201, same content) and does not create a second one. The same key with a different body returns 422 with `extra.error=IDEMPOTENCY_KEY_REUSED`. The key is scoped per merchant; concurrent retries wait for the first call. Without the header every call creates a new order.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders"
              ],
              "variable": [],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_reference\": \"SHOP-10045\",\n  \"delivery_address\": {\n    \"first_name\": \"Max\",\n    \"last_name\": \"Mustermann\",\n    \"street\": \"Musterstraße\",\n    \"house_number\": \"1\",\n    \"postcode\": \"50667\",\n    \"city\": \"Köln\",\n    \"country\": \"DE\",\n    \"email\": \"max.mustermann@example.com\"\n  },\n  \"billing_address\": {\n    \"first_name\": \"Max\",\n    \"last_name\": \"Mustermann\",\n    \"street\": \"Musterstraße\",\n    \"house_number\": \"1\",\n    \"postcode\": \"50667\",\n    \"city\": \"Köln\",\n    \"country\": \"DE\",\n    \"email\": \"max.mustermann@example.com\"\n  },\n  \"items\": [\n    {\n      \"sku\": \"TSHIRT-SCHWARZ-M\",\n      \"external_id\": \"LINE-1\",\n      \"amount\": 2,\n      \"processings\": [\n        {\n          \"processing_type\": \"DTG\",\n          \"position\": \"FRONT\",\n          \"design_url\": \"https://example.com/designs/motiv-001.png\"\n        }\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get an order by external reference",
          "request": {
            "method": "GET",
            "description": "Liefert eine Bestellung des authentifizierten Händlers über die eigene `external_reference` (Scope `merchant_order:read`). Antwortform, Query-Parameter und Einschränkungen sind identisch zu `GET /api/orders/{order_id}`.\n\nDie `external_reference` ist nicht eindeutig: Stornierte Bestellungen geben sie wieder frei, sodass sie mehrfach vorkommen kann. Es wird genau eine Bestellung geliefert; nicht stornierte Bestellungen haben Vorrang vor stornierten, innerhalb davon gewinnt die neueste. Existieren nur stornierte Bestellungen, wird die neueste davon geliefert. Der Treffer ist exakt und unterscheidet Groß- und Kleinschreibung. Gibt es keine eigene Bestellung mit dieser Referenz, ist die Antwort 404; fremde Bestellungen mit gleicher Referenz bleiben unsichtbar. Die Suche nach Teilen oder ohne Beachtung der Schreibweise leistet `GET /api/orders/search`.\n\nReturns an order of the authenticated merchant by its own `external_reference` (scope `merchant_order:read`). Response shape, query parameters and restrictions are identical to `GET /api/orders/{order_id}`.\n\nThe `external_reference` is not unique: canceled orders release it, so it can occur more than once. Exactly one order is returned; non-canceled orders take precedence over canceled ones and, within that, the newest wins. If only canceled orders exist, the newest of them is returned. Matching is exact and case-sensitive. Without an own order for this reference the response is 404; orders of other merchants with the same reference stay invisible. Case-insensitive search is provided by `GET /api/orders/search`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/by-external-reference/:external_reference",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "by-external-reference",
                ":external_reference"
              ],
              "variable": [
                {
                  "key": "external_reference",
                  "value": "",
                  "description": "Eigene Bestellreferenz, wie bei der Anlage gesendet; exakter Treffer mit Beachtung der Schreibweise. / Own order reference as sent at creation; exact, case-sensitive match."
                }
              ],
              "query": [
                {
                  "key": "mos",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; Fertigungsaufträge erscheinen nur mit `tracking=true`. / Has no effect for merchants; manufacturing orders only appear with `tracking=true`."
                },
                {
                  "key": "log",
                  "value": "",
                  "disabled": true,
                  "description": "Muss für Händler `false` bleiben; `true` ergibt 403. / Must stay `false` for merchants; `true` returns 403."
                },
                {
                  "key": "payment",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; Zahlungsangaben sind immer enthalten, sofern vorhanden. / Has no effect for merchants; payment data is always included when available."
                },
                {
                  "key": "tracking",
                  "value": "",
                  "disabled": true,
                  "description": "`true` füllt `manufacturing_orders` mit den Sendungen der Teilaufträge; Standard `false` (leere Liste). / `true` fills `manufacturing_orders` with the shipments of the partial orders; default `false` (empty list)."
                },
                {
                  "key": "invoices",
                  "value": "",
                  "disabled": true,
                  "description": "Muss für Händler `false` bleiben; `true` ergibt 403. / Must stay `false` for merchants; `true` returns 403."
                },
                {
                  "key": "payload",
                  "value": "",
                  "disabled": true,
                  "description": "Muss für Händler `false` bleiben; `true` ergibt 403. / Must stay `false` for merchants; `true` returns 403."
                }
              ]
            }
          }
        },
        {
          "name": "Preview order creation",
          "request": {
            "method": "POST",
            "description": "Prüft einen Bestellentwurf, ohne etwas zu speichern (Scope `merchant_order:read`). Der Request-Body ist derselbe wie bei `POST /api/orders` und durchläuft dieselbe Bereinigung: Felder, die ein Händler nicht setzen darf, ergeben 403 mit den Feldpfaden. Die Antwort zeigt je Position SKU, Produktname, Stückzahl und Preise, die Adressen, Netto- und Bruttobetrag in Cent, ob die Bestellung produzierbar wäre (`is_feasible`, mit `unassignable_skus`), sowie `fingerprint` und `confirmed_summary`. Zwei Vorschauen desselben Entwurfs mit unverändertem Stand liefern denselben `fingerprint`.\n\nDas Guthaben des Händlers wird nicht geprüft (`wallet_coverage_checked` ist immer `false`). Ein fehlender Katalogpreis ergibt `null` bei Preisen und Beträgen. Ein nicht produzierbarer Entwurf ist kein Fehler, sondern steht im 200-Body. Fehler im Request selbst ergeben 422 mit `extra.error`: `INVALID_FIELDS` (mit `extra.fields`, Liste aus Feldpfad und Fehlercode), `UNKNOWN_SKU`, `AMBIGUOUS_SKU` (mit `extra.candidate_skus`), `VARIANT_INACTIVE` (mit `extra.alternative_variant_ids`), `UNKNOWN_PROCESSING_TYPE` und `NOT_AN_ADDITIONAL_PRODUCT`. Ein Body, der kein JSON-Objekt ist, ergibt 422 `INVALID_FIELDS`. Die Anlage selbst verhält sich bei inaktiven Varianten anders, siehe `POST /api/orders`.\n\nValidates an order draft without saving anything (scope `merchant_order:read`). The request body is the same as for `POST /api/orders` and goes through the same sanitisation: fields a merchant may not set return 403 with the field paths. The response shows per position SKU, product name, quantity and prices, the addresses, net and gross amount in cents, whether the order could be produced (`is_feasible`, with `unassignable_skus`), plus `fingerprint` and `confirmed_summary`. Two previews of the same draft in an unchanged state return the same `fingerprint`.\n\nThe merchant balance is not checked (`wallet_coverage_checked` is always `false`). A missing catalog price yields `null` for prices and amounts. A draft that cannot be produced is not an error but stated in the 200 body. Errors in the request itself return 422 with `extra.error`: `INVALID_FIELDS` (with `extra.fields`, a list of field path and error code), `UNKNOWN_SKU`, `AMBIGUOUS_SKU` (with `extra.candidate_skus`), `VARIANT_INACTIVE` (with `extra.alternative_variant_ids`), `UNKNOWN_PROCESSING_TYPE` and `NOT_AN_ADDITIONAL_PRODUCT`. A body that is not a JSON object returns 422 `INVALID_FIELDS`. Creation itself behaves differently for inactive variants, see `POST /api/orders`.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "preview"
              ],
              "variable": [],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_reference\": \"SHOP-10045\",\n  \"delivery_address\": {\n    \"first_name\": \"Max\",\n    \"last_name\": \"Mustermann\",\n    \"street\": \"Musterstraße\",\n    \"house_number\": \"1\",\n    \"postcode\": \"50667\",\n    \"city\": \"Köln\",\n    \"country\": \"DE\",\n    \"email\": \"max.mustermann@example.com\"\n  },\n  \"billing_address\": {\n    \"first_name\": \"Max\",\n    \"last_name\": \"Mustermann\",\n    \"street\": \"Musterstraße\",\n    \"house_number\": \"1\",\n    \"postcode\": \"50667\",\n    \"city\": \"Köln\",\n    \"country\": \"DE\",\n    \"email\": \"max.mustermann@example.com\"\n  },\n  \"items\": [\n    {\n      \"sku\": \"TSHIRT-SCHWARZ-M\",\n      \"external_id\": \"LINE-1\",\n      \"amount\": 2,\n      \"processings\": [\n        {\n          \"processing_type\": \"DTG\",\n          \"position\": \"FRONT\",\n          \"design_url\": \"https://example.com/designs/motiv-001.png\"\n        }\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List retention reasons",
          "request": {
            "method": "GET",
            "description": "Liefert die aktiven Rückhaltegründe (Scope `merchant_order:read`): die Gründe, aus denen eine Bestellung angehalten sein kann. Die Liste ist nicht seitenweise und nach Anlagezeitpunkt des Grundes absteigend sortiert. Jeder Eintrag hat `id` und `name`. Die `id` ist der stabile Schlüssel und passt zu `retention.reason_id` an Bestellungen sowie zu den Filtern `retention_reason_id` und `retention_reason_ids` von `GET /api/orders`; der `name` ist ein Anzeigetext, der sich ändern kann und nicht für Logik verwendet werden sollte. Deaktivierte Gründe fehlen.\n\nReturns the active retention reasons (scope `merchant_order:read`): the reasons why an order can be held. The list is not paginated and sorted by creation time of the reason, newest first. Each entry has `id` and `name`. The `id` is the stable key and matches `retention.reason_id` on orders as well as the filters `retention_reason_id` and `retention_reason_ids` of `GET /api/orders`; the `name` is a display text that may change and should not be used for logic. Deactivated reasons are missing.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/retention-reasons",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "retention-reasons"
              ],
              "variable": [],
              "query": []
            }
          }
        },
        {
          "name": "Order counts per retention reason",
          "request": {
            "method": "GET",
            "description": "Zählt die angehaltenen Bestellungen des Händlers je Rückhaltegrund (Scope `merchant_order:read`), in einer Anfrage statt einer Listenabfrage je Grund. Nur aktive Gründe werden gezählt; Gründe ohne Bestellung fehlen in `counts` und sind nicht als 0 enthalten. `counts` ist nach `retention_reason_id` aufsteigend sortiert. Eine Bestellung hat höchstens einen Grund, `total_count` ist daher die Summe aller `count`. Mit dem Parameter `status` lässt sich auf dieselbe Statusmenge einschränken wie in `GET /api/orders`, damit die Zahlen zur dortigen Liste passen; ohne `status` zählen alle Status.\n\nCounts the held orders of the merchant per retention reason (scope `merchant_order:read`), in one request instead of one list query per reason. Only active reasons are counted; reasons without an order are missing from `counts` and not included as 0. `counts` is sorted by `retention_reason_id` ascending. An order has at most one reason, so `total_count` is the sum of all `count` values. The `status` parameter restricts to the same set of statuses as in `GET /api/orders`, so the numbers match the list there; without `status` all statuses count.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/retention-summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "retention-summary"
              ],
              "variable": [],
              "query": [
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": "Beschränkt die Zählung auf einen oder mehrere Bestellstatus (`OrderStatus`); Parameter wiederholen, mehrere Werte gelten als ODER. Ohne Angabe zählen alle Status. / Restricts the count to one or more order statuses (`OrderStatus`); repeat the parameter, several values combine with OR. Without it all statuses count."
                }
              ]
            }
          }
        },
        {
          "name": "Search orders",
          "request": {
            "method": "GET",
            "description": "Sucht Bestellungen des authentifizierten Händlers über die Bestellnummer (`slug`, beginnt mit `CL-`), die numerische Bestell-ID oder die `external_reference` (Scope `merchant_order:read`). Der Treffer ist exakt und ohne Beachtung der Groß-/Kleinschreibung; führende und nachfolgende Leerzeichen in `q` werden entfernt. Es gibt keine Teilwort- oder Präfixsuche.\n\nAblauf: Beginnt `q` mit `CL-` oder `CO-`, wird zuerst die Bestellnummer geprüft und nur bei keinem eigenen Treffer als `external_reference` gesucht. Eine Zahl wird zuerst als `external_reference` gesucht, bei keinem Treffer als Bestell-ID. Alles andere wird als `external_reference` gesucht. Ein Treffer über Bestellnummer oder ID ist höchstens eine Bestellung; bei `offset` über 0 ist `items` dann leer, `total_count` bleibt 1. Mehrere Bestellungen mit gleicher `external_reference` kommen neueste zuerst. Kein Treffer, ein fremder Treffer und ein `q` über 255 Zeichen ergeben eine leere Liste (`total_count` 0), nie einen Fehler. Ein leeres oder nur aus Leerzeichen bestehendes `q` ergibt 400. Die Antwort hat dieselbe Form wie `GET /api/orders`.\n\nSearches the orders of the authenticated merchant by order number (`slug`, starts with `CL-`), numeric order ID or `external_reference` (scope `merchant_order:read`). Matching is exact and case-insensitive; leading and trailing whitespace in `q` is removed. There is no partial-word or prefix search.\n\nFlow: if `q` starts with `CL-` or `CO-`, the order number is checked first and only without an own match searched as `external_reference`. A number is searched as `external_reference` first and, without a match, as order ID. Anything else is searched as `external_reference`. A hit by order number or ID is at most one order; with `offset` above 0, `items` is empty and `total_count` stays 1. Several orders with the same `external_reference` are returned newest first. No match, a match owned by someone else and a `q` longer than 255 characters return an empty list (`total_count` 0), never an error. An empty or whitespace-only `q` returns 400. The response has the same shape as `GET /api/orders`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "search"
              ],
              "variable": [],
              "query": [
                {
                  "key": "q",
                  "value": "",
                  "disabled": true,
                  "description": "Suchbegriff: Bestellnummer (`CL-…`), numerische Bestell-ID oder `external_reference`; Pflicht, höchstens 255 Zeichen wirksam. / Search term: order number (`CL-…`), numeric order ID or `external_reference`; required, at most 255 characters take effect."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Seitengröße; Werte außerhalb von 1 bis 200 werden auf diesen Bereich begrenzt. / Page size; values outside 1 to 200 are clamped to that range."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl der zu überspringenden Treffer; negative Werte gelten als 0. / Number of matches to skip; negative values are treated as 0."
                },
                {
                  "key": "subscribed_by_me",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; der Wert wird ignoriert. / Has no effect for merchants; the value is ignored."
                }
              ]
            }
          }
        },
        {
          "name": "Order statuses that allow each action",
          "request": {
            "method": "GET",
            "description": "Liefert je Aktion die Bestellstatus, in denen sie angenommen wird (Scope `merchant_order:read`). Die Tabelle gilt für alle Bestellungen gleich und hängt nicht vom Händler ab. Sie spiegelt die tatsächlich erzwungenen Regeln. Eine Aufnahme in die Liste ist notwendig, aber nicht immer hinreichend: `item_mod` setzt zusätzlich voraus, dass kein Fertigungsauftrag mehr dagegen spricht (z. B. schon an den Produktionspartner übergeben), und bei `cancel` entscheidet je Fertigungsauftrag, was sich tatsächlich stornieren lässt. Die Statuslisten sind alphabetisch sortiert.\n\nReturns for each action the order statuses in which it is accepted (scope `merchant_order:read`). The table is the same for all orders and does not depend on the merchant. It mirrors the rules that are actually enforced. Being listed is necessary but not always sufficient: `item_mod` additionally requires that no manufacturing order speaks against it (e.g. already handed over to the production partner), and for `cancel` each manufacturing order decides what can actually be canceled. The status lists are sorted alphabetically.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/status-gates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                "status-gates"
              ],
              "variable": [],
              "query": []
            }
          }
        },
        {
          "name": "Get an order",
          "request": {
            "method": "GET",
            "description": "Liefert eine Bestellung des authentifizierten Händlers mit Adressen, Positionen, Zahlungsangaben, Rückhaltegrund und verknüpften Support-Tickets (Scope `merchant_order:read`). Die Bestellung wird über die numerische Bestell-ID adressiert; eine fremde oder unbekannte ID ergibt 404.\n\nDie Query-Parameter steuern, was zusätzlich geladen wird. `tracking=true` ergänzt `manufacturing_orders` mit den Sendungen (Tracking-Nummer, Link, Versanddienstleister); ohne diesen Parameter ist `manufacturing_orders` leer. `log=true`, `invoices=true` und `payload=true` sind für Händler nicht zulässig und ergeben 403, auch bei Bestellungen des Händlers selbst. `payment` und `mos` haben für Händler keine Wirkung: Zahlungsangaben sind immer enthalten, sofern vorhanden.\n\nReturns an order of the authenticated merchant with addresses, line items, payment data, retention reason and linked support tickets (scope `merchant_order:read`). The order is addressed by its numeric order ID; an ID owned by someone else or unknown returns 404.\n\nThe query parameters control what is loaded in addition. `tracking=true` adds `manufacturing_orders` with the shipments (tracking number, link, carrier); without it `manufacturing_orders` is empty. `log=true`, `invoices=true` and `payload=true` are not allowed for merchants and return 403, even for the merchant own orders. `payment` and `mos` have no effect for merchants: payment data is always included when available.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Numerische Bestell-ID (`id` aus der Anlage- oder Listenantwort). / Numeric order ID (`id` from the create or list response)."
                }
              ],
              "query": [
                {
                  "key": "mos",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; Fertigungsaufträge erscheinen nur mit `tracking=true`. / Has no effect for merchants; manufacturing orders only appear with `tracking=true`."
                },
                {
                  "key": "log",
                  "value": "",
                  "disabled": true,
                  "description": "Muss für Händler `false` bleiben; `true` ergibt 403. / Must stay `false` for merchants; `true` returns 403."
                },
                {
                  "key": "payment",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händler; Zahlungsangaben sind immer enthalten, sofern vorhanden. / Has no effect for merchants; payment data is always included when available."
                },
                {
                  "key": "tracking",
                  "value": "",
                  "disabled": true,
                  "description": "`true` füllt `manufacturing_orders` mit den Sendungen der Teilaufträge; Standard `false` (leere Liste). / `true` fills `manufacturing_orders` with the shipments of the partial orders; default `false` (empty list)."
                },
                {
                  "key": "invoices",
                  "value": "",
                  "disabled": true,
                  "description": "Muss für Händler `false` bleiben; `true` ergibt 403. / Must stay `false` for merchants; `true` returns 403."
                },
                {
                  "key": "payload",
                  "value": "",
                  "disabled": true,
                  "description": "Muss für Händler `false` bleiben; `true` ergibt 403. / Must stay `false` for merchants; `true` returns 403."
                }
              ]
            }
          }
        },
        {
          "name": "Correct the billing address",
          "request": {
            "method": "PUT",
            "description": "Korrigiert die Rechnungsadresse einer Bestellung. Die Adresse ersetzt die bisherige vollständig; alle Pflichtfelder (`first_name`, `last_name`, `street`, `postcode`, `city`, `country`) müssen gesendet werden. Die Operation ändert nur Adressfelder, keine Steuerangaben (USt-IdNr. u. Ä.).\n\n**Wann möglich:** solange zur Bestellung keine aktive Rechnung existiert. Der Bestellstatus spielt dafür keine Rolle. Sobald eine aktive Rechnung vorliegt, antwortet die Operation mit 409; eine stornierte Rechnung (Gutschrift mit Neuausstellung) öffnet das Fenster wieder.\n\n**Land:** Ist bereits eine Rechnungsadresse hinterlegt, muss `country` dem hinterlegten Land entsprechen (Groß-/Kleinschreibung egal), sonst 409. Fehlt noch eine Rechnungsadresse, muss `country` dem Land der Lieferadresse entsprechen (sonst 409); hat die Bestellung keine Lieferadresse, ist die Antwort 422. Ein Landwechsel ist über die Händler-API nicht möglich, weil er die steuerliche Behandlung ändert.\n\n**Validierung:** Die Adresse wird strukturell geprüft (Hausnummer, Postleitzahl-Format, plausible Straße); bei einem Fehler antwortet die Operation mit 400 und nennt die Mängel in `detail`. Eine Zustellbarkeitsprüfung beim Paketdienst findet nicht statt, Packstationen werden nicht ausgenommen. Antwort: die aktualisierte Bestellung.\n\nCorrects the billing address of an order. The new address replaces the old one completely; all required fields (`first_name`, `last_name`, `street`, `postcode`, `city`, `country`) must be sent. Only address fields change, no tax details (VAT ID etc.).\n\n**When possible:** as long as the order has no active invoice. The order status does not matter. As soon as an active invoice exists the operation answers 409; a reversed invoice (credit note with re-issue) reopens the window.\n\n**Country:** If a billing address already exists, `country` must equal the stored country (case-insensitive), otherwise 409. If there is no billing address yet, `country` must equal the delivery country (otherwise 409); an order without a delivery address answers 422. A country change is not possible through the merchant API because it changes the tax treatment.\n\n**Validation:** The address is checked structurally (house number, postal code format, plausible street); on failure the operation answers 400 and lists the problems in `detail`. No deliverability check with the carrier takes place and Packstations are not exempt. Response: the updated order.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/billing-address",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "billing-address"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"first_name\": \"Max\",\n  \"last_name\": \"Mustermann\",\n  \"company\": \"Mustermann GmbH\",\n  \"street\": \"Musterstraße\",\n  \"house_number\": \"1\",\n  \"postcode\": \"50667\",\n  \"city\": \"Köln\",\n  \"country\": \"DE\",\n  \"email\": \"max.mustermann@example.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel an order",
          "request": {
            "method": "POST",
            "description": "Storniert eine Bestellung als Ganzes (alles oder nichts). Der Body ist optional. `force` wird für Händlerschlüssel ignoriert. Vorab zeigt `POST /api/orders/{order_id}/cancel/preview`, was die Stornierung jetzt bewirken würde.\n\n**Ablauf:** Shirtee prüft live, ob die Teile der Bestellung noch stornierbar sind. Ist eines in Produktion, versendet oder gerade in Übergabe, oder lässt sich sein Stand nicht prüfen, wird nichts storniert und `overall_outcome` ist `ABORTED_NOT_ELIGIBLE`. Fallen für die Stornierung Kosten an, bricht die Operation ebenfalls mit `ABORTED_NOT_ELIGIBLE` ab und setzt `paid_cancel_available` auf `true`; mit `accept_cost: true` wird sie dann kostenpflichtig ausgeführt (Rechnung und Guthabenbelastung für diese Positionen bleiben bestehen, es gibt dafür keine Gutschrift). Wurde zuvor eine Stornierung angestoßen und ist noch nicht bestätigt, führt ein erneuter Aufruf zu `ABORTED_NOT_ELIGIBLE` oder `CANCEL_PENDING_VERIFICATION`, ohne eine zweite Stornierung auszulösen.\n\n**Statuscodes:** 201 mit dem Ergebnis (auch bei nicht ausgeführter Stornierung, das Ergebnis steht in `overall_outcome`). 404 bei fremder oder unbekannter Bestellung. 409, wenn die Bestellung bereits den Status `CANCELED` oder `CANCELLATION_PENDING` hat; für einen Wiederholungsaufruf heißt das: erledigt bzw. in Klärung. Nach vollständiger Stornierung ist `phoenix_order_status_after` `CANCELED`, oder `CANCELLATION_PENDING`, solange die Bestätigung des Herstellers aussteht. `reason` ist für den Protokolleintrag; fehlt er, setzt Shirtee einen Standardtext.\n\nCancels an order as a whole (all or nothing). The body is optional. `force` is ignored for merchant keys. `POST /api/orders/{order_id}/cancel/preview` shows beforehand what the cancellation would do now.\n\n**Flow:** Shirtee checks live whether the parts of the order can still be canceled. If one is in production, shipped or being handed over, or its state cannot be verified, nothing is canceled and `overall_outcome` is `ABORTED_NOT_ELIGIBLE`. If the cancellation incurs costs, the operation also aborts with `ABORTED_NOT_ELIGIBLE` and sets `paid_cancel_available` to `true`; with `accept_cost: true` it is then executed against cost (invoice and wallet debit for these positions stand, no credit note is issued for them). If a cancellation was requested earlier and is not yet confirmed, a repeated call yields `ABORTED_NOT_ELIGIBLE` or `CANCEL_PENDING_VERIFICATION` without triggering a second cancellation.\n\n**Status codes:** 201 with the result (also when nothing was canceled; the outcome is in `overall_outcome`). 404 for a foreign or unknown order. 409 if the order is already `CANCELED` or `CANCELLATION_PENDING`; for a retry that means: done, or in clarification. After a full cancellation `phoenix_order_status_after` is `CANCELED`, or `CANCELLATION_PENDING` while the manufacturer's confirmation is outstanding. `reason` goes to the audit trail; if omitted, Shirtee uses a default text.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "cancel"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Bestellung vom Endkunden storniert\",\n  \"accept_cost\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Preview order cancellation",
          "request": {
            "method": "POST",
            "description": "Zeigt, was `POST /api/orders/{order_id}/cancel` für diese Bestellung jetzt tun würde. Es wird nichts storniert und nichts geschrieben. Die Vorschau fragt dieselben Live-Stände ab wie die Stornierung (nur lesend) und bewertet sie mit den Regeln für Händlerschlüssel (ohne `force`, ohne `accept_cost`). Kein Body.\n\n**`outcome`:** `CANCELABLE` (Stornierung würde gelingen), `CANCELABLE_WITH_COST` (nur kostenpflichtig, die Stornierung braucht `accept_cost: true`), `PENDING_MANUFACTURER_CONFIRMATION` (eine Stornierung läuft, die Bestätigung steht aus), `ALREADY_CANCELED` (eine weitere Stornierung würde mit 409 antworten; bei einem Wiederholungsaufruf bedeutet das: erledigt) oder `REJECTED` (mit `reasons`). Der Stand kann sich zwischen Vorschau und Stornierung ändern; maßgeblich ist die Stornierung selbst. Der Bestellzustand ist hier nie ein Fehler, sondern steht in der 200-Antwort; 404 nur bei fremder oder unbekannter Bestellung. Die REST-API verlangt die Vorschau nicht vor der Stornierung.\n\nShows what `POST /api/orders/{order_id}/cancel` would do for this order now. Nothing is canceled and nothing is written. The preview queries the same live states as the cancellation (read-only) and evaluates them with the rules for merchant keys (no `force`, no `accept_cost`). No body.\n\n**`outcome`:** `CANCELABLE` (the cancellation would succeed), `CANCELABLE_WITH_COST` (only against cost, the cancellation needs `accept_cost: true`), `PENDING_MANUFACTURER_CONFIRMATION` (a cancellation is under way, confirmation outstanding), `ALREADY_CANCELED` (another cancellation would answer 409; for a retry that means: done) or `REJECTED` (with `reasons`). The state can change between preview and cancellation; the cancellation itself is authoritative. The state of the order is never an error here but part of the 200 response; 404 only for a foreign or unknown order. The REST API does not require the preview before the cancellation.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/cancel/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "cancel",
                "preview"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Change the delivery address",
          "request": {
            "method": "PUT",
            "description": "Ändert die Lieferadresse einer Bestellung. Die Adresse ersetzt die bisherige vollständig; alle Pflichtfelder (`first_name`, `last_name`, `street`, `postcode`, `city`, `country`) müssen gesendet werden. Vorab zeigt `POST /api/orders/{order_id}/delivery-address/preview`, ob und wie die Änderung greifen würde.\n\n**Wann möglich und was passiert:**\n\n| Zustand der Bestellung | Wirkung |\n|---|---|\n| Status `CHECK`, nichts an den Hersteller übergeben | Adresse wird gespeichert, die Bestellung verlässt `CHECK` und wird fortgesetzt (bei Bedarf neu zugeteilt). Bei Abrechnung über das Guthaben: reicht es nicht, wird nur die Adresse gespeichert und die Bestellung bleibt angehalten. |\n| Status `PAUSE`, nichts an den Hersteller übergeben | Adresse wird gespeichert, die Bestellung bleibt in `PAUSE`. |\n| Teile sind an den Hersteller übergeben oder in Klärung | Die Änderung wird an den Hersteller übermittelt und erst bei Annahme gespeichert. Versendet der Hersteller über ein von Shirtee erzeugtes Versandetikett, das noch nicht erstellt ist, bleibt die Änderung bei Shirtee und das Etikett entsteht mit der neuen Adresse. |\n\nIst ein Teil der Bestellung gerade in Übergabe oder bereits versendet, ist die Änderung nicht mehr möglich (400). Ohne an den Hersteller übergebene Teile ist jeder andere Bestellstatus als `CHECK`/`PAUSE` ebenfalls 400.\n\n**Fehler:** 400 bei ungültiger Adresse (Mängel in `detail`; bei Packstationen `extra.error` = `INVALID_PACKSTATION`), bei nicht änderbarem Zustand; 404 bei fremder oder unbekannter Bestellung; 409 wenn das Versandetikett schon erstellt ist, der Hersteller die Änderung ablehnt oder auf der Rechnung bereits eine Gutschrift steht; 422 wenn der Hersteller der Bestellung Adressänderungen nicht unterstützt (dann stornieren und neu anlegen); 502 wenn die Übermittlung an den Hersteller scheitert (später erneut versuchen).\n\n**Nebenwirkung:** Eine für die Bestellung bereits ausgestellte Rechnung wird per Gutschrift storniert und bei der nächsten Übergabe an die Produktion neu ausgestellt. Antwort: die aktualisierte Bestellung.\n\nChanges the delivery address of an order. The new address replaces the old one completely; all required fields (`first_name`, `last_name`, `street`, `postcode`, `city`, `country`) must be sent. `POST /api/orders/{order_id}/delivery-address/preview` shows beforehand whether and how the change would apply.\n\n**When possible and what happens:**\n\n| State of the order | Effect |\n|---|---|\n| Status `CHECK`, nothing handed to the manufacturer | The address is saved, the order leaves `CHECK` and continues (routed again if needed). When billed via the wallet balance: if it does not cover the order, only the address is saved and the order stays on hold. |\n| Status `PAUSE`, nothing handed to the manufacturer | The address is saved, the order stays in `PAUSE`. |\n| Parts are handed to the manufacturer or in clarification | The change is sent to the manufacturer and saved only if accepted. If the manufacturer ships on a Shirtee-generated label that has not been created yet, the change stays with Shirtee and the label is built with the new address. |\n\nIf a part of the order is being handed over or already shipped, the change is no longer possible (400). Without parts handed to the manufacturer, any order status other than `CHECK`/`PAUSE` is 400 as well.\n\n**Errors:** 400 for an invalid address (problems in `detail`; for Packstations `extra.error` = `INVALID_PACKSTATION`) or a state that cannot be changed; 404 for a foreign or unknown order; 409 if the shipping label already exists, the manufacturer rejects the change, or the invoice already carries a credit note; 422 if the order's manufacturer does not support address changes (cancel and re-create instead); 502 if forwarding to the manufacturer fails (retry later).\n\n**Side effect:** An invoice already issued for the order is reversed by a credit note and re-issued at the next handover to production. Response: the updated order.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/delivery-address",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "delivery-address"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"first_name\": \"Max\",\n  \"last_name\": \"Mustermann\",\n  \"street\": \"Musterstraße\",\n  \"house_number\": \"1\",\n  \"postcode\": \"50667\",\n  \"city\": \"Köln\",\n  \"country\": \"DE\",\n  \"email\": \"max.mustermann@example.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Preview delivery address change",
          "request": {
            "method": "POST",
            "description": "Zeigt, was `PUT /api/orders/{order_id}/delivery-address` mit derselben Adresse jetzt bewirken würde. Es wird nichts gespeichert und nichts an einen Hersteller gesendet. Der Body ist identisch zur Änderung.\n\n**Antwort 200:** `is_editable` und `blockers` sagen, ob die Änderung jetzt möglich ist (Zustand der Bestellung, kein Fehler); `mode` sagt, wie sie angewendet würde (`null`, wenn sie blockiert ist); `address` ist die geprüfte Adresse; `fingerprint` und `confirmed_summary` kennzeichnen den Vorschaustand, siehe Schema. Die Vorschau prüft nicht, ob der Hersteller eine übermittelte Änderung annimmt; das zeigt sich erst bei der Änderung.\n\n**Antwort 422:** Die Anfrage selbst ist nicht vorschaubar. `extra.error` ist `INVALID_FIELDS` (mit `extra.fields`), `INVALID_DELIVERY_ADDRESS` (mit `extra.issues`: `HOUSE_NUMBER_MISSING`, `POSTCODE_INVALID`, `STREET_IMPLAUSIBLE`, `HOUSE_NUMBER_IMPLAUSIBLE`, `ADDRESS_UNVERIFIED`) oder `INVALID_PACKSTATION`. 404 bei fremder oder unbekannter Bestellung. Die REST-API verlangt die Vorschau nicht vor der Änderung.\n\nShows what `PUT /api/orders/{order_id}/delivery-address` would do now with the same address. Nothing is saved and nothing is sent to a manufacturer. The body is identical to the change.\n\n**Response 200:** `is_editable` and `blockers` say whether the change is possible now (state of the order, not an error); `mode` says how it would apply (`null` if blocked); `address` is the validated address; `fingerprint` and `confirmed_summary` identify the previewed state, see the schema. The preview does not tell whether the manufacturer accepts a forwarded change; that only shows when the change is made.\n\n**Response 422:** The request itself cannot be previewed. `extra.error` is `INVALID_FIELDS` (with `extra.fields`), `INVALID_DELIVERY_ADDRESS` (with `extra.issues`: `HOUSE_NUMBER_MISSING`, `POSTCODE_INVALID`, `STREET_IMPLAUSIBLE`, `HOUSE_NUMBER_IMPLAUSIBLE`, `ADDRESS_UNVERIFIED`) or `INVALID_PACKSTATION`. 404 for a foreign or unknown order. The REST API does not require the preview before the change.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/delivery-address/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "delivery-address",
                "preview"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"first_name\": \"Max\",\n  \"last_name\": \"Mustermann\",\n  \"street\": \"Musterstraße\",\n  \"house_number\": \"1\",\n  \"postcode\": \"50667\",\n  \"city\": \"Köln\",\n  \"country\": \"DE\",\n  \"email\": \"max.mustermann@example.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Upload a delivery receipt insert",
          "request": {
            "method": "POST",
            "description": "Lädt einen eigenen Lieferschein (Beileger) für eine Bestellung des Händlers hoch (Scope `merchant_order:create` oder `merchant_order:write`). Der Request ist `multipart/form-data` mit einer Datei im Feld `file`. Erlaubt sind PDF, PNG und JPEG bis 25 MiB; der Dateityp wird am Inhalt erkannt, nicht am Dateinamen oder Content-Type. Die Datei wird immer gespeichert. Ein zuvor hochgeladener Lieferschein derselben Bestellung wird dadurch ersetzt.\n\nDer Lieferschein wird nur bei der Übergabe an den Produktionspartner mitgegeben; es findet kein Aufruf beim Partner statt. Die Antwort nennt je Fertigungsauftrag, was passiert (`per_mo`): `WILL_ATTACH` (wird bei der Übergabe mitgegeben), `TOO_LATE` (bereits übergeben, wird nicht mehr mitgegeben) oder `UNSUPPORTED` (der Partner unterstützt keine Lieferscheine). `overall` fasst zusammen: `OK` (alle Fertigungsaufträge nehmen ihn mit), `PARTIAL` (nur ein Teil) oder `STORED_ONLY` (keiner, oder es gibt noch keinen Fertigungsauftrag). Eine fremde oder unbekannte Bestell-ID ergibt 404. Ein nicht erlaubter Dateityp ergibt 422, eine zu große Datei einen Fehler wegen der Request-Größe (413), ein gleichzeitiger zweiter Upload für dieselbe Bestellung 409.\n\nUploads an own delivery receipt (insert) for an order of the merchant (scope `merchant_order:create` or `merchant_order:write`). The request is `multipart/form-data` with one file in the field `file`. PDF, PNG and JPEG up to 25 MiB are allowed; the file type is detected from the content, not from the file name or content type. The file is always stored. A previously uploaded receipt of the same order is replaced by it.\n\nThe receipt is only handed over at transfer to the production partner; no call to the partner takes place now. The response states per manufacturing order what happens (`per_mo`): `WILL_ATTACH` (handed over at transfer), `TOO_LATE` (already transferred, no longer handed over) or `UNSUPPORTED` (the partner does not support delivery receipts). `overall` summarises: `OK` (all manufacturing orders take it along), `PARTIAL` (only some) or `STORED_ONLY` (none, or there is no manufacturing order yet). An order ID owned by someone else or unknown returns 404. A file type that is not allowed returns 422, a file that is too large a request-size error (413), a concurrent second upload for the same order 409.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/delivery-receipt",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "delivery-receipt"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Numerische ID der Bestellung, zu der der Lieferschein gehört. / Numeric ID of the order the delivery receipt belongs to."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Add an order item",
          "request": {
            "method": "POST",
            "description": "Fügt einer bestehenden Bestellung eine Position hinzu (Menge immer 1). Der Preis kommt aus dem Katalog; Preis-, Branding- und Integrationsfelder sowie Druckgeometrie sind für Händlerschlüssel nicht erlaubt (403, `detail` nennt die Felder).\n\nVoraussetzung: Die Bestellung ist noch änderbar. Das trifft zu, wenn der Bestellstatus `PENDING`, `READY`, `ERROR`, `CHECK` oder `PAUSE` ist, kein Teil der Bestellung gerade an die Produktion übergeben wird, bereits übergeben, angelegt, versendet oder storniert ist und keine automatische Motivprüfung (DTF) läuft. Andernfalls antwortet die Operation mit 409; `extra.blockers` nennt die Gründe (Werte von `EditabilityBlocker`).\n\n**Wirkung:** Bereits erzeugte Zuteilungen an Hersteller werden verworfen, die Verarbeitung der Bestellung wird angehalten und die Bestellung muss neu zugeteilt werden. Über die Händler-API gibt es dafür keinen Neustart-Aufruf; `PUT /api/orders/{order_id}/items/variants` stößt den Neustart selbst an. Eine aktive Rechnung blockiert das Hinzufügen (422).\n\n**Fehler:** 403 bei nicht erlaubten Feldern; 404 bei fremder oder unbekannter Bestellung; 409 bei nicht änderbarer Bestellung; 422 bei unbekanntem oder inaktivem `sku`, mehrdeutigem `sku`, bereits vergebener `external_id` oder `item_id` unter den aktiven Positionen, aktiver Rechnung; 400 bei ungültigem Body (z. B. doppelte `position` in `processings`, kein `https`-`design_url`, weder `design_url` noch `design_reference`); 503 wenn der Stand der Bestellung gerade nicht abgleichbar ist. Antwort 201: die neue Position.\n\nAdds an item to an existing order (quantity is always 1). The price comes from the catalog; price, branding and integration fields as well as print geometry are not allowed for merchant keys (403, `detail` names the fields).\n\nPrecondition: the order is still changeable. That holds when the order status is `PENDING`, `READY`, `ERROR`, `CHECK` or `PAUSE`, no part of the order is being handed to production, has been handed over, created, shipped or canceled, and no automatic motif check (DTF) is running. Otherwise the operation answers 409; `extra.blockers` lists the reasons (values of `EditabilityBlocker`).\n\n**Effect:** Manufacturer assignments already created are discarded, processing of the order is paused and the order must be routed again. The merchant API has no restart call for this; `PUT /api/orders/{order_id}/items/variants` triggers the restart itself. An active invoice blocks adding (422).\n\n**Errors:** 403 for disallowed fields; 404 for a foreign or unknown order; 409 if the order is not changeable; 422 for an unknown or inactive `sku`, an ambiguous `sku`, an `external_id` or `item_id` already used by an active item, or an active invoice; 400 for an invalid body (e.g. duplicate `position` in `processings`, a non-`https` `design_url`, neither `design_url` nor `design_reference`); 503 if the state of the order cannot be reconciled right now. Response 201: the new item.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/items",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "items"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sku\": \"TSHIRT-BLK-M\",\n  \"external_id\": \"POS-3\",\n  \"processings\": [\n    {\n      \"processing_type\": \"DTG\",\n      \"position\": \"FRONT\",\n      \"design_url\": \"https://designs.example.com/motiv-1.png\"\n    }\n  ],\n  \"reason\": \"Zusätzliche Position auf Kundenwunsch\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Preview variant swap",
          "request": {
            "method": "POST",
            "description": "Zeigt, was `PUT /api/orders/{order_id}/items/variants` mit denselben Positionen jetzt bewirken würde. Es wird nichts geschrieben. Der Body ist identisch zum Sammel-Tausch (`items` und optional `reason`).\n\n**Antwort 200:** Jede Position wird mit den Regeln des Sammel-Tauschs geprüft (nur Varianten desselben Produkts) und erscheint in `positions` mit alter und neuer Variante. `is_editable` und `blockers` zeigen, ob die Bestellung jetzt änderbar ist (Zustand der Bestellung, kein Fehler). Die Vorschau sperrt und gleicht nichts ab; Bestellungen mit fehlgeschlagener Übergabe können daher `MO_RECONCILE_UNAVAILABLE` melden, obwohl der Tausch nach dem Abgleich gelingt. `restart_follows` und `further_changes_blocked_after_restart` beschreiben die Folge: Nach dem Tausch folgt ein Neustart, danach sind keine weiteren Änderungen mehr möglich, sobald die Bestellung wieder zugeteilt ist.\n\n**Antwort 422:** `extra.error` nennt die Ursache mit `extra.item_id` der ersten abgelehnten Position: `INVALID_FIELDS`, `ITEM_NOT_FOUND`, `ITEM_INACTIVE`, `DUPLICATE_ITEM`, `VARIANT_NOT_FOUND`, `SAME_VARIANT`, `CROSS_PRODUCT_SWAP`, `VARIANT_INACTIVE` (mit `extra.alternative_variant_ids`, ggf. leer) oder `PLACEHOLDER_ITEM`. 404 bei fremder oder unbekannter Bestellung. Die REST-API verlangt die Vorschau nicht vor dem Tausch.\n\nShows what `PUT /api/orders/{order_id}/items/variants` would do now with the same positions. Nothing is written. The body is identical to the batch swap (`items` and optional `reason`).\n\n**Response 200:** Each position is checked with the rules of the batch swap (variants of the same product only) and appears in `positions` with old and new variant. `is_editable` and `blockers` show whether the order is changeable now (state of the order, not an error). The preview takes no lock and runs no reconciliation; orders with a failed handover can therefore report `MO_RECONCILE_UNAVAILABLE` although the swap succeeds after the reconciliation. `restart_follows` and `further_changes_blocked_after_restart` describe the consequence: a restart follows the swap, after which no further changes are possible once the order is routed again.\n\n**Response 422:** `extra.error` names the cause, with `extra.item_id` of the first rejected position: `INVALID_FIELDS`, `ITEM_NOT_FOUND`, `ITEM_INACTIVE`, `DUPLICATE_ITEM`, `VARIANT_NOT_FOUND`, `SAME_VARIANT`, `CROSS_PRODUCT_SWAP`, `VARIANT_INACTIVE` (with `extra.alternative_variant_ids`, possibly empty) or `PLACEHOLDER_ITEM`. 404 for a foreign or unknown order. The REST API does not require the preview before the swap.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/items/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "items",
                "preview"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"item_id\": 9001,\n      \"product_variant_id\": 503\n    },\n    {\n      \"item_id\": 9002,\n      \"product_variant_id\": 513\n    }\n  ],\n  \"reason\": \"Kunde wünscht Größe L\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Swap the variants of several order items",
          "request": {
            "method": "PUT",
            "description": "Tauscht die Varianten mehrerer Positionen in **einer** Transaktion gegen andere Varianten desselben Produkts und startet danach den Neustart der Bestellung. Scheitert eine Position, wird nichts geschrieben (alles oder nichts). Vorab zeigt `POST /api/orders/{order_id}/items/preview`, was der Tausch bewirken würde.\n\nVoraussetzung: Die Bestellung ist noch änderbar. Das trifft zu, wenn der Bestellstatus `PENDING`, `READY`, `ERROR`, `CHECK` oder `PAUSE` ist, kein Teil der Bestellung gerade an die Produktion übergeben wird, bereits übergeben, angelegt, versendet oder storniert ist und keine automatische Motivprüfung (DTF) läuft. Andernfalls antwortet die Operation mit 409; `extra.blockers` nennt die Gründe (Werte von `EditabilityBlocker`).\n\n**Neustart:** Der Tausch wird zuerst festgeschrieben, danach wird die Bestellung im selben Aufruf neu angestoßen. Verweigert der Neustart die Ausführung, bleibt der Tausch bestehen; die Antwort hat dann `restart_triggered: false`, `restart_required: true` sowie `restart_error` (Code) und `restart_detail` (Text). Mögliche Codes sind u. a. `NOT_RESTARTABLE`, `NO_ELIGIBLE_MANUFACTURER`, `ORDER_NOT_FOUND`, `CANCELLATION_PENDING`, `MO_DELETE_RESTRICTED`, `UNCONFIRMED_CANCEL`, `CREDIT_NOTE_MISSING`, `SUBSET_ITEMS_ALREADY_COVERED`, `UNCANCELED_DIRECT_DISPATCH`, `TESTLAUF_STRANDED`, `ACTIVE_REORDER_EXISTS`.\n\n**Fehler:** 404 bei fremder oder unbekannter Bestellung, Position oder Zielvariante (`extra.error` `ITEM_NOT_FOUND`/`VARIANT_NOT_FOUND`); 409 bei nicht änderbarer Bestellung (`extra.blockers`) oder gleichzeitiger Änderung; 410 bei entfernter Position (`ITEM_INACTIVE`); 422 bei `SAME_VARIANT`, `CROSS_PRODUCT_SWAP`, `VARIANT_INACTIVE`, `DUPLICATE_ITEM`, `PLACEHOLDER_ITEM` (jeweils `extra.error` und `extra.item_id`) oder aktiver Rechnung; 503 wenn der Stand der Bestellung gerade nicht abgleichbar ist. Antwort 200: die getauschten Positionen und der Neustart-Stand.\n\nSwaps the variants of several items for other variants of the same product in **one** transaction and then restarts the order. If one position fails, nothing is written (all or nothing). `POST /api/orders/{order_id}/items/preview` shows beforehand what the swap would do.\n\nPrecondition: the order is still changeable. That holds when the order status is `PENDING`, `READY`, `ERROR`, `CHECK` or `PAUSE`, no part of the order is being handed to production, has been handed over, created, shipped or canceled, and no automatic motif check (DTF) is running. Otherwise the operation answers 409; `extra.blockers` lists the reasons (values of `EditabilityBlocker`).\n\n**Restart:** The swap is committed first, then the order is restarted within the same call. If the restart refuses to run, the swap stands; the response then has `restart_triggered: false`, `restart_required: true`, plus `restart_error` (code) and `restart_detail` (text). Possible codes include `NOT_RESTARTABLE`, `NO_ELIGIBLE_MANUFACTURER`, `ORDER_NOT_FOUND`, `CANCELLATION_PENDING`, `MO_DELETE_RESTRICTED`, `UNCONFIRMED_CANCEL`, `CREDIT_NOTE_MISSING`, `SUBSET_ITEMS_ALREADY_COVERED`, `UNCANCELED_DIRECT_DISPATCH`, `TESTLAUF_STRANDED`, `ACTIVE_REORDER_EXISTS`.\n\n**Errors:** 404 for a foreign or unknown order, item or target variant (`extra.error` `ITEM_NOT_FOUND`/`VARIANT_NOT_FOUND`); 409 if the order is not changeable (`extra.blockers`) or on a concurrent change; 410 for a removed item (`ITEM_INACTIVE`); 422 for `SAME_VARIANT`, `CROSS_PRODUCT_SWAP`, `VARIANT_INACTIVE`, `DUPLICATE_ITEM`, `PLACEHOLDER_ITEM` (each with `extra.error` and `extra.item_id`) or an active invoice; 503 if the state of the order cannot be reconciled right now. Response 200: the swapped items and the restart state.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/items/variants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "items",
                "variants"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"item_id\": 9001,\n      \"product_variant_id\": 503\n    },\n    {\n      \"item_id\": 9002,\n      \"product_variant_id\": 513\n    }\n  ],\n  \"reason\": \"Kunde wünscht Größe L\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Swap the variant of an order item",
          "request": {
            "method": "PATCH",
            "description": "Tauscht die Variante (Farbe/Größe) einer einzelnen Position gegen eine andere Variante **desselben Produkts**. Für mehrere Positionen auf einmal gibt es `PUT /api/orders/{order_id}/items/variants`, das zusätzlich den Neustart auslöst. Die Position behält Motiv und Druckdaten; nur die Variante ändert sich.\n\nVoraussetzung: Die Bestellung ist noch änderbar. Das trifft zu, wenn der Bestellstatus `PENDING`, `READY`, `ERROR`, `CHECK` oder `PAUSE` ist, kein Teil der Bestellung gerade an die Produktion übergeben wird, bereits übergeben, angelegt, versendet oder storniert ist und keine automatische Motivprüfung (DTF) läuft. Andernfalls antwortet die Operation mit 409; `extra.blockers` nennt die Gründe (Werte von `EditabilityBlocker`).\n\n**Wirkung:** Bereits erzeugte Zuteilungen an Hersteller werden verworfen und die Verarbeitung der Bestellung wird angehalten; die Bestellung muss neu zugeteilt werden. Dieser Aufruf startet das nicht selbst (anders als der Sammel-Tausch); über die Händler-API gibt es dafür keinen Neustart-Aufruf. Eigene Mockups der Position werden ungültig.\n\n**Fehler:** 404 bei fremder oder unbekannter Bestellung, Position oder Zielvariante; 409 bei nicht änderbarer Bestellung oder wenn sich die Variante der Position gleichzeitig geändert hat; 410 bei bereits entfernter Position; 422, wenn die Zielvariante (oder ihr Produkt) inaktiv ist, der aktuellen Variante entspricht, zu einem anderen Produkt gehört oder eine aktive Rechnung die Änderung blockiert; 503 wenn der Stand der Bestellung gerade nicht abgleichbar ist. Antwort 200: die aktualisierte Position.\n\nSwaps the variant (color/size) of a single item for another variant **of the same product**. For several items at once use `PUT /api/orders/{order_id}/items/variants`, which also triggers the restart. The item keeps its motif and print data; only the variant changes.\n\nPrecondition: the order is still changeable. That holds when the order status is `PENDING`, `READY`, `ERROR`, `CHECK` or `PAUSE`, no part of the order is being handed to production, has been handed over, created, shipped or canceled, and no automatic motif check (DTF) is running. Otherwise the operation answers 409; `extra.blockers` lists the reasons (values of `EditabilityBlocker`).\n\n**Effect:** Manufacturer assignments already created are discarded and processing of the order is paused; the order must be routed again. This call does not start that itself (unlike the batch swap); the merchant API has no restart call for it. Own mockups of the item become invalid.\n\n**Errors:** 404 for a foreign or unknown order, item or target variant; 409 if the order is not changeable or the item's variant changed concurrently; 410 for an already removed item; 422 if the target variant (or its product) is inactive, equals the current one, belongs to another product, or an active invoice blocks the change; 503 if the state of the order cannot be reconciled right now. Response 200: the updated item.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/items/:item_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "items",
                ":item_id"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                },
                {
                  "key": "item_id",
                  "value": "",
                  "description": "Positions-ID (`id` aus der Bestellung, ganze Zahl) / Item ID (`id` of the order item, integer). Eine Position, die nicht zur Bestellung gehört, antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_id\": 503,\n  \"reason\": \"Kunde wünscht Größe L\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Remove an order item",
          "request": {
            "method": "DELETE",
            "description": "Entfernt eine Position aus der Bestellung. Die Position wird deaktiviert, nicht physisch gelöscht; sie erscheint danach nicht mehr als aktive Position. Der Body ist Pflicht (`reason`).\n\nVoraussetzung: Die Bestellung ist noch änderbar. Das trifft zu, wenn der Bestellstatus `PENDING`, `READY`, `ERROR`, `CHECK` oder `PAUSE` ist, kein Teil der Bestellung gerade an die Produktion übergeben wird, bereits übergeben, angelegt, versendet oder storniert ist und keine automatische Motivprüfung (DTF) läuft. Andernfalls antwortet die Operation mit 409; `extra.blockers` nennt die Gründe (Werte von `EditabilityBlocker`).\n\n**Wirkung:** Bereits erzeugte Zuteilungen an Hersteller werden verworfen und die Verarbeitung der Bestellung wird angehalten; sie muss neu zugeteilt werden (über die Händler-API gibt es dafür keinen Neustart-Aufruf). Ob und wie der Betrag der Position auf dem Guthaben berücksichtigt wird, hängt vom Grund ab; `apply_wallet_correction` darf ein Händlerschlüssel nicht setzen (403).\n\n**Fehler:** 404 bei fremder oder unbekannter Bestellung oder Position; 409 bei nicht änderbarer Bestellung; 410 wenn die Position bereits entfernt ist (ein wiederholter Aufruf ist damit erkennbar); 422, wenn es die letzte aktive Position ist (stattdessen die Bestellung stornieren) oder eine aktive Rechnung die Änderung blockiert; 503 wenn der Stand der Bestellung gerade nicht abgleichbar ist. Antwort 204 ohne Body.\n\nRemoves an item from the order. The item is deactivated, not physically deleted; it no longer appears as an active item. The body is required (`reason`).\n\nPrecondition: the order is still changeable. That holds when the order status is `PENDING`, `READY`, `ERROR`, `CHECK` or `PAUSE`, no part of the order is being handed to production, has been handed over, created, shipped or canceled, and no automatic motif check (DTF) is running. Otherwise the operation answers 409; `extra.blockers` lists the reasons (values of `EditabilityBlocker`).\n\n**Effect:** Manufacturer assignments already created are discarded and processing of the order is paused; the order must be routed again (the merchant API has no restart call for this). Whether and how the item amount is reflected on the wallet depends on the reason; a merchant key must not set `apply_wallet_correction` (403).\n\n**Errors:** 404 for a foreign or unknown order or item; 409 if the order is not changeable; 410 if the item is already removed (so a repeated call is recognizable); 422 if it is the last active item (cancel the order instead) or an active invoice blocks the change; 503 if the state of the order cannot be reconciled right now. Response 204 without a body.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/items/:item_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "items",
                ":item_id"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Bestell-ID (`id` der Bestellung, ganze Zahl) / Order ID (`id` of the order, integer). Eine fremde oder unbekannte Bestellung antwortet mit 404."
                },
                {
                  "key": "item_id",
                  "value": "",
                  "description": "Positions-ID (`id` aus der Bestellung, ganze Zahl) / Item ID (`id` of the order item, integer). Eine Position, die nicht zur Bestellung gehört, antwortet mit 404."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"CUSTOMER_CANCELLATION\",\n  \"note\": \"Endkunde hat die Position abbestellt\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Reorder an existing order",
          "request": {
            "method": "POST",
            "description": "Legt aus einer bestehenden Bestellung des Händlers eine neue Bestellung an (Scope `merchant_order:create` oder `merchant_order:write`). Die neue Bestellung übernimmt Positionen, Veredelungen, Adressen und Preise der Quellbestellung und durchläuft die normale Anlage mit neuer Zuweisung. Ihre `external_reference` erhält das Präfix `RE-` (bei weiteren Nachbestellungen `RE2-`, `RE3-` …), `reordered_from_order_id` verweist auf die Quelle. Für Händler gilt immer `pricing=original`: Die Preise der Quelle werden 1:1 übernommen und der Betrag wird dem Guthaben des Händlers belastet; der Parameter `pricing` wird ignoriert. Der Body ist optional; wirksam ist nur `item_ids`, um nur einen Teil der Positionen nachzubestellen. `variant_overrides`, `delivery_address` und `allow_open_source` sind für Händler nicht erlaubt und ergeben 403.\n\nEine Bestellung mit noch laufender Produktion kann nicht nachbestellt werden: Hat sie einen Fertigungsauftrag, der weder versendet noch storniert ist, antwortet die API mit 409 und `extra.error=SOURCE_STILL_IN_PRODUCTION` sowie `extra.open_manufacturing_order_count`; es wurde nichts angelegt, ein Wiederholen hilft erst nach Ende oder Storno der Produktion. Bei einer Teil-Nachbestellung blockieren nur offene Fertigungsaufträge, die eine gewählte Position enthalten. Ein 409 ohne `extra.error` ist ein kurzzeitiger Konflikt bei der Referenzvergabe und kann ohne Änderung wiederholt werden. 400 mit `extra.error` heißt, dass sich die Bestellung nicht nachbilden lässt (`SOURCE_VARIANT_UNAVAILABLE`, `SKU_NOT_FOUND`, `PROCESSING_TYPE_NOT_FOUND`, `INVALID_DELIVERY_ADDRESS` mit `extra.issues`, `ITEM_NOT_IN_ORDER`, `NO_LINE_ITEMS`); ein Wiederholen ohne Änderung hilft nicht. Eine fremde oder unbekannte Quell-ID ergibt 404.\n\nMit dem optionalen Header `Idempotency-Key` ist der Aufruf wiederholbar: Derselbe Schlüssel für dieselbe Quellbestellung liefert die erste Nachbestellung erneut (201, gleicher Inhalt) und belastet das Guthaben kein zweites Mal. `pricing` und `item_ids` einer Wiederholung werden nicht mit dem ersten Aufruf verglichen; die erste Nachbestellung gilt.\n\nCreates a new order from an existing order of the merchant (scope `merchant_order:create` or `merchant_order:write`). The new order takes over items, processings, addresses and prices of the source order and goes through normal creation with a fresh assignment. Its `external_reference` gets the prefix `RE-` (`RE2-`, `RE3-` … for further re-orders), and `reordered_from_order_id` points to the source. For merchants `pricing=original` always applies: the source prices are taken over 1:1 and the amount is charged to the merchant balance; the `pricing` parameter is ignored. The body is optional; only `item_ids` takes effect, to re-order just part of the items. `variant_overrides`, `delivery_address` and `allow_open_source` are not allowed for merchants and return 403.\n\nAn order whose production is still running cannot be re-ordered: if it has a manufacturing order that is neither shipped nor canceled, the API responds with 409 and `extra.error=SOURCE_STILL_IN_PRODUCTION` plus `extra.open_manufacturing_order_count`; nothing was created, and retrying only helps once production has ended or been canceled. For a partial re-order, only open manufacturing orders containing a selected item block. A 409 without `extra.error` is a short-lived conflict while assigning the reference and can be retried unchanged. 400 with `extra.error` means the order cannot be reconstructed (`SOURCE_VARIANT_UNAVAILABLE`, `SKU_NOT_FOUND`, `PROCESSING_TYPE_NOT_FOUND`, `INVALID_DELIVERY_ADDRESS` with `extra.issues`, `ITEM_NOT_IN_ORDER`, `NO_LINE_ITEMS`); retrying unchanged does not help. A source ID owned by someone else or unknown returns 404.\n\nWith the optional `Idempotency-Key` header the call is repeatable: the same key for the same source order returns the first re-order again (201, same content) and does not charge the balance a second time. `pricing` and `item_ids` of a repeat are not compared with the first call; the first re-order wins.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/orders/:order_id/reorder",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "orders",
                ":order_id",
                "reorder"
              ],
              "variable": [
                {
                  "key": "order_id",
                  "value": "",
                  "description": "Numerische ID der Quellbestellung, die nachbestellt wird. / Numeric ID of the source order to re-order."
                }
              ],
              "query": [
                {
                  "key": "pricing",
                  "value": "",
                  "disabled": true,
                  "description": "Preismodus. Für Händler wird immer `original` angewendet; abweichende Werte werden ignoriert. / Pricing mode. For merchants `original` always applies; other values are ignored."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"item_ids\": [\n    900101\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Product Colors",
      "item": [
        {
          "name": "List product colors",
          "request": {
            "method": "GET",
            "description": "Liefert alle Farben des Katalogs. Das ist eine globale Nachschlageliste ohne Paginierung und ohne Filter, unabhängig vom Händler. Eine Variante verweist über `color_id` auf einen Eintrag; die Liste dient dazu, Farbfelder mit Anzeigename und Hex-Wert darzustellen. Die Anzeigebezeichnung einer Variante steht bereits in `color_label`. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns all colors of the catalog. This is a global lookup list without pagination or filters, independent of the merchant. A variant references an entry through `color_id`; use the list to render color swatches with display name and hex value. The display label of a variant is already in `color_label`. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-colors",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-colors"
              ],
              "variable": [],
              "query": []
            }
          }
        },
        {
          "name": "Get a product color",
          "request": {
            "method": "GET",
            "description": "Liefert eine einzelne Farbe anhand ihrer ID, zum Beispiel die `color_id` einer Variante. Die Farben sind global und nicht an einen Händler gebunden. Eine unbekannte ID ergibt 404. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns a single color by ID, for example the `color_id` of a variant. Colors are global and not bound to a merchant. An unknown ID yields 404. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-colors/:color_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-colors",
                ":color_id"
              ],
              "variable": [
                {
                  "key": "color_id",
                  "value": "",
                  "description": "ID der Farbe (Feld `color_id` einer Variante bzw. `id` in der Farbliste); unbekannte IDs ergeben 404 / Color ID (`color_id` of a variant, `id` in the color list); unknown IDs return 404."
                }
              ],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Product Sizes",
      "item": [
        {
          "name": "List product sizes",
          "request": {
            "method": "GET",
            "description": "Liefert alle Größen des Katalogs. Das ist eine globale Nachschlageliste ohne Paginierung und ohne Filter, unabhängig vom Händler. Eine Variante verweist über `size_id` auf einen Eintrag. Die Anzeigebezeichnung einer Variante steht bereits in `size_label`. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns all sizes of the catalog. This is a global lookup list without pagination or filters, independent of the merchant. A variant references an entry through `size_id`. The display label of a variant is already in `size_label`. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-sizes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-sizes"
              ],
              "variable": [],
              "query": []
            }
          }
        },
        {
          "name": "Get a product size",
          "request": {
            "method": "GET",
            "description": "Liefert eine einzelne Größe anhand ihrer ID, zum Beispiel die `size_id` einer Variante. Die Größen sind global und nicht an einen Händler gebunden. Eine unbekannte ID ergibt 404. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns a single size by ID, for example the `size_id` of a variant. Sizes are global and not bound to a merchant. An unknown ID yields 404. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-sizes/:size_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-sizes",
                ":size_id"
              ],
              "variable": [
                {
                  "key": "size_id",
                  "value": "",
                  "description": "ID der Größe (Feld `size_id` einer Variante bzw. `id` in der Größenliste); unbekannte IDs ergeben 404 / Size ID (`size_id` of a variant, `id` in the size list); unknown IDs return 404."
                }
              ],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Product Variants",
      "item": [
        {
          "name": "List product variants",
          "request": {
            "method": "GET",
            "description": "Liefert Produktvarianten als einfache Liste: pro Eintrag eine bestellbare Einheit mit eindeutiger `sku`, Farbe (`color_id`, `color_label`) und Größe (`size_id`, `size_label`). Die Liste ist nach `sku` aufsteigend sortiert und wird mit `limit` (Standard 100) und `offset` (Standard 0) paginiert; sie liefert keine Gesamtzahl. Für Suche, Verfügbarkeitsfilter, Sortierung nach Produkt und eine Gesamtzahl ist `GET /api/product-variants/paged` gedacht; diese Liste eignet sich, um alle Varianten eines Produkts (`product_id`) oder einer Fertigungsart (`fulfillment_type`) abzuholen.\n\nSichtbar sind Varianten eigener Produkte und der gemeinsamen, keinem Händler zugeordneten `ON_DEMAND`-Produkte. Eine fremde `product_id` liefert eine leere Liste, kein 404. Inaktive Varianten sind enthalten (Feld `status`). Ohne `stock=true` bleiben die Bestandsfelder (`stock_total`, `gross_stock`, `reserved`, `available`, `stock_display`) leer; mit `stock=true` werden sie berechnet. `available` ist die verfügbare Menge: Gesamtbestand minus Reservierung, nie negativ; `stock_display` gibt an, ob die Variante ab Lager lieferbar (`IN_STOCK`) oder Auftragsware (`JIT`) ist. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns product variants as a plain list: each entry is one orderable unit with a unique `sku`, color (`color_id`, `color_label`) and size (`size_id`, `size_label`). The list is sorted by `sku` ascending and paginated with `limit` (default 100) and `offset` (default 0); it returns no total count. For search, availability filter, sorting by product and a total count use `GET /api/product-variants/paged`; this list suits fetching all variants of one product (`product_id`) or one fulfillment type (`fulfillment_type`).\n\nVisible are variants of own products and of the shared `ON_DEMAND` products that belong to no merchant. A foreign `product_id` yields an empty list, not 404. Inactive variants are included (field `status`). Without `stock=true` the stock fields (`stock_total`, `gross_stock`, `reserved`, `available`, `stock_display`) stay empty; with `stock=true` they are computed. `available` is the available quantity: total stock minus reservations, never negative; `stock_display` tells whether the variant is deliverable from stock (`IN_STOCK`) or made to order (`JIT`). A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-variants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-variants"
              ],
              "variable": [],
              "query": [
                {
                  "key": "product_id",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Varianten dieses Produkts (ID aus `GET /api/products`); ohne Angabe alle sichtbaren Produkte / Only variants of this product (ID from `GET /api/products`); omitted means all visible products."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Maximale Anzahl Einträge pro Antwort, Standard 100; die API begrenzt den Wert nicht / Maximum number of entries per response, default 100; the API does not clamp the value."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl zu überspringender Einträge, Standard 0; für die nächste Seite um `limit` erhöhen / Number of entries to skip, default 0; increase by `limit` for the next page."
                },
                {
                  "key": "stock",
                  "value": "",
                  "disabled": true,
                  "description": "Bei `true` werden Bestandsfelder (`stock_total`, `gross_stock`, `reserved`, `available`, `stock_display`) berechnet, Standard `false` / When `true`, the stock fields are computed; default `false`."
                },
                {
                  "key": "manufacturer_stock",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händlerschlüssel; Herstellerbestände werden nicht ausgeliefert / No effect for merchant keys; manufacturer stock is never returned."
                },
                {
                  "key": "fulfillment_type",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Varianten von Produkten dieser Fertigungsart (`ON_DEMAND`, `WAREHOUSE`, `ADDITIONAL_PRODUCT`); ohne Angabe alle / Only variants of products of this fulfillment type; omitted means all types."
                }
              ]
            }
          }
        },
        {
          "name": "Get a product variant by SKU",
          "request": {
            "method": "GET",
            "description": "Löst eine SKU in die zugehörige Variante auf, etwa um aus einer Bestellposition oder einer externen Artikelnummer die `product_id` und die Variante zu ermitteln. Die SKU wird ohne Beachtung der Groß-/Kleinschreibung verglichen (`MUSTER-TS-01_NAVY_M` findet `MUSTER-TS-01_Navy_M`). Gesucht wird nur unter sichtbaren Produkten (eigene und gemeinsame `ON_DEMAND`-Produkte). Eine unbekannte oder fremde SKU ergibt 404, ohne zu verraten, ob sie existiert. Gesucht wird nach der `sku`, nicht nach `brand_sku` oder EAN; dafür dient `GET /api/product-variants/paged`.\n\nDie Antwort hat die Form der Variante wie bei `GET /api/product-variants/{variant_id}`, enthält aber keine Bestandszahlen; diese liefern die ID-Abfrage oder die Listen mit `stock=true`. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nResolves a SKU to the matching variant, for example to determine the `product_id` and the variant from an order line or an external article number. The SKU is compared case-insensitively (`MUSTER-TS-01_NAVY_M` finds `MUSTER-TS-01_Navy_M`). Only visible products are searched (own and shared `ON_DEMAND` products). An unknown or foreign SKU yields 404 without revealing whether it exists. The lookup matches the `sku` only, not `brand_sku` or EAN; use `GET /api/product-variants/paged` for those.\n\nThe response has the variant shape of `GET /api/product-variants/{variant_id}` but contains no stock figures; the lookup by ID or the lists with `stock=true` return them. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-variants/by-sku/:sku",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-variants",
                "by-sku",
                ":sku"
              ],
              "variable": [
                {
                  "key": "sku",
                  "value": "",
                  "description": "SKU der Variante (Feld `sku`), ohne Beachtung der Groß-/Kleinschreibung; unbekannte oder fremde SKUs ergeben 404 / Variant SKU (field `sku`), case-insensitive; unknown or foreign SKUs return 404."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "List product variants (paged)",
          "request": {
            "method": "GET",
            "description": "Liefert Produktvarianten seitenweise im Umschlag `{items, limit, offset, total_count}`. Das ist die Operation für Katalogansichten, Artikelsuche und Abgleiche: Sie bietet gegenüber `GET /api/product-variants` zusätzlich die Filter `search`, `availability` und `ean`, die Sortierung `sort`, die Gesamtzahl `total_count` und liefert je Eintrag `product_name`. Sichtbarkeit, Filter `product_id`/`fulfillment_type` und Bestandsfelder (`stock=true`) sind identisch zur Liste.\n\n**Sortierung:** nach Produkt (Standard: Produktname aufsteigend, mit `sort=created_desc` das zuletzt angelegte Produkt zuerst), innerhalb eines Produkts nach Farbbezeichnung, dann nach Größe in Tragereihenfolge (S, M, L, XL …; Buchstabengrößen zuerst, dann Größen mit führender Zahl, dann die übrigen), zuletzt nach `sku`. Die Varianten eines Produkts stehen immer in einem Block, und die Reihenfolge ist eindeutig, sodass `offset`-Paginierung keine Zeile auslässt oder wiederholt.\n\n**Paginierung:** `limit` Standard 20, wird auf 1 bis 100 begrenzt; `offset` mindestens 0. Die Antwort nennt die tatsächlich verwendeten (begrenzten) Werte. `total_count` zählt alle Treffer der Filter.\n\n**Filter:** `search` sucht ohne Beachtung der Groß-/Kleinschreibung als Teilstring in Produktname, `sku`, `brand_sku` und Farbbezeichnung (leer = kein Filter). `availability` filtert auf `AVAILABLE` (`available` größer 0) oder `SOLD_OUT` (`available` nicht größer 0). `ean` ist ein exakter Treffer (umgebende Leerzeichen werden entfernt) und findet nur Varianten eigener Produkte. `brand_sku` und `ean` stehen in der Antwort nur bei eigenen Produkten. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns product variants page by page in the envelope `{items, limit, offset, total_count}`. This is the operation for catalog views, article search and reconciliation: compared to `GET /api/product-variants` it adds the filters `search`, `availability` and `ean`, the sort option `sort`, the total count `total_count`, and returns `product_name` on every entry. Visibility, the `product_id`/`fulfillment_type` filters and the stock fields (`stock=true`) are identical to the list.\n\n**Sorting:** by product (default: product name ascending; with `sort=created_desc` the most recently created product first), within a product by color label, then by size in wearing order (S, M, L, XL ...; letter sizes first, then sizes with a leading number, then the rest), finally by `sku`. The variants of one product always form one block, and the order is total, so `offset` pagination never skips or repeats a row.\n\n**Pagination:** `limit` defaults to 20 and is clamped to 1 to 100; `offset` is at least 0. The response states the values actually used (clamped). `total_count` counts all matches of the filters.\n\n**Filters:** `search` is a case-insensitive substring match on product name, `sku`, `brand_sku` and color label (blank = no filter). `availability` filters on `AVAILABLE` (`available` greater than 0) or `SOLD_OUT` (`available` not greater than 0). `ean` is an exact match (surrounding blanks are ignored) and finds only variants of own products. `brand_sku` and `ean` appear in the response only for own products. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-variants/paged",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-variants",
                "paged"
              ],
              "variable": [],
              "query": [
                {
                  "key": "product_id",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Varianten dieses Produkts (ID aus `GET /api/products`); ohne Angabe alle sichtbaren Produkte / Only variants of this product (ID from `GET /api/products`); omitted means all visible products."
                },
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Einträge pro Seite, Standard 20, wird auf 1 bis 100 begrenzt / Entries per page, default 20, clamped to 1 to 100."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl zu überspringender Einträge, Standard 0, negative Werte gelten als 0 / Number of entries to skip, default 0, negative values count as 0."
                },
                {
                  "key": "stock",
                  "value": "",
                  "disabled": true,
                  "description": "Bei `true` werden Bestandsfelder (`stock_total`, `gross_stock`, `reserved`, `available`, `stock_display`) berechnet, Standard `false`; die Filter wirken unabhängig davon / When `true`, the stock fields are computed, default `false`; the filters work independently of it."
                },
                {
                  "key": "manufacturer_stock",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händlerschlüssel; Herstellerbestände werden nicht ausgeliefert / No effect for merchant keys; manufacturer stock is never returned."
                },
                {
                  "key": "fulfillment_type",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Varianten von Produkten dieser Fertigungsart (`ON_DEMAND`, `WAREHOUSE`, `ADDITIONAL_PRODUCT`); ohne Angabe alle / Only variants of products of this fulfillment type; omitted means all types."
                },
                {
                  "key": "search",
                  "value": "",
                  "disabled": true,
                  "description": "Suchtext, Teilstring ohne Beachtung der Groß-/Kleinschreibung in Produktname, `sku`, `brand_sku` und Farbbezeichnung; leer oder fehlend = kein Filter / Search text, case-insensitive substring of product name, `sku`, `brand_sku` and color label; blank or omitted means no filter."
                },
                {
                  "key": "availability",
                  "value": "",
                  "disabled": true,
                  "description": "`AVAILABLE` = verfügbare Menge größer 0, `SOLD_OUT` = nicht größer 0; ohne Angabe beides / `AVAILABLE` = available quantity greater than 0, `SOLD_OUT` = not greater than 0; omitted means both."
                },
                {
                  "key": "customer_id",
                  "value": "",
                  "disabled": true,
                  "description": "Ohne Wirkung für Händlerschlüssel, der Händler ergibt sich aus dem Schlüssel; Wert mindestens 1 / No effect for merchant keys, the merchant is derived from the key; value at least 1."
                },
                {
                  "key": "ean",
                  "value": "",
                  "disabled": true,
                  "description": "Exakter Treffer auf die EAN der Variante, 1 bis 100 Zeichen, umgebende Leerzeichen werden ignoriert; findet nur Varianten eigener Produkte / Exact match on the variant EAN, 1 to 100 characters, surrounding blanks ignored; finds only variants of own products."
                },
                {
                  "key": "sort",
                  "value": "",
                  "disabled": true,
                  "description": "Reihenfolge der Produkte: `name_asc` (Standard) nach Produktname, `created_desc` zuletzt angelegtes Produkt zuerst / Order of products: `name_asc` (default) by product name, `created_desc` most recently created product first."
                }
              ]
            }
          }
        },
        {
          "name": "Get a product variant",
          "request": {
            "method": "GET",
            "description": "Liefert eine einzelne Variante anhand ihrer ID. Die Variante muss zu einem sichtbaren Produkt gehören (eigenes oder gemeinsames, keinem Händler zugeordnetes `ON_DEMAND`-Produkt); sonst antwortet die API mit 404, ohne zu verraten, ob die ID existiert. Eine Variante ist eindeutig durch ihre `sku` bestimmt; Farbe und Größe stehen in `color_id`/`color_label` und `size_id`/`size_label`.\n\nDie Bestandszahlen `gross_stock` (Gesamtbestand), `reserved` (Reservierung) und `available` (verfügbare Menge, nie negativ) sind hier immer gefüllt, ohne `stock`-Parameter; sie sind `null`, wenn für die Variante keine Bestandsdaten vorliegen. `stock_total` und `stock_display` liefert diese Operation nicht, sie stehen nur in den Listen mit `stock=true`. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns a single variant by ID. The variant must belong to a visible product (own product or shared `ON_DEMAND` product that belongs to no merchant); otherwise the API answers 404 without revealing whether the ID exists. A variant is uniquely identified by its `sku`; color and size are in `color_id`/`color_label` and `size_id`/`size_label`.\n\nThe stock figures `gross_stock` (total stock), `reserved` (reservations) and `available` (available quantity, never negative) are always filled here, without a `stock` parameter; they are `null` when no stock data exists for the variant. This operation does not return `stock_total` and `stock_display`; they appear only in the lists with `stock=true`. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/product-variants/:variant_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "product-variants",
                ":variant_id"
              ],
              "variable": [
                {
                  "key": "variant_id",
                  "value": "",
                  "description": "ID der Variante aus den Variantenlisten (Feld `id`); unbekannte oder fremde IDs ergeben 404 / Variant ID from the variant lists (field `id`); unknown or foreign IDs return 404."
                }
              ],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Products",
      "item": [
        {
          "name": "List products",
          "request": {
            "method": "GET",
            "description": "Liefert alle Produkte, die der Händler sehen darf, als einfache Liste ohne Paginierung und ohne Filter, aufsteigend nach Produktname sortiert. Sichtbar sind die eigenen Produkte des Händlers sowie die gemeinsamen Produkte der Auftragsware (`ON_DEMAND`), die keinem Händler gehören. Lagerartikel (`WAREHOUSE`) anderer Händler und frei verfügbare Lagerartikel erscheinen nie. Es gibt keinen Statusfilter: aktive und inaktive Produkte sind enthalten, der Zustand steht im Feld `status`.\n\nEin Produkt ist die Hülle über seinen Varianten; die bestellbaren Einheiten (Farbe, Größe, SKU) liefern `GET /api/product-variants` und `GET /api/product-variants/paged`. Das Feld `image_url` ist das repräsentative Katalogbild, aus dem Bild einer Variante abgeleitet; es ist `null`, wenn keine Variante ein Bild hat. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns every product the merchant may see as a plain list without pagination or filters, sorted by product name ascending. Visible are the merchant's own products and the shared made-to-order products (`ON_DEMAND`) that belong to no merchant. Warehouse articles (`WAREHOUSE`) of other merchants and unowned warehouse articles never appear. There is no status filter: active and inactive products are included, see the `status` field.\n\nA product is the container for its variants; the orderable units (color, size, SKU) are returned by `GET /api/product-variants` and `GET /api/product-variants/paged`. `image_url` is the representative catalog image derived from a variant image; it is `null` when no variant has an image. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products"
              ],
              "variable": [],
              "query": []
            }
          }
        },
        {
          "name": "Newest and best-selling public catalog products",
          "request": {
            "method": "GET",
            "description": "Liefert die Kacheln für die Startseite eines Händlers: bis zu vier neueste und bis zu vier meistbestellte Produkte aus dem gemeinsamen Katalog der Auftragsware. Berücksichtigt werden nur aktive `ON_DEMAND`-Produkte ohne Händlerzuordnung, nie das Produkt eines anderen Händlers. `newest` ist nach Anlagedatum absteigend sortiert. `bestsellers` ist nach Rang aufsteigend sortiert (Rang 1 = am häufigsten bestellt); gezählt werden die Händler, die das Produkt in den letzten 30 Tagen bestellt haben, und nur Produkte mit mindestens drei verschiedenen Händlern erscheinen. Die Antwort nennt nur den Rang, keine Mengen. Daher kann `bestsellers` weniger als vier oder keine Einträge enthalten.\n\nDie Antwort ist für alle Händler gleich und wird je Kalendertag (Zeitzone Europe/Berlin) bis zu eine Stunde zwischengespeichert; neue Produkte können dadurch verzögert erscheinen. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns the tiles for a merchant's home page: up to four newest and up to four best-selling products from the shared catalog of made-to-order products. Only active `ON_DEMAND` products without an owner are considered, never another merchant's product. `newest` is sorted by creation date, newest first. `bestsellers` is sorted by rank ascending (rank 1 = ordered most often); it counts the merchants that ordered the product in the last 30 days, and only products with at least three distinct merchants appear. The response carries the rank only, no quantities. Therefore `bestsellers` may hold fewer than four entries or none.\n\nThe response is identical for all merchants and cached for up to one hour per calendar day (time zone Europe/Berlin); new products may therefore appear with a delay. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products/highlights",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products",
                "highlights"
              ],
              "variable": [],
              "query": []
            }
          }
        },
        {
          "name": "Get a product",
          "request": {
            "method": "GET",
            "description": "Liefert ein einzelnes Produkt anhand seiner ID. Das Produkt muss für den Händler sichtbar sein (eigenes Produkt oder gemeinsames, keinem Händler zugeordnetes `ON_DEMAND`-Produkt, siehe `GET /api/products`). Ist die ID unbekannt oder gehört das Produkt einem anderen Händler, antwortet die API mit 404; die beiden Fälle sind absichtlich nicht unterscheidbar.\n\nDie Antwort entspricht einem Eintrag von `GET /api/products`, einschließlich `image_url` und `images`. Die Varianten des Produkts liefert `GET /api/product-variants?product_id=<id>`. Ein Schlüssel ohne den erforderlichen Scope erhält 403, ein ungültiger Aufruf (z. B. Parameter mit falschem Typ) 400.\n\nReturns a single product by ID. The product must be visible to the merchant (own product or a shared `ON_DEMAND` product that belongs to no merchant, see `GET /api/products`). If the ID is unknown or the product belongs to another merchant, the API answers 404; the two cases are deliberately indistinguishable.\n\nThe response matches one entry of `GET /api/products`, including `image_url` and `images`. The variants of the product are returned by `GET /api/product-variants?product_id=<id>`. A key without the required scope receives 403; an invalid call (for example a parameter of the wrong type) receives 400.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/products/:product_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "products",
                ":product_id"
              ],
              "variable": [
                {
                  "key": "product_id",
                  "value": "",
                  "description": "ID des Produkts aus `GET /api/products` (Feld `id`); unbekannte oder fremde IDs ergeben 404 / Product ID from `GET /api/products` (field `id`); unknown or foreign IDs return 404."
                }
              ],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Warehouse Products",
      "item": [
        {
          "name": "List own warehouse products",
          "request": {
            "method": "GET",
            "description": "Listet die eigenen Lagerartikel des Händlers mit Varianten und Bestand, seitenweise.\n\nOhne Filter erscheinen die Artikel, die über die Händler-Anlage (`POST /api/warehouse/products`) entstanden sind, neueste zuerst (`sort=created_desc`). Mit `include_unreviewed=true` kommen eigene Lagerartikel ohne Prüfstatus hinzu (`review_status` ist dann `null`, `customer_can_edit` `false`). Andere Händler sind nie sichtbar. `total_count` ist die Zahl aller Treffer nach Filtern, unabhängig von `limit` und `offset`. Pro Variante enthält die Liste `gross_stock` und `available`; ist ein Sicherheitsbestand wirksam, kommen `available_unbuffered` und `safety_stock` hinzu.\n\n**Prüfstatus (`review_status`):** `DRAFT` (Entwurf) -> `IN_REVIEW` (eingereicht) -> `APPROVED` (freigegeben) oder `DECLINED` (abgelehnt, mit Begründung in `review_note`). Ein abgelehnter Artikel wird durch eine Änderung per `PUT` wieder zum `DRAFT` und muss erneut eingereicht werden.\n\nErfordert den Scope `warehouse:read`. Fehler: 400 bei ungültigen Parametern, 403 bei fehlendem Scope oder Token ohne Kundenbezug, 404 wenn die Funktion für Händler nicht freigeschaltet ist.\n\nLists the merchant's own warehouse products with variants and stock, paged.\n\nWithout filters the list contains the products created through the merchant flow (`POST /api/warehouse/products`), newest first (`sort=created_desc`). With `include_unreviewed=true` own warehouse products without a review status are added (`review_status` is `null`, `customer_can_edit` is `false`). Other merchants are never visible. `total_count` is the number of all matches after filtering, independent of `limit` and `offset`. Each variant carries `gross_stock` and `available`; if a safety stock applies, `available_unbuffered` and `safety_stock` are added.\n\n**Review status (`review_status`):** `DRAFT` -> `IN_REVIEW` (submitted) -> `APPROVED` or `DECLINED` (with the reason in `review_note`). Changing a declined product with `PUT` turns it back into `DRAFT`; it must be submitted again.\n\nRequires the scope `warehouse:read`. Errors: 400 for invalid parameters, 403 for a missing scope or a token without a customer, 404 if the feature is not enabled for merchants.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "products"
              ],
              "variable": [],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl Artikel pro Seite, 1 bis 100, Standard 20 / Number of products per page, 1 to 100, default 20."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl zu überspringender Artikel, mindestens 0, Standard 0 / Number of products to skip, minimum 0, default 0."
                },
                {
                  "key": "review_status",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Artikel mit diesem Prüfstatus: `DRAFT`, `IN_REVIEW`, `APPROVED` oder `DECLINED`. Ohne Angabe kein Filter / Only products with this review status. Not set: no filter."
                },
                {
                  "key": "search",
                  "value": "",
                  "disabled": true,
                  "description": "Suchbegriff, 1 bis 100 Zeichen: Teilstring ohne Beachtung der Groß-/Kleinschreibung in Name und SKU des Artikels sowie in SKU, EAN, Hersteller-SKU und Farbe der Varianten. Leerzeichen am Rand werden ignoriert / Search term, 1 to 100 characters: case-insensitive substring of product name and SKU and of variant SKU, EAN, brand SKU and color. Surrounding whitespace is ignored."
                },
                {
                  "key": "sort",
                  "value": "",
                  "disabled": true,
                  "description": "Reihenfolge: `created_desc` (Standard, neueste Anlage zuerst), `updated_desc` (zuletzt geändert zuerst), `submitted_asc` (am längsten wartende Einreichung zuerst, nie eingereichte am Ende), `name_asc` (Name aufsteigend). Gleichstand entscheidet die ID / Sort order: `created_desc` (default, newest first), `updated_desc` (last changed first), `submitted_asc` (longest waiting submission first, never submitted last), `name_asc` (name ascending). Ties are broken by ID."
                },
                {
                  "key": "include_unreviewed",
                  "value": "",
                  "disabled": true,
                  "description": "Mit `true` auch eigene Lagerartikel ohne Prüfstatus aufnehmen (nicht über die Händler-Anlage entstanden); sie haben `review_status` `null`, `customer_can_edit` `false` und nicht immer eine `sku`. Standard `false` / With `true`, own warehouse products without a review status are included too; they have `review_status` `null`, `customer_can_edit` `false` and not always a `sku`. Default `false`."
                },
                {
                  "key": "availability",
                  "value": "",
                  "disabled": true,
                  "description": "Filter auf Artikelebene: `AVAILABLE` = mindestens eine Variante mit `available` > 0, `SOLD_OUT` = keine (Artikel ohne Varianten eingeschlossen). Ohne Angabe kein Filter / Product-level filter: `AVAILABLE` = at least one variant with `available` > 0, `SOLD_OUT` = none (products without variants included). Not set: no filter."
                }
              ]
            }
          }
        },
        {
          "name": "Create a warehouse product draft",
          "request": {
            "method": "POST",
            "description": "Legt einen Lagerartikel mit seinen Varianten als Entwurf (`DRAFT`) an. Die Antwort ist der angelegte Artikel mit Status 201.\n\nDer Artikel gehört immer dem Kunden des Tokens. Die Artikel-SKU (`SWP_` plus 8 Zeichen aus `A-Z0-9`) vergibt der Server; die Varianten-SKU lautet `<Artikel-SKU>__<Farbe>__<Größe>` und ändert sich nie. Größe und Farbe werden als Namen übergeben; bekannte Schreibweisen und Aliase wie `2XL`, `2xl` und `XXL` landen auf derselben Größe, unbekannte Namen legt der Server an. Der Artikel ist zunächst `INACTIVE`; erst nach der Freigabe sind Artikel und Varianten `ACTIVE`. Bestandsfelder der Varianten sind in dieser Antwort `null`; sie stehen in `GET /api/warehouse/products/{product_id}`. Der Entwurf wird erst durch `POST /api/warehouse/products/{product_id}/submit` zur Prüfung eingereicht.\n\n**Prüfstatus (`review_status`):** `DRAFT` (Entwurf) -> `IN_REVIEW` (eingereicht) -> `APPROVED` (freigegeben) oder `DECLINED` (abgelehnt, mit Begründung in `review_note`). Ein abgelehnter Artikel wird durch eine Änderung per `PUT` wieder zum `DRAFT` und muss erneut eingereicht werden.\n\nErfordert den Scope `warehouse:write`. Fehler: 400 mit `extra.error` = `INVALID_VARIANTS` (keine Variante, leere Größe oder Farbe, dieselbe Kombination aus Größe und Farbe doppelt - auch nach Aliasen -, oder Farbe/Größe ohne Buchstaben oder Ziffern), 403 bei fehlendem Scope oder Token ohne Kundenbezug, 404 wenn die Funktion für Händler nicht freigeschaltet ist.\n\nCreates a warehouse product with its variants as a draft (`DRAFT`). The response is the created product with status 201.\n\nThe product always belongs to the customer of the token. The server assigns the product SKU (`SWP_` plus 8 characters from `A-Z0-9`); the variant SKU is `<product SKU>__<color>__<size>` and never changes. Size and color are passed as names; known spellings and aliases such as `2XL`, `2xl` and `XXL` resolve to the same size, unknown names are created by the server. The product starts as `INACTIVE`; product and variants become `ACTIVE` only on approval. Stock fields of the variants are `null` in this response; they are available from `GET /api/warehouse/products/{product_id}`. The draft is only sent for review by `POST /api/warehouse/products/{product_id}/submit`.\n\n**Review status (`review_status`):** `DRAFT` -> `IN_REVIEW` (submitted) -> `APPROVED` or `DECLINED` (with the reason in `review_note`). Changing a declined product with `PUT` turns it back into `DRAFT`; it must be submitted again.\n\nRequires the scope `warehouse:write`. Errors: 400 with `extra.error` = `INVALID_VARIANTS` (no variant, blank size or color, the same size and color combination twice - also after aliases - or a color/size without letters or digits), 403 for a missing scope or a token without a customer, 404 if the feature is not enabled for merchants.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "products"
              ],
              "variable": [],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Muster-T-Shirt Basic\",\n  \"description\": \"Unisex-T-Shirt aus Bio-Baumwolle, Musterartikel.\",\n  \"supplier_number\": \"LIEF-0001\",\n  \"is_packaged\": true,\n  \"allow_return_restock\": true,\n  \"recommended_price\": 1999,\n  \"variants\": [\n    {\n      \"size\": \"M\",\n      \"color\": \"Schwarz\",\n      \"ean\": \"4006381333931\",\n      \"weight_g\": 180,\n      \"width_mm\": 300,\n      \"height_mm\": 400,\n      \"depth_mm\": 20\n    },\n    {\n      \"size\": \"L\",\n      \"color\": \"Schwarz\",\n      \"ean\": null,\n      \"weight_g\": 190,\n      \"width_mm\": null,\n      \"height_mm\": null,\n      \"depth_mm\": null\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get a warehouse product",
          "request": {
            "method": "GET",
            "description": "Liefert einen eigenen Lagerartikel mit Varianten, Bestand, Prüfstatus und der Begründung der letzten Ablehnung (`review_note`).\n\nAuch Lagerartikel ohne Prüfstatus (nicht über die Händler-Anlage entstanden) sind lesbar; sie haben `review_status` `null` und `customer_can_edit` `false`. Pro Variante stehen `gross_stock` und `available`; ist ein Sicherheitsbestand wirksam, kommen `available_unbuffered` und `safety_stock` hinzu. Die Begründung einer Ablehnung bleibt nach der Korrektur stehen und wird erst mit der erneuten Einreichung geleert.\n\n**Prüfstatus (`review_status`):** `DRAFT` (Entwurf) -> `IN_REVIEW` (eingereicht) -> `APPROVED` (freigegeben) oder `DECLINED` (abgelehnt, mit Begründung in `review_note`). Ein abgelehnter Artikel wird durch eine Änderung per `PUT` wieder zum `DRAFT` und muss erneut eingereicht werden.\n\nErfordert den Scope `warehouse:read`. Fehler: 403 bei fehlendem Scope oder Token ohne Kundenbezug, 404 wenn der Artikel nicht existiert, einem anderen Kunden gehört (nie 403) oder die Funktion für Händler nicht freigeschaltet ist.\n\nReturns one own warehouse product with variants, stock, review status and the reason of the last decline (`review_note`).\n\nWarehouse products without a review status (not created through the merchant flow) can be read as well; they have `review_status` `null` and `customer_can_edit` `false`. Each variant carries `gross_stock` and `available`; if a safety stock applies, `available_unbuffered` and `safety_stock` are added. The reason of a decline stays after the correction and is cleared only when the product is submitted again.\n\n**Review status (`review_status`):** `DRAFT` -> `IN_REVIEW` (submitted) -> `APPROVED` or `DECLINED` (with the reason in `review_note`). Changing a declined product with `PUT` turns it back into `DRAFT`; it must be submitted again.\n\nRequires the scope `warehouse:read`. Errors: 403 for a missing scope or a token without a customer, 404 if the product does not exist, belongs to another customer (never 403) or the feature is not enabled for merchants.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/products/:product_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "products",
                ":product_id"
              ],
              "variable": [
                {
                  "key": "product_id",
                  "value": "",
                  "description": "ID des Lagerartikels, wie in `id` der Artikelantwort / ID of the warehouse product, as returned in `id` of the product response."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Replace a warehouse product",
          "request": {
            "method": "PUT",
            "description": "Ersetzt den gesamten Stand eines eigenen Lagerartikels: Artikelfelder und Variantenliste. Es gibt kein Teil-Update; die Variantenliste im Request ist der gewünschte Endstand.\n\n**Wann änderbar:** nur in `DRAFT` und `DECLINED` (`customer_can_edit` = `true`). Eingereichte (`IN_REVIEW`) und freigegebene (`APPROVED`) Artikel lassen sich nicht ändern; der Aufruf antwortet 409 mit `extra.error` = `NOT_EDITABLE`. Eine Änderung aus `DECLINED` setzt den Artikel auf `DRAFT`; danach erneut einreichen.\n\n**Varianten:** Sie werden über die Kombination aus Größe und Farbe abgeglichen. Vorhandene Varianten behalten ID und SKU und übernehmen EAN, Gewicht, Maße und die eingegebenen Bezeichnungen neu. Neue Kombinationen werden angelegt. Nicht mehr genannte Kombinationen werden entfernt, solange der Artikel nie freigegeben war; ein bereits freigegebener Artikel behält sie als `INACTIVE`.\n\n**`recommended_price`:** Fehlt das Feld im Request, bleibt der gespeicherte Wert; `null` löscht ihn. Die übrigen optionalen Artikelfelder werden dagegen mit dem Request-Wert überschrieben, fehlende Felder werden zu `null`.\n\nErfordert den Scope `warehouse:write`. Fehler: 400 mit `extra.error` = `INVALID_VARIANTS` (wie beim Anlegen), 403 bei fehlendem Scope oder Token ohne Kundenbezug, 404 bei unbekanntem oder fremdem Artikel oder nicht freigeschalteter Funktion, 409 mit `extra.error` = `NOT_EDITABLE` (Artikel nicht in `DRAFT`/`DECLINED`; `extra.review_status` nennt den aktuellen Wert). Bestandsfelder der Varianten sind in der Antwort `null`.\n\nReplaces the complete state of an own warehouse product: product fields and variant list. There is no partial update; the variant list in the request is the desired final state.\n\n**When editable:** only in `DRAFT` and `DECLINED` (`customer_can_edit` = `true`). Submitted (`IN_REVIEW`) and approved (`APPROVED`) products cannot be changed; the call answers 409 with `extra.error` = `NOT_EDITABLE`. A change from `DECLINED` sets the product to `DRAFT`; submit it again afterwards.\n\n**Variants:** They are matched by the combination of size and color. Existing variants keep ID and SKU and take over EAN, weight, dimensions and the entered labels. New combinations are created. Combinations no longer listed are removed as long as the product was never approved; an already approved product keeps them as `INACTIVE`.\n\n**`recommended_price`:** If the field is missing from the request the stored value is kept; `null` clears it. The other optional product fields are overwritten with the request value, missing fields become `null`.\n\nRequires the scope `warehouse:write`. Errors: 400 with `extra.error` = `INVALID_VARIANTS` (as on create), 403 for a missing scope or a token without a customer, 404 for an unknown or foreign product or a feature that is not enabled, 409 with `extra.error` = `NOT_EDITABLE` (product not in `DRAFT`/`DECLINED`; `extra.review_status` carries the current value). Stock fields of the variants are `null` in the response.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/products/:product_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "products",
                ":product_id"
              ],
              "variable": [
                {
                  "key": "product_id",
                  "value": "",
                  "description": "ID des Lagerartikels, wie in `id` der Artikelantwort / ID of the warehouse product, as returned in `id` of the product response."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Muster-T-Shirt Basic\",\n  \"description\": \"Unisex-T-Shirt aus Bio-Baumwolle, Musterartikel.\",\n  \"supplier_number\": \"LIEF-0001\",\n  \"is_packaged\": true,\n  \"allow_return_restock\": true,\n  \"recommended_price\": 1999,\n  \"variants\": [\n    {\n      \"size\": \"M\",\n      \"color\": \"Schwarz\",\n      \"ean\": \"4006381333931\",\n      \"weight_g\": 180,\n      \"width_mm\": 300,\n      \"height_mm\": 400,\n      \"depth_mm\": 20\n    },\n    {\n      \"size\": \"L\",\n      \"color\": \"Schwarz\",\n      \"ean\": null,\n      \"weight_g\": 190,\n      \"width_mm\": null,\n      \"height_mm\": null,\n      \"depth_mm\": null\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Submit a warehouse product for review",
          "request": {
            "method": "POST",
            "description": "Reicht einen Entwurf zur Prüfung ein: `DRAFT` -> `IN_REVIEW`. Die Antwort ist der Artikel im neuen Stand.\n\nDie Einreichung setzt `submitted_at` und leert eine frühere Entscheidung (`review_note`, `reviewed_at`). Danach ist der Artikel nicht mehr änderbar (`customer_can_edit` = `false`), bis er abgelehnt wird. Die Prüfung erfolgt durch Shirtee; bei Freigabe und bei Ablehnung (mit Begründung) geht eine E-Mail an das Login des Händlers. Erst ein freigegebener Artikel (`APPROVED`) ist `ACTIVE`. Ein abgelehnter Artikel (`DECLINED`) kann nicht direkt eingereicht werden: erst per `PUT` korrigieren (das setzt `DRAFT`), dann einreichen. Der Aufruf braucht keinen Request-Body. Bestandsfelder der Varianten sind in der Antwort `null`.\n\n**Prüfstatus (`review_status`):** `DRAFT` (Entwurf) -> `IN_REVIEW` (eingereicht) -> `APPROVED` (freigegeben) oder `DECLINED` (abgelehnt, mit Begründung in `review_note`). Ein abgelehnter Artikel wird durch eine Änderung per `PUT` wieder zum `DRAFT` und muss erneut eingereicht werden.\n\nErfordert den Scope `warehouse:write`. Fehler: 403 bei fehlendem Scope oder Token ohne Kundenbezug, 404 bei unbekanntem oder fremdem Artikel oder nicht freigeschalteter Funktion, 409 mit `extra.error` = `REVIEW_STATE_CHANGED`, wenn der Artikel nicht (mehr) `DRAFT` ist; `extra.review_status` nennt den aktuellen Wert. Wiederholtes Einreichen eines bereits eingereichten Artikels liefert daher 409, keine zweite Einreichung.\n\nSubmits a draft for review: `DRAFT` -> `IN_REVIEW`. The response is the product in its new state.\n\nSubmitting sets `submitted_at` and clears an earlier decision (`review_note`, `reviewed_at`). The product can then no longer be changed (`customer_can_edit` = `false`) until it is declined. Shirtee performs the review; on approval and on decline (with a reason) an email goes to the merchant's login. Only an approved product (`APPROVED`) is `ACTIVE`. A declined product (`DECLINED`) cannot be submitted directly: correct it with `PUT` first (which sets `DRAFT`), then submit. The call needs no request body. Stock fields of the variants are `null` in the response.\n\n**Review status (`review_status`):** `DRAFT` -> `IN_REVIEW` (submitted) -> `APPROVED` or `DECLINED` (with the reason in `review_note`). Changing a declined product with `PUT` turns it back into `DRAFT`; it must be submitted again.\n\nRequires the scope `warehouse:write`. Errors: 403 for a missing scope or a token without a customer, 404 for an unknown or foreign product or a feature that is not enabled, 409 with `extra.error` = `REVIEW_STATE_CHANGED` if the product is not (or no longer) `DRAFT`; `extra.review_status` carries the current value. Submitting an already submitted product therefore answers 409 and does not submit a second time.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/products/:product_id/submit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "products",
                ":product_id",
                "submit"
              ],
              "variable": [
                {
                  "key": "product_id",
                  "value": "",
                  "description": "ID des Lagerartikels, wie in `id` der Artikelantwort / ID of the warehouse product, as returned in `id` of the product response."
                }
              ],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Warehouse Safety Stock",
      "item": [
        {
          "name": "Get the safety stock of one own article",
          "request": {
            "method": "GET",
            "description": "Liefert den Sicherheitsbestand eines eigenen Lagerartikels: die Ausnahme am Artikel (`override`), den Standard des Händlers (`default`) und die daraus wirksame Staffel (`effective`).\n\nDer **Sicherheitsbestand** hält einen Teil des freien Bestands einer Variante zurück: `available` in der Artikelantwort ist der freie Bestand abzüglich dieses Puffers (nie negativ). Er gilt nur für Lagerartikel. Die Staffel hat drei Werte: ab `threshold` freien Stück hält der Puffer `buffer` zurück, darunter `low_buffer`. Gemeldet wird `max(0, frei - Puffer)`. Die Staffel ist bewusst nicht monoton (siehe `preview`).\n\n**Auflösung:** Eine Ausnahme am Artikel gilt vor dem Standard. Ausnahme `CUSTOM` = eigene Staffel, Ausnahme `NONE` = kein Puffer trotz Standard. Ohne Ausnahme gilt der Standard, sofern er eingeschaltet ist (`default.enabled`), sonst gibt es keinen Puffer. `source` nennt die Herkunft: `OVERRIDE`, `DEFAULT` oder `NONE`; bei `NONE` ist `effective` `null`.\n\nErfordert den Scope `warehouse:read`. Der Artikel kann in jedem Prüfstatus abgefragt werden, solange er ein Lagerartikel mit Besitzer ist. Fehler: 403 bei fehlendem Scope oder Token ohne Kundenbezug, 404 bei unbekanntem oder fremdem Artikel, Artikel, der kein Lagerartikel ist, oder nicht freigeschalteter Funktion. Die Referenz enthält nur lesende Aufrufe zum Sicherheitsbestand.\n\nReturns the safety stock of an own warehouse product: the override on the product (`override`), the merchant default (`default`) and the tiers that apply as a result (`effective`).\n\nThe **safety stock** holds back part of the free stock of a variant: `available` in the product response is the free stock minus this buffer (never negative). It applies to warehouse products only. The tiers have three values: from `threshold` free pieces upwards the buffer held back is `buffer`, below it `low_buffer`. The reported quantity is `max(0, free - buffer)`. The tiers are deliberately not monotonic (see `preview`).\n\n**Resolution:** An override on the product takes precedence over the default. Override `CUSTOM` = own tiers, override `NONE` = no buffer despite the default. Without an override the default applies if it is switched on (`default.enabled`), otherwise there is no buffer. `source` names the origin: `OVERRIDE`, `DEFAULT` or `NONE`; with `NONE`, `effective` is `null`.\n\nRequires the scope `warehouse:read`. The product can be queried in any review status as long as it is a warehouse product with an owner. Errors: 403 for a missing scope or a token without a customer, 404 for an unknown or foreign product, a product that is no warehouse product, or a feature that is not enabled. This reference contains only read calls for the safety stock.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/products/:product_id/safety-stock",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "products",
                ":product_id",
                "safety-stock"
              ],
              "variable": [
                {
                  "key": "product_id",
                  "value": "",
                  "description": "ID des Lagerartikels, wie in `id` der Artikelantwort / ID of the warehouse product, as returned in `id` of the product response."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Get own safety-stock default",
          "request": {
            "method": "GET",
            "description": "Liefert den Standard-Sicherheitsbestand des Händlers. Er gilt für alle eigenen Lagerartikel, die keine eigene Ausnahme haben (siehe `GET /api/warehouse/products/{product_id}/safety-stock`).\n\nDer **Sicherheitsbestand** hält einen Teil des freien Bestands einer Variante zurück: `available` in der Artikelantwort ist der freie Bestand abzüglich dieses Puffers (nie negativ). Er gilt nur für Lagerartikel. Solange nie ein Standard gesetzt wurde, ist `rule` `null`, `enabled` `false` und `updated_at`/`updated_by` sind `null`. Ist ein Standard gesetzt, aber ausgeschaltet, bleiben die Werte in `rule` sichtbar und `enabled` ist `false`; sie wirken dann nicht. `rule.preview` zeigt an Stützpunkten, wie viele Stück bei welchem freien Bestand gemeldet werden.\n\nErfordert den Scope `warehouse:read`. Fehler: 403 bei fehlendem Scope oder Token ohne Kundenbezug, 404 wenn die Funktion nicht freigeschaltet ist.\n\nReturns the merchant's default safety stock. It applies to all own warehouse products without an override of their own (see `GET /api/warehouse/products/{product_id}/safety-stock`).\n\nThe **safety stock** holds back part of the free stock of a variant: `available` in the product response is the free stock minus this buffer (never negative). It applies to warehouse products only. As long as no default was ever set, `rule` is `null`, `enabled` is `false` and `updated_at`/`updated_by` are `null`. If a default is set but switched off, its values stay visible in `rule` and `enabled` is `false`; they then have no effect. `rule.preview` shows at sample points how many pieces are reported for a given free stock.\n\nRequires the scope `warehouse:read`. Errors: 403 for a missing scope or a token without a customer, 404 if the feature is not enabled.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/safety-stock",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "safety-stock"
              ],
              "variable": [],
              "query": []
            }
          }
        }
      ]
    },
    {
      "name": "Warehouse Shipments",
      "item": [
        {
          "name": "List inbound shipments",
          "request": {
            "method": "GET",
            "description": "Listet die Anlieferungen ans Lager, die zum Händlerkonto gehören, seitenweise. Jeder Eintrag hat dieselbe Form wie bei `GET /api/warehouse/shipments/{shipment_id}`, also mit Positionen und aktiven Dokumenten. Interne Umlagerungen des Lagers erscheinen nie.\n\n**Sortierung:** ohne Datumsfilter die zuletzt angelegten zuerst. Sobald `date_from` oder `date_to` gesetzt ist, aufsteigend nach Kalendertag (`planned_inbound_on`, ersatzweise `expected_on`), dann nach `id`; Anlieferungen ohne Tag stehen am Ende. Beide Datumsgrenzen gelten inklusive. `include_undated=true` nimmt Anlieferungen ohne Tag in das Ergebnis auf und wirkt nur zusammen mit einer Datumsgrenze. Liegt `date_to` vor `date_from`, antwortet der Aufruf mit 400 und `extra.error` = `INVALID_DATE_RANGE`.\n\nDie Antwort enthält `total_count` (Treffer insgesamt) sowie `limit` und `offset` der Anfrage. Ein `customer_id`, das nicht zum Händlerkonto gehört, liefert eine leere Liste. Benötigter Scope: `warehouse:read`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nLists the inbound shipments to the warehouse that belong to the merchant account, page by page. Each entry has the same shape as `GET /api/warehouse/shipments/{shipment_id}`, including items and active documents. Internal transfers of the warehouse never appear.\n\n**Sorting:** without a date filter, most recently created first. As soon as `date_from` or `date_to` is set, ascending by calendar day (`planned_inbound_on`, otherwise `expected_on`), then by `id`; shipments without a day come last. Both date bounds are inclusive. `include_undated=true` adds shipments without a day to the result and only has an effect together with a date bound. If `date_to` is before `date_from`, the call answers 400 with `extra.error` = `INVALID_DATE_RANGE`.\n\nThe response carries `total_count` (all matches) plus the `limit` and `offset` of the request. A `customer_id` that does not belong to the merchant account yields an empty list. Required scope: `warehouse:read`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments"
              ],
              "variable": [],
              "query": [
                {
                  "key": "limit",
                  "value": "",
                  "disabled": true,
                  "description": "Maximale Anzahl Einträge je Seite, 1 bis 100, Standard 20 / Maximum number of entries per page, 1 to 100, default 20."
                },
                {
                  "key": "offset",
                  "value": "",
                  "disabled": true,
                  "description": "Anzahl der zu überspringenden Einträge für die Paginierung, mindestens 0, Standard 0 / Number of entries to skip for pagination, minimum 0, default 0."
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true,
                  "description": "Nur Anlieferungen in diesem Status: `ANNOUNCED`, `RECEIVED` oder `CANCELED`. Ohne Angabe alle Status / Only shipments in this status: `ANNOUNCED`, `RECEIVED` or `CANCELED`. Omit for all statuses."
                },
                {
                  "key": "customer_id",
                  "value": "",
                  "disabled": true,
                  "description": "Optionaler Filter auf eine Kunden-ID, mindestens 1. Er schränkt nur ein: Fremde Kunden-IDs liefern eine leere Liste / Optional filter on a customer ID, minimum 1. It only narrows the result: other customers' IDs yield an empty list."
                },
                {
                  "key": "date_from",
                  "value": "",
                  "disabled": true,
                  "description": "Untere Datumsgrenze (inklusiv), Format `YYYY-MM-DD`, bezogen auf `planned_inbound_on`, ersatzweise `expected_on`. Schaltet die Sortierung nach Tag ein / Lower date bound (inclusive), format `YYYY-MM-DD`, applied to `planned_inbound_on`, otherwise `expected_on`. Switches to sorting by day."
                },
                {
                  "key": "date_to",
                  "value": "",
                  "disabled": true,
                  "description": "Obere Datumsgrenze (inklusiv), Format `YYYY-MM-DD`, bezogen auf `planned_inbound_on`, ersatzweise `expected_on`; darf nicht vor `date_from` liegen / Upper date bound (inclusive), format `YYYY-MM-DD`, applied to `planned_inbound_on`, otherwise `expected_on`; must not be before `date_from`."
                },
                {
                  "key": "include_undated",
                  "value": "",
                  "disabled": true,
                  "description": "Mit `true` erscheinen auch Anlieferungen ohne Tag (weder `planned_inbound_on` noch `expected_on`) am Ende; wirkt nur mit `date_from` oder `date_to`, Standard `false` / With `true`, shipments without a day (neither `planned_inbound_on` nor `expected_on`) are included at the end; only effective with `date_from` or `date_to`, default `false`."
                }
              ]
            }
          }
        },
        {
          "name": "Announce an inbound shipment",
          "request": {
            "method": "POST",
            "description": "Kündigt eine Anlieferung ans Lager an. Die neue Anlieferung entsteht im Status `ANNOUNCED` und bekommt die Anlieferungsnummer `reference` = `WHS_<id>`. Der Aufruf ist nicht idempotent: Jeder erfolgreiche Aufruf legt eine weitere Anlieferung an.\n\n**Positionen:** `items` braucht mindestens eine Position, jede Variante höchstens einmal (sonst 400 mit `extra.error` = `INVALID_ITEMS`). Jede `product_variant_id` muss eine Lagerartikel-Variante des eigenen Händlerkontos sein, sonst 404. Eine deaktivierte Variante wird nicht neu angenommen: 400 mit `extra.error` = `VARIANT_INACTIVE` und `extra.product_variant_ids`. Verstöße gegen Wertebereiche und Längen der Felder liefern 400 ohne `extra.error`.\n\n**Händler-Besonderheiten:** Der Kunde ergibt sich aus dem Zugang; `customer_id`, `planned_inbound_on`, `target_manufacturer_id` und `is_internal` im Body werden ignoriert. `planned_inbound_on` setzt das Lager. Liegt der gespeicherte Termin bereits im Sperrfenster, ist gespeichert und die Antwort enthält die Warnung `EDIT_WINDOW_CLOSED`: Ab dann ändert nur noch das Lager. Als Händler lassen sich Anlieferungen nur im Status `ANNOUNCED` und vor dem Stichtag ändern. Stichtag ist der Kalendertag (Zeitzone Europe/Berlin), der drei Tage vor dem vom Lager gesetzten Eingangstermin (`planned_inbound_on`, ersatzweise vor `expected_on`) liegt; am Stichtag selbst ist bereits gesperrt. Ohne beide Termine gibt es keinen Stichtag. Eine im Lagersystem bereits angelegte Einkaufsbestellung (`zoho_po_state` ungleich `NONE`) sperrt ebenfalls, auch vor dem Stichtag. `customer_can_edit` in der Antwort zeigt, ob noch geändert werden darf.\n\nBenötigter Scope: `warehouse:shipment_write` oder `warehouse:write`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nAnnounces an inbound shipment to the warehouse. The new shipment is created with status `ANNOUNCED` and gets the shipment number `reference` = `WHS_<id>`. The call is not idempotent: every successful call creates another shipment.\n\n**Items:** `items` needs at least one item, each variant at most once (otherwise 400 with `extra.error` = `INVALID_ITEMS`). Every `product_variant_id` must be a warehouse-stock variant of the merchant's own account, otherwise 404. A deactivated variant is not accepted as a new item: 400 with `extra.error` = `VARIANT_INACTIVE` and `extra.product_variant_ids`. Violations of value ranges and lengths return 400 without `extra.error`.\n\n**Merchant specifics:** the customer is derived from the credentials; `customer_id`, `planned_inbound_on`, `target_manufacturer_id` and `is_internal` in the body are ignored. The warehouse sets `planned_inbound_on`. If the stored date already lies in the lock window, the shipment is saved and the response carries the warning `EDIT_WINDOW_CLOSED`: from then on only the warehouse can change it. As a merchant you can change shipments only while the status is `ANNOUNCED` and before the cut-off date. The cut-off is the calendar day (time zone Europe/Berlin) three days before the inbound date set by the warehouse (`planned_inbound_on`, otherwise `expected_on`); the cut-off day itself is already locked. Without either date there is no cut-off. A purchase order already created in the warehouse system (`zoho_po_state` other than `NONE`) also locks the shipment, even before the cut-off. `customer_can_edit` in the response tells whether changes are still allowed.\n\nRequired scope: `warehouse:shipment_write` or `warehouse:write`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments"
              ],
              "variable": [],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"product_variant_id\": 48213,\n      \"announced_quantity\": 120,\n      \"note\": \"Karton 1 bis 3\"\n    }\n  ],\n  \"notice\": \"Bitte vor 12 Uhr anliefern\",\n  \"expected_on\": \"2026-02-10\",\n  \"package_count\": 3,\n  \"pallet_count\": 1,\n  \"carrier\": \"Spedition Mustermann\",\n  \"tracking_number\": \"MUSTER-0001234\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Translate a shipping list into a shipment proposal",
          "request": {
            "method": "POST",
            "description": "Liest eine Excel-Versandliste (Shippingliste, `.xlsx`) ein und liefert daraus einen Vorschlag für eine Anlieferung. Der Aufruf legt nichts an und ändert nichts. Die Datei kommt als `multipart/form-data` im Feld `file`; größer als 20 MB wird sie abgewiesen.\n\n**Format der Datei:** Gelesen wird nur das erste Tabellenblatt. Die Kopfzeile muss in den ersten 20 Zeilen stehen und eine Artikelspalte sowie eine Mengenspalte enthalten. Die Überschriften werden ohne Beachtung von Groß-/Kleinschreibung, Leer- und Sonderzeichen erkannt. Artikel: `SKU`, `Artikelnummer`, `Artikelnr`, `ArtNr`, `Item Number`, `Item No`, `Product Number`, `Produktnummer`, `Varianten-SKU`, `Variant SKU`, `Eigene SKU`, `Custom SKU` oder EAN: `EAN`, `EAN13`, `EAN-Code`, `GTIN`, `GTIN13`, `Barcode`, `Strichcode`. Menge: `Menge`, `Anzahl`, `Qty`, `Quantity`, `Stück`, `Stk`, `Pcs`, `Pieces`, `Stückzahl`. Optional: `Notiz`, `Bemerkung`, `Note`, `Notes`, `Kommentar`, `Comment`, `Hinweis`. `Amount` zählt nicht als Mengenspalte. Eine Menge ist eine ganze Zahl größer 0 (Zahl oder Ziffernfolge); leere Zeilen werden übersprungen. Höchstens 5.000 Datenzeilen.\n\n**Zuordnung:** Jede Zeile wird den eigenen Lagerartikeln des Händlerkontos zugeordnet, je Kennung in der Reihenfolge SKU, Marken-SKU, EAN; die SKU-Spalte hat Vorrang vor der EAN-Spalte. Zeilen mit derselben Variante werden aufsummiert (`rows` nennt die Excel-Zeilen, Notizen werden mit `; ` verbunden und auf 512 Zeichen gekürzt). Was nicht zugeordnet wurde, steht in `unmatched` (kein Treffer), `ambiguous` (mehrere aktive Treffer) oder `invalid` (Zeile unbrauchbar, Grund in `reason`). Eine Summe über 100.000 je Variante verwirft alle beteiligten Zeilen mit `QUANTITY_TOO_LARGE`. `columns` nennt die erkannten Spalten mit ihrer Beschriftung.\n\n**Weiterverarbeitung:** Für `POST /api/warehouse/shipments` je Position nur `product_variant_id`, `announced_quantity` und `note` übernehmen; `sku` und `rows` kennt das Anlegen nicht. Die Datei selbst lässt sich danach als Dokument an die Anlieferung hängen.\n\n**Fehler:** Ist die Datei nicht lesbar, antwortet der Aufruf mit 400, `extra.error` = `INVALID_SHIPPING_LIST` und `extra.reason`: `NOT_XLSX` (keine lesbare xlsx-Datei), `FILE_REJECTED` (Datei entpackt unverhältnismäßig groß), `HEADER_NOT_FOUND` (keine Kopfzeile mit Artikel- und Mengenspalte) oder `TOO_MANY_ROWS` (mehr als 5.000 Datenzeilen oder mehr als 20.000 gelesene Zeilen einschließlich leerer). Eine Datei über 20 MB liefert 400 mit `extra.error` = `FILE_TOO_LARGE`; ist schon der Request über 20 MB zuzüglich Multipart-Rahmen, 413 ohne `extra.error`. Benötigter Scope: `warehouse:shipment_write` oder `warehouse:write`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nReads an Excel shipping list (`.xlsx`) and returns a proposal for an inbound shipment. The call creates and changes nothing. The file is sent as `multipart/form-data` in the field `file`; files larger than 20 MB are rejected.\n\n**File format:** only the first worksheet is read. The header row must be within the first 20 rows and contain an article column and a quantity column. Headings are matched ignoring case, spaces and special characters. Article: `SKU`, `Artikelnummer`, `Artikelnr`, `ArtNr`, `Item Number`, `Item No`, `Product Number`, `Produktnummer`, `Varianten-SKU`, `Variant SKU`, `Eigene SKU`, `Custom SKU`, or EAN: `EAN`, `EAN13`, `EAN-Code`, `GTIN`, `GTIN13`, `Barcode`, `Strichcode`. Quantity: `Menge`, `Anzahl`, `Qty`, `Quantity`, `Stück`, `Stk`, `Pcs`, `Pieces`, `Stückzahl`. Optional: `Notiz`, `Bemerkung`, `Note`, `Notes`, `Kommentar`, `Comment`, `Hinweis`. `Amount` does not count as a quantity column. A quantity is a whole number greater than 0 (number or string of digits); empty rows are skipped. At most 5,000 data rows.\n\n**Matching:** every row is matched against the merchant account's own warehouse articles, per identifier in the order SKU, brand SKU, EAN; the SKU column takes precedence over the EAN column. Rows with the same variant are summed (`rows` lists the Excel rows, notes are joined with `; ` and cut to 512 characters). Whatever could not be matched is listed in `unmatched` (no hit), `ambiguous` (several active hits) or `invalid` (row unusable, reason in `reason`). A sum above 100,000 per variant discards all contributing rows with `QUANTITY_TOO_LARGE`. `columns` names the detected columns with their label in the file.\n\n**Next step:** for `POST /api/warehouse/shipments` take only `product_variant_id`, `announced_quantity` and `note` per item; creating does not accept `sku` and `rows`. The file itself can then be attached to the shipment as a document.\n\n**Errors:** if the file cannot be read, the call answers 400 with `extra.error` = `INVALID_SHIPPING_LIST` and `extra.reason`: `NOT_XLSX` (not a readable xlsx file), `FILE_REJECTED` (file expands disproportionately), `HEADER_NOT_FOUND` (no header row with an article and a quantity column) or `TOO_MANY_ROWS` (more than 5,000 data rows or more than 20,000 scanned rows including empty ones). A file over 20 MB yields 400 with `extra.error` = `FILE_TOO_LARGE`; if the whole request exceeds 20 MB plus multipart overhead, 413 without `extra.error`. Required scope: `warehouse:shipment_write` or `warehouse:write`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments/import",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments",
                "import"
              ],
              "variable": [],
              "query": [
                {
                  "key": "customer_id",
                  "value": "",
                  "disabled": true,
                  "description": "Hat für Händler keine Wirkung: Der Kunde ergibt sich aus dem Zugang / Has no effect for merchants: the customer is derived from the credentials."
                }
              ]
            }
          }
        },
        {
          "name": "Get an inbound shipment",
          "request": {
            "method": "GET",
            "description": "Liefert eine Anlieferung mit allen Positionen und den aktiven (nicht entfernten) Dokumenten. Anlieferungen anderer Händler, interne Umlagerungen des Lagers und unbekannte IDs antworten gleichermaßen mit 404, nie mit 403. Die Anlieferung ist in jedem Status abrufbar, auch nach Wareneingang oder Storno.\n\nBenötigter Scope: `warehouse:read`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nReturns an inbound shipment with all items and the active (not removed) documents. Shipments of other merchants, internal transfers of the warehouse and unknown IDs all answer 404, never 403. The shipment can be retrieved in every status, also after goods receipt or cancellation.\n\nRequired scope: `warehouse:read`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments/:shipment_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments",
                ":shipment_id"
              ],
              "variable": [
                {
                  "key": "shipment_id",
                  "value": "",
                  "description": "ID der Anlieferung (`id` aus Anlegen oder Liste) / ID of the inbound shipment (`id` from create or list)."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Replace an inbound shipment",
          "request": {
            "method": "PUT",
            "description": "Ersetzt den gesamten Stand einer Anlieferung (Kopfdaten und Positionen); es ist kein Teil-Update. Felder, die fehlen oder `null` sind, werden geleert. Positionen, die im Body nicht mehr vorkommen, werden entfernt; vorhandene Varianten erhalten Menge und Notiz neu, neue Varianten werden ergänzt. Für `items` gelten dieselben Regeln wie beim Anlegen (mindestens eine Position, keine doppelte Variante, nur eigene Lagerartikel, deaktivierte Varianten nicht neu; bereits enthaltene deaktivierte Varianten dürfen bleiben).\n\n**Voraussetzungen:** Die Anlieferung muss im Status `ANNOUNCED` sein, sonst 409 mit `extra.error` = `SHIPMENT_NOT_EDITABLE` und `extra.status`. Nach dem Stichtag antwortet der Aufruf mit 409 und `extra.error` = `SHIPMENT_LOCKED`. Geprüft wird dabei der gespeicherte Stand, nicht der neue: Ein verschobener Termin im Body hebt die Sperre nicht auf. Als Händler lassen sich Anlieferungen nur im Status `ANNOUNCED` und vor dem Stichtag ändern. Stichtag ist der Kalendertag (Zeitzone Europe/Berlin), der drei Tage vor dem vom Lager gesetzten Eingangstermin (`planned_inbound_on`, ersatzweise vor `expected_on`) liegt; am Stichtag selbst ist bereits gesperrt. Ohne beide Termine gibt es keinen Stichtag. Eine im Lagersystem bereits angelegte Einkaufsbestellung (`zoho_po_state` ungleich `NONE`) sperrt ebenfalls, auch vor dem Stichtag. `customer_can_edit` in der Antwort zeigt, ob noch geändert werden darf.\n\n**Optimistische Sperre:** Wird `expected_updated_at` mitgesendet (der `updated_at`-Wert des bearbeiteten Stands, Vergleich auf Millisekunden), antwortet der Aufruf mit 409 und `extra.error` = `SHIPMENT_STALE`, wenn die Anlieferung inzwischen geändert wurde. Ohne das Feld wird nicht geprüft.\n\n`customer_id` gibt es hier nicht. `planned_inbound_on`, `target_manufacturer_id` und `is_internal` werden für Händler ignoriert; der gespeicherte Eingangstermin bleibt unverändert. Liegt der gespeicherte Termin nach dem Speichern im Sperrfenster, enthält die Antwort die Warnung `EDIT_WINDOW_CLOSED`. Benötigter Scope: `warehouse:shipment_write` oder `warehouse:write`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nReplaces the complete state of an inbound shipment (header data and items); it is not a partial update. Fields that are missing or `null` are cleared. Items no longer present in the body are removed; existing variants get their quantity and note replaced, new variants are added. `items` follows the same rules as on create (at least one item, no duplicate variant, own warehouse articles only, deactivated variants not as new items; deactivated variants already on the shipment may stay).\n\n**Preconditions:** the shipment must have status `ANNOUNCED`, otherwise 409 with `extra.error` = `SHIPMENT_NOT_EDITABLE` and `extra.status`. After the cut-off the call answers 409 with `extra.error` = `SHIPMENT_LOCKED`. The stored state is checked, not the new one: moving the date in the body does not lift the lock. As a merchant you can change shipments only while the status is `ANNOUNCED` and before the cut-off date. The cut-off is the calendar day (time zone Europe/Berlin) three days before the inbound date set by the warehouse (`planned_inbound_on`, otherwise `expected_on`); the cut-off day itself is already locked. Without either date there is no cut-off. A purchase order already created in the warehouse system (`zoho_po_state` other than `NONE`) also locks the shipment, even before the cut-off. `customer_can_edit` in the response tells whether changes are still allowed.\n\n**Optimistic lock:** if `expected_updated_at` is sent (the `updated_at` value of the state you edited, compared to the millisecond), the call answers 409 with `extra.error` = `SHIPMENT_STALE` when the shipment has changed in the meantime. Without the field nothing is checked.\n\nThere is no `customer_id` here. `planned_inbound_on`, `target_manufacturer_id` and `is_internal` are ignored for merchants; the stored inbound date stays unchanged. If the stored date lies in the lock window after saving, the response carries the warning `EDIT_WINDOW_CLOSED`. Required scope: `warehouse:shipment_write` or `warehouse:write`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments/:shipment_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments",
                ":shipment_id"
              ],
              "variable": [
                {
                  "key": "shipment_id",
                  "value": "",
                  "description": "ID der zu ersetzenden Anlieferung / ID of the inbound shipment to replace."
                }
              ],
              "query": []
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"product_variant_id\": 48213,\n      \"announced_quantity\": 150,\n      \"note\": \"Karton 1 bis 4\"\n    },\n    {\n      \"product_variant_id\": 48220,\n      \"announced_quantity\": 40,\n      \"note\": null\n    }\n  ],\n  \"notice\": \"Bitte vor 12 Uhr anliefern\",\n  \"expected_on\": \"2026-02-12\",\n  \"package_count\": 4,\n  \"pallet_count\": 1,\n  \"carrier\": \"Spedition Mustermann\",\n  \"tracking_number\": \"MUSTER-0001234\",\n  \"expected_updated_at\": \"2026-01-15T10:30:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel an inbound shipment",
          "request": {
            "method": "DELETE",
            "description": "Storniert eine Anlieferung. Nichts wird gelöscht: Die Anlieferung bleibt mit Status `CANCELED` und `canceled_at` abrufbar und ist danach nicht mehr änderbar. Der Aufruf liefert die stornierte Anlieferung mit Status 200 zurück.\n\n**Voraussetzungen:** Nur eine Anlieferung im Status `ANNOUNCED` lässt sich stornieren; sonst 409 mit `extra.error` = `SHIPMENT_NOT_EDITABLE` und `extra.status`. Das gilt auch für einen wiederholten Aufruf auf eine bereits stornierte Anlieferung. Nach dem Stichtag antwortet der Aufruf mit 409 und `extra.error` = `SHIPMENT_LOCKED`. Als Händler lassen sich Anlieferungen nur im Status `ANNOUNCED` und vor dem Stichtag ändern. Stichtag ist der Kalendertag (Zeitzone Europe/Berlin), der drei Tage vor dem vom Lager gesetzten Eingangstermin (`planned_inbound_on`, ersatzweise vor `expected_on`) liegt; am Stichtag selbst ist bereits gesperrt. Ohne beide Termine gibt es keinen Stichtag. Eine im Lagersystem bereits angelegte Einkaufsbestellung (`zoho_po_state` ungleich `NONE`) sperrt ebenfalls, auch vor dem Stichtag. `customer_can_edit` in der Antwort zeigt, ob noch geändert werden darf.\n\nBenötigter Scope: `warehouse:shipment_cancel` oder `warehouse:write`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nCancels an inbound shipment. Nothing is deleted: the shipment stays retrievable with status `CANCELED` and `canceled_at` and can no longer be changed. The call returns the canceled shipment with status 200.\n\n**Preconditions:** only a shipment with status `ANNOUNCED` can be canceled; otherwise 409 with `extra.error` = `SHIPMENT_NOT_EDITABLE` and `extra.status`. This also applies to a repeated call on an already canceled shipment. After the cut-off the call answers 409 with `extra.error` = `SHIPMENT_LOCKED`. As a merchant you can change shipments only while the status is `ANNOUNCED` and before the cut-off date. The cut-off is the calendar day (time zone Europe/Berlin) three days before the inbound date set by the warehouse (`planned_inbound_on`, otherwise `expected_on`); the cut-off day itself is already locked. Without either date there is no cut-off. A purchase order already created in the warehouse system (`zoho_po_state` other than `NONE`) also locks the shipment, even before the cut-off. `customer_can_edit` in the response tells whether changes are still allowed.\n\nRequired scope: `warehouse:shipment_cancel` or `warehouse:write`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments/:shipment_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments",
                ":shipment_id"
              ],
              "variable": [
                {
                  "key": "shipment_id",
                  "value": "",
                  "description": "ID der zu stornierenden Anlieferung / ID of the inbound shipment to cancel."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Attach a document to an inbound shipment",
          "request": {
            "method": "POST",
            "description": "Hängt ein Dokument an eine Anlieferung, etwa die Excel-Versandliste oder einen Lieferschein. Die Datei kommt als `multipart/form-data` im Feld `file`, eine Datei je Aufruf. Erlaubt sind Excel-Dateien (`.xlsx`) und PDF, höchstens 20 MB. Der Dateityp wird am Inhalt geprüft, nicht am Dateinamen; CSV und das alte `.xls`-Format werden daher abgewiesen (400, `extra.error` = `INVALID_FILE_TYPE`). Eine Datei über 20 MB liefert 400 mit `extra.error` = `FILE_TOO_LARGE`; ist schon der Request über 20 MB zuzüglich Multipart-Rahmen, 413 ohne `extra.error`. Dateinamen über 255 Zeichen werden unter Beibehaltung der Endung gekürzt.\n\n**Voraussetzungen:** Als Händler nur bei Status `ANNOUNCED` (sonst 409, `extra.error` = `SHIPMENT_NOT_EDITABLE`) und vor dem Stichtag (sonst 409, `extra.error` = `SHIPMENT_LOCKED`). Als Händler lassen sich Anlieferungen nur im Status `ANNOUNCED` und vor dem Stichtag ändern. Stichtag ist der Kalendertag (Zeitzone Europe/Berlin), der drei Tage vor dem vom Lager gesetzten Eingangstermin (`planned_inbound_on`, ersatzweise vor `expected_on`) liegt; am Stichtag selbst ist bereits gesperrt. Ohne beide Termine gibt es keinen Stichtag. Eine im Lagersystem bereits angelegte Einkaufsbestellung (`zoho_po_state` ungleich `NONE`) sperrt ebenfalls, auch vor dem Stichtag. `customer_can_edit` in der Antwort zeigt, ob noch geändert werden darf.\n\nDas Dokument erscheint danach in `documents` der Anlieferung. Benötigter Scope: `warehouse:document_write` oder `warehouse:write`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nAttaches a document to an inbound shipment, for example the Excel shipping list or a delivery note. The file is sent as `multipart/form-data` in the field `file`, one file per call. Excel files (`.xlsx`) and PDF are allowed, at most 20 MB. The file type is checked on the content, not the file name; CSV and the old `.xls` format are therefore rejected (400, `extra.error` = `INVALID_FILE_TYPE`). A file over 20 MB yields 400 with `extra.error` = `FILE_TOO_LARGE`; if the whole request exceeds 20 MB plus multipart overhead, 413 without `extra.error`. File names over 255 characters are shortened, keeping the extension.\n\n**Preconditions:** as a merchant only with status `ANNOUNCED` (otherwise 409, `extra.error` = `SHIPMENT_NOT_EDITABLE`) and before the cut-off (otherwise 409, `extra.error` = `SHIPMENT_LOCKED`). As a merchant you can change shipments only while the status is `ANNOUNCED` and before the cut-off date. The cut-off is the calendar day (time zone Europe/Berlin) three days before the inbound date set by the warehouse (`planned_inbound_on`, otherwise `expected_on`); the cut-off day itself is already locked. Without either date there is no cut-off. A purchase order already created in the warehouse system (`zoho_po_state` other than `NONE`) also locks the shipment, even before the cut-off. `customer_can_edit` in the response tells whether changes are still allowed.\n\nThe document then appears in `documents` of the shipment. Required scope: `warehouse:document_write` or `warehouse:write`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments/:shipment_id/documents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments",
                ":shipment_id",
                "documents"
              ],
              "variable": [
                {
                  "key": "shipment_id",
                  "value": "",
                  "description": "ID der Anlieferung, an die das Dokument gehängt wird / ID of the inbound shipment the document is attached to."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Remove a shipment document",
          "request": {
            "method": "DELETE",
            "description": "Entfernt ein Dokument von einer Anlieferung. Das Dokument verschwindet aus `documents` und aus `GET …/documents/{document_id}/url` (404); es wird nicht physisch gelöscht, sondern deaktiviert. Bei Erfolg antwortet der Aufruf mit 204 ohne Body.\n\n**Voraussetzungen:** Dieselben Regeln wie beim Hochladen: Als Händler nur bei Status `ANNOUNCED` (sonst 409, `SHIPMENT_NOT_EDITABLE`) und vor dem Stichtag (sonst 409, `SHIPMENT_LOCKED`). Ein unbekanntes oder bereits entferntes Dokument sowie ein Dokument einer anderen Anlieferung ergibt 404. Benötigter Scope: `warehouse:document_write` oder `warehouse:write`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nRemoves a document from an inbound shipment. The document disappears from `documents` and from `GET …/documents/{document_id}/url` (404); it is deactivated, not physically deleted. On success the call answers 204 without a body.\n\n**Preconditions:** the same rules as for upload: as a merchant only with status `ANNOUNCED` (otherwise 409, `SHIPMENT_NOT_EDITABLE`) and before the cut-off (otherwise 409, `SHIPMENT_LOCKED`). An unknown or already removed document, or a document of another shipment, yields 404. Required scope: `warehouse:document_write` or `warehouse:write`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments/:shipment_id/documents/:document_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments",
                ":shipment_id",
                "documents",
                ":document_id"
              ],
              "variable": [
                {
                  "key": "shipment_id",
                  "value": "",
                  "description": "ID der Anlieferung, zu der das Dokument gehört / ID of the inbound shipment the document belongs to."
                },
                {
                  "key": "document_id",
                  "value": "",
                  "description": "ID des zu entfernenden Dokuments (`id` aus `documents` der Anlieferung) / ID of the document to remove (`id` from `documents` of the shipment)."
                }
              ],
              "query": []
            }
          }
        },
        {
          "name": "Get a signed download URL for a shipment document",
          "request": {
            "method": "GET",
            "description": "Erzeugt eine signierte Download-URL für ein Dokument einer Anlieferung. Die URL ist 15 Minuten gültig (`expires_in_seconds` = 900); danach muss eine neue angefordert werden. Wer die URL kennt, kann die Datei in dieser Zeit ohne weitere Anmeldung herunterladen, die URL ist daher vertraulich zu behandeln und nicht zu speichern. Jeder Aufruf erzeugt eine neue URL.\n\nDie URL ist in jedem Status der Anlieferung abrufbar, auch nach Wareneingang oder Storno. Ein unbekanntes oder entferntes Dokument sowie ein Dokument einer anderen Anlieferung ergibt 404. Benötigter Scope: `warehouse:read`. Ist die Anlieferungsfunktion für dieses Konto nicht verfügbar, antworten alle Aufrufe mit 404.\n\nCreates a signed download URL for a document of an inbound shipment. The URL is valid for 15 minutes (`expires_in_seconds` = 900); after that a new one must be requested. Anyone who knows the URL can download the file during that time without further authentication, so treat the URL as confidential and do not store it. Every call creates a new URL.\n\nThe URL can be requested in every shipment status, also after goods receipt or cancellation. An unknown or removed document, or a document of another shipment, yields 404. Required scope: `warehouse:read`. If the inbound shipment feature is not available for this account, every call answers 404.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/warehouse/shipments/:shipment_id/documents/:document_id/url",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "warehouse",
                "shipments",
                ":shipment_id",
                "documents",
                ":document_id",
                "url"
              ],
              "variable": [
                {
                  "key": "shipment_id",
                  "value": "",
                  "description": "ID der Anlieferung, zu der das Dokument gehört / ID of the inbound shipment the document belongs to."
                },
                {
                  "key": "document_id",
                  "value": "",
                  "description": "ID des Dokuments (`id` aus `documents` der Anlieferung) / ID of the document (`id` from `documents` of the shipment)."
                }
              ],
              "query": []
            }
          }
        }
      ]
    }
  ]
}
