openapi: 3.1.0
info:
  title: Nucleo WMS T-Data API
  version: v1
  summary: T-Data compatible /V1 facade that a warehouse management system polls to fulfil Nucleo Commerce orders and returns.
  description: |
    The **WMS T-Data facade** lets a warehouse or 3PL management system (WMS) fulfil the orders of a Nucleo Commerce merchant
    using the T-Data "Integration MoR/WMS" `/V1` contract: same paths, same methods, same JSON fields. A WMS that already
    speaks T-Data only changes the base URL and the credentials.

    **The warehouse is always the caller.** Nucleo never calls the WMS: the WMS polls for new orders and expected returns and
    pushes acknowledgements, picking results, shipments, stock levels and return outcomes.

    **Authentication.** Every call carries the credentials of one warehouse connection: an API key in `X-Api-Key`
    (recommended), the same key as `Authorization: Bearer <key>`, or HTTP Basic. The credentials alone identify the merchant
    and the warehouse; there is no store identifier in the path.

    ## Integration flow

    1. **Download orders** – `GET /V1/Orders/New` every 1–5 minutes. An order is returned at every call until you acknowledge it.
    2. **Acknowledge** – `POST /V1/Orders/Acknowledge` with `Success: true` (taken over) or `Success: false` plus a rejection code.
    3. **Picking result** – `POST /V1/Orders/Processed` with picked quantities and parcels (full or partial pick).
    4. **Shipment** – `POST /V1/Orders/Shipped` with courier and waybill. Nucleo creates the fulfilment on the sales channel and
       the customer receives the tracking.
    5. **Cannot fulfil** – `POST /V1/Orders/Canceled` when an acknowledged order cannot be shipped.
    6. **Stock** – `PUT /V1/Stock/Update` with absolute levels, full or only the changed items, as often as stock changes.
    7. **Returns** – `GET /V1/Returns/New` → `POST /V1/Returns/Acknowledge` → `POST /V1/Return/Update` with the per-line
       outcome after inspection; `POST /V1/Returns/Canceled` for an expected return that will not arrive.
    8. **Optional services** – courier labels produced by Nucleo (`/V1/Labels/*`), order documents (`/V1/Documents/Get`) and
       the WMS item registry (`/V1/Catalogue`) are switched off by default and answer `404` until the merchant enables them.

    ## Conventions

    - **Paths are case-insensitive**: `/V1/Orders/New`, `/v1/orders/new` and `/V1/ORDERS/NEW` are the same endpoint.
      Aliases: `/V1/Orders/Cancel` = `/V1/Orders/Canceled`, `/V1/Returns/Update` = `/V1/Return/Update`.
    - **JSON in, JSON out** (UTF-8). The request body is parsed as JSON whatever the `Content-Type`; send `application/json`.
    - **Tolerant input**, as in the T-Data samples: field names are case-insensitive; `ErrorcCode` and `ErrorCode` are equivalent;
      booleans are accepted as `true`/`false`, `"True"`/`"False"`, `1`/`0`; a trailing comma before `}` or `]` is accepted;
      `ProductList` may be an array or an object wrapping the array.
    - **Dates**: Nucleo sends ISO 8601 UTC with `Z` (`2026-10-05T08:12:30Z`). It accepts ISO 8601 with offset and optional
      milliseconds (`2026-10-05T16:54:40.408+02:00`). A date **without offset is read as UTC**: always send the offset.
    - **Order and return numbers**: `OmsOrderNumber` is the channel order name exactly as the merchant sees it, including a
      leading `#` (`#1042`); encode it as `%23` in query strings. `OmsReturnNumber` is the Nucleo return name.
    - **Item key**: `Sku` is the item code agreed for the connection, by default the variant barcode (EAN), both ways.
    - **No pagination**: `Orders/New` and `Returns/New` return at most 500 items per call, oldest first. The rest arrive on the
      next calls once you acknowledge what you received.
    - **Validation is all-or-nothing**: in calls carrying several items (an Acknowledge array, a Stock/Update list) a single
      invalid item rejects the whole call and nothing is applied.
    - **Retries are safe**: every write can be repeated after a timeout without double effects (see each operation).

    ## Responses and errors

    | HTTP | Body | When |
    |---|---|---|
    | 200 | `{"Success":true,"ErrorMessage":""}` | write accepted |
    | 200 | `{"ErrorCode":"LabelNotReady","success":false}` | business error (labels and documents only) |
    | 400 | `{"ErrorCode":"Exception","Message":"<reason>"}` | invalid JSON, missing field, unknown order or return |
    | 401 | `{"ErrorCode":"Exception","Message":"Unauthorized"}` | missing or wrong credentials |
    | 403 | `{"ErrorCode":"Exception","Message":"Forbidden"}` | caller IP not in the connection allowlist |
    | 404 | `{"ErrorCode":"Exception","Message":"Not found"}` | unknown path, or optional service switched off |
    | 405 | `{"ErrorCode":"Exception","Message":"Method not allowed"}` | wrong method for a known path |
    | 429 | `{"ErrorCode":"Exception","Message":"Too many requests"}` | rate limit exceeded |
    | 500 | `{"ErrorCode":"Exception","Message":"service paused"}` | connection paused by the merchant (`service not active` while still a draft): nothing read or written, retry later |
    | 500 | `{"ErrorCode":"Exception","Message":"Internal error (ref <uuid>)"}` | unexpected error; quote the `ref` to support |

    Credentials must be sent on every request without waiting for a challenge: a `401` carries no `WWW-Authenticate` header.

    ## Limits and monitoring

    - **600 calls per minute per connection** (all endpoints together; the merchant can change it). No `Retry-After` or
      `X-RateLimit-*` headers are sent: on `429` back off for at least 60 seconds.
    - **30 failed authentications within 5 minutes from one IP** block that IP for the rest of the window, valid credentials included.
    - Every call is logged in full (request and response) before Nucleo answers, so support can find any call by time, path or order.
    - If the WMS stays silent for more than 30 minutes while orders are waiting, Nucleo flags the connection as **Error** and
      alerts the merchant; the next successful call clears it.

    ## Credentials

    The merchant creates a warehouse connection of type T-Data in **Commerce › Orders › Channels and logistics**. Nucleo shows
    the endpoint, username, password and API key **once**; the merchant shares them with you over a separate channel.
    **Rotate credentials** on the same page invalidates the old ones immediately. A new connection starts as a draft and answers
    `service not active` until the merchant activates it.
  contact:
    name: Nucleo developer support
    url: https://nucleoplatform.com
x-nucleo:
  product: wms-tdata
  module: commerce
  audience: [warehouse]
  stability: beta
  format: json
  order: 40
servers:
  - url: https://{host}
    description: Production. Use the endpoint shown when the credentials were issued (without the trailing `/V1`).
    variables:
      host:
        default: api-commerce.nucleoplatform.com
        description: Commerce API host. If the merchant sets a dedicated host on the connection, the connection answers only on that host; paths do not change.
security:
  - apiKey: []
  - bearerKey: []
  - basicAuth: []
tags:
  - name: Orders
    description: Download orders, acknowledge them, report picking, shipment or impossibility to fulfil.
  - name: Stock
    description: Push stock levels from the warehouse.
  - name: Returns
    description: Download expected returns, acknowledge them, report the inspection outcome.
  - name: Labels and documents
    description: Optional services, off by default. Courier labels generated by Nucleo, order documents and the WMS item registry.
paths:
  /V1/Orders/New:
    get:
      operationId: listNewOrders
      summary: List orders to fulfil
      tags: [Orders]
      description: |
        Returns the orders allocated to this warehouse that are paid, free of holds and **not yet acknowledged** (positively or
        negatively) by this connection. No parameters.

        - An order is returned at **every** call until `Orders/Acknowledge` arrives: if a response is lost, the order comes back.
        - `ProductList` contains only the lines allocated to this warehouse, with the quantity still open. For an order split
          across warehouses you see only your part.
        - An order cancelled on the sales channel before you acknowledge it disappears from the list.
        - At most 500 orders per call, oldest order date first.
        - An order never reaches the warehouse while it is on hold (payment pending, hold tag, fraud risk, manual hold, no
          carrier rule for the destination, line without item key). When documents are enabled, an order that needs external
          documents is returned only once they are ready.
        - Side effect: the first time an order is returned it moves to **Exported**. Until it is acknowledged Nucleo keeps
          subtracting it from your stock before publishing availability (see `Stock/Update`).
        - With Nucleo-managed courier labels enabled, the return label (`IdLabel` 0) and the first parcel label (`IdLabel` 1)
          are generated when the order is first returned.

        Note that the body carries `"StatusCode": 201` while the HTTP status is 200, as in the T-Data contract.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      responses:
        '200':
          description: Orders waiting for acknowledgement (possibly none).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrdersNewResponse'
              examples:
                orders:
                  summary: One order with two lines
                  value:
                    Content:
                      - OrderData:
                          OmsOrderNumber: '#1042'
                          OmsCustomerId: '7712345678'
                          OrderDateTime: '2026-10-05T08:12:30Z'
                          HasExternalDocuments: 'false'
                          PreparationType: '001'
                          OrderType: B2C
                          SelectedCourier: GLS
                          Priority: 3
                          UserLanguage: it-IT
                          ShipmentType: 1
                        ShippingData:
                          FirstName: Giulia
                          LastName: Bianchi
                          EmailAddress: giulia.bianchi@example.com
                          PhoneNumber: '+39 333 0000000'
                          Address:
                            City: Milano
                            CountryCode: IT
                            PostalCode: '20121'
                            StateOrProvinceCode: MI
                            Street: Via Roma 1 Scala B
                            Notes: 'Ring twice — Company: Acme Apparel Srl'
                        ProductList:
                          - Sku: '8000000000017'
                            Quantity: 1
                          - Sku: '8000000000024'
                            Quantity: 2
                    StatusCode: 201
                    Success: true
                    Message: ''
                empty:
                  summary: Nothing to fulfil
                  value:
                    Content: []
                    StatusCode: 201
                    Success: true
                    Message: ''
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '405': {$ref: '#/components/responses/MethodNotAllowed'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Orders/Acknowledge:
    post:
      operationId: acknowledgeOrders
      summary: Accept or reject orders
      tags: [Orders]
      description: |
        Confirms (or rejects) that the warehouse took over one or more orders. Accepts a single object or an array.

        - `Success: true` → the order is **acknowledged**, leaves `Orders/New`, and `WmsOrderNumber` is stored and shown on the
          order. From now on Nucleo no longer subtracts the order from your stock: your next stock levels must already exclude it.
        - `Success: false` → the order is **rejected by the warehouse** with the code in `ErrorcCode`; the merchant is alerted,
          fixes the order and re-releases it, and it reappears in `Orders/New`.
        - If the order was cancelled on the sales channel between your download and your acknowledgement, the acknowledgement is
          accepted but the merchant is alerted to stop the order with you manually (the contract has no cancel call towards the WMS).

        **Validation first**: every item is checked before any is applied; an unknown order (or an order not allocated to this
        warehouse) or a non-boolean `Success` rejects the whole call with `400`.

        **Idempotent**: repeating the same acknowledgement has no further effect.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/OrderAcknowledge'
                - type: array
                  minItems: 1
                  items:
                    $ref: '#/components/schemas/OrderAcknowledge'
            examples:
              batch:
                summary: One accepted, one rejected
                value:
                  - OmsOrderNumber: '#1042'
                    WmsOrderNumber: WMS-778812
                    Success: true
                    ErrorcCode: ''
                  - OmsOrderNumber: '#1043'
                    WmsOrderNumber: ''
                    Success: false
                    ErrorcCode: '002'
              single:
                summary: Single object
                value:
                  OmsOrderNumber: '#1042'
                  WmsOrderNumber: WMS-778812
                  Success: 'True'
                  ErrorcCode: ''
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400':
          description: Invalid body or unknown order; nothing applied.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                orderNotFound:
                  value: {ErrorCode: Exception, Message: 'Order not found: #9999'}
                notBoolean:
                  value: {ErrorCode: Exception, Message: 'Success must be a boolean for #1042'}
                invalidJson:
                  value: {ErrorCode: Exception, Message: Invalid JSON body}
                invalidItem:
                  value: {ErrorCode: Exception, Message: Invalid acknowledge item}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Orders/Processed:
    post:
      operationId: reportOrderProcessed
      summary: Report picked quantities and parcels
      tags: [Orders]
      description: |
        Picking is complete, fully or partially. Send the picked quantity per item (with lot and serial when relevant) and the
        parcels with weight and dimensions.

        - The order moves to **Processed**; picked quantities, lots, serials and parcels are stored.
        - A line picked for less than its open quantity is marked **unfulfillable** for the difference and the merchant is alerted
          (partial fulfilment). Refunding unfulfilled lines is done by the merchant, or automatically if they enabled it.
        - Quantities are matched to lines by item key; several `ProductList` entries for the same `Sku` are summed.
        - Send it before `Orders/Shipped`: the shipped quantities are the picked ones.

        **Idempotent**: repeating the call **replaces** the previous values, it does not add to them.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/OrderProcessed'}
            examples:
              partial:
                summary: Second line picked 1 of 2, two parcels
                value:
                  OmsOrderNumber: '#1042'
                  EventDateTime: '2026-10-05T16:54:40.408+02:00'
                  ParcelQty: 2
                  ProductList:
                    - {Sku: '8000000000017', Quantity: 1, LotNumber: '', LotExpirationDate: '', SerialNumber: ''}
                    - {Sku: '8000000000024', Quantity: 1}
                  ParcelList:
                    - {idParcel: WmsId.0AA01, CourierLabelId: GLS0000000001, ParcelWeightGr: 820, ParcelHeightMm: 120, ParcelWidthMm: 300, ParcelDepthMm: 400}
                    - {idParcel: WmsId.0AA02, CourierLabelId: GLS0000000002, ParcelWeightGr: 410, ParcelHeightMm: 100, ParcelWidthMm: 250, ParcelDepthMm: 350}
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400': {$ref: '#/components/responses/OrderBadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Orders/Shipped:
    post:
      operationId: reportOrderShipped
      summary: Report shipment with courier and waybill
      tags: [Orders]
      description: |
        The order left the warehouse.

        - The order moves to **Shipped**; Nucleo records courier, waybill, total weight and volume, and the return waybill.
        - Shipped quantities are those reported in `Orders/Processed`; without it, all open lines allocated to this warehouse.
        - Nucleo creates the fulfilment on the sales channel with courier and tracking; the shipping email to the customer follows
          the merchant's channel settings. `CourierCode` is translated to the channel's carrier name when the merchant has mapped it
          (for example `DHL` → `DHL Express`); unknown codes are passed through as they are.
        - With an empty `TrackingUrl`, Nucleo builds the tracking link from the carrier when it knows how.
        - `NeedsCloseWorkDay` is recorded; it has an effect only when courier labels are managed by Nucleo.
        - If the order was being cancelled, the shipment is recorded anyway and the merchant is alerted.

        `CourierCode` and `CourierWaybill` are **required** when the courier is managed by the warehouse (the default).

        **Idempotent**: the same call repeated with the same `CourierWaybill` creates no second shipment and no second email.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/OrderShipped'}
            examples:
              shipped:
                value:
                  OmsOrderNumber: '#1042'
                  EventDateTime: '2026-10-05T18:00:00+02:00'
                  CourierCode: GLS
                  CourierWaybill: GLS0000000001
                  ReturnCourierCode: GLS
                  ReturnCourierWaybill: GLS0000000099
                  NeedsCloseWorkDay: 'False'
                  ShipmentTotalWeightGr: 1230
                  ShipmentTotalVolumeCm3: 6000
                  TrackingUrl: ''
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400':
          description: Invalid body, unknown order or missing courier data.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                courierRequired:
                  value: {ErrorCode: Exception, Message: CourierCode and CourierWaybill are required}
                orderNotFound:
                  value: {ErrorCode: Exception, Message: 'Order not found: #9999'}
                invalidJson:
                  value: {ErrorCode: Exception, Message: Invalid JSON body}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Orders/Canceled:
    post:
      operationId: reportOrderCanceled
      summary: Declare an order unfulfillable
      tags: [Orders]
      description: |
        The warehouse cannot fulfil the order. The order moves to **Cancelled by warehouse** and the merchant is alerted to cancel
        and refund it on the sales channel (automatically, if the merchant enabled it; no restock is made, stock comes from your next
        `Stock/Update`). Alias: `POST /V1/Orders/Cancel`.

        **Idempotent**: repeating the call has no further effect. On an order whose state no longer allows a cancellation (for
        example already shipped) Nucleo answers `500 Internal error (ref …)`.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/OrderCanceled'
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400': {$ref: '#/components/responses/OrderBadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Orders/Cancel:
    post:
      operationId: reportOrderCancel
      summary: Declare an order unfulfillable (alias)
      tags: [Orders]
      description: |
        Alias of `POST /V1/Orders/Canceled`, same body, behaviour and responses.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/OrderCanceled'
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400': {$ref: '#/components/responses/OrderBadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Stock/Update:
    put:
      operationId: updateStock
      summary: Push stock levels
      tags: [Stock]
      description: |
        Absolute stock levels per item, classification and warehouse. `POST` is accepted as well as `PUT`.

        - `StockLevel` is an **absolute** quantity, not a variation. It must already exclude the orders you have acknowledged.
          Nucleo subtracts the orders you have not acknowledged yet, then publishes availability to the sales channels.
        - Send everything or only what changed: **items not sent are not zeroed**. To zero an item, send it with `StockLevel: 0`.
        - Only `Sellable` (case-insensitive; the default when missing) counts as sellable. Other classifications (for example
          `Unsellable`) are stored separately.
        - **Out-of-order protection**: a value whose `EventDateTime` is older than the last one applied for the same item and
          classification is ignored (the call still answers `Success`).
        - An unknown `Sku` is skipped and the merchant is alerted; the rest of the call is applied.
        - `IdWarehouse` defaults to the warehouse code configured on the connection (`001` unless the merchant changed it).
        - `Success` means the levels are stored; publication to the channels follows asynchronously.

        **Idempotent**: repeating the call re-applies the same values.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/StockUpdate'
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400':
          description: Invalid body or invalid item; nothing stored.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                invalidItem:
                  value: {ErrorCode: Exception, Message: 'ProductList[2]: Sku and numeric StockLevel are required'}
                invalidJson:
                  value: {ErrorCode: Exception, Message: Invalid JSON body}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
    post:
      operationId: updateStockPost
      summary: Push stock levels (POST)
      tags: [Stock]
      description: |
        Same as `PUT /V1/Stock/Update`, for clients that cannot send `PUT`.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/StockUpdate'
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400':
          description: Invalid body or invalid item; nothing stored.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              example: {ErrorCode: Exception, Message: 'ProductList[0]: Sku and numeric StockLevel are required'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Returns/New:
    get:
      operationId: listNewReturns
      summary: List expected returns
      tags: [Returns]
      description: |
        Returns announced by the customer and expected at this warehouse, not yet acknowledged. A return is routed to the
        warehouse of its return location when the merchant set one, otherwise to the warehouse that shipped the order.
        No parameters.

        - A return is listed at every call until `Returns/Acknowledge` arrives (positive or negative).
        - At most 500 per call, oldest first. Reading has no side effect.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      responses:
        '200':
          description: Expected returns (possibly none).
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ReturnsNewResponse'}
              examples:
                returns:
                  value:
                    Content:
                      - OmsOrderNumber: '#1042'
                        OmsReturnNumber: '#1042-R1'
                        EventDateTime: '2026-10-07T07:41:02Z'
                        ProductList:
                          - {Sku: '8000000000017', QuantityReturned: 1}
                    Success: true
                    ErrorMessage: ''
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '405': {$ref: '#/components/responses/MethodNotAllowed'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Returns/Acknowledge:
    post:
      operationId: acknowledgeReturns
      summary: Accept or reject expected returns
      tags: [Returns]
      description: |
        Single object or array. With `Success: true` the return becomes **awaited at warehouse**; with `Success: false` it stays in
        Nucleo with your code and the merchant is alerted. In both cases it leaves `Returns/New`.

        **Validation first**: an unknown return or a non-boolean `Success` rejects the whole call with `400`.

        **Idempotent**: repeating the call has no further effect.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/ReturnAcknowledge'
                - type: array
                  minItems: 1
                  items: {$ref: '#/components/schemas/ReturnAcknowledge'}
            examples:
              accepted:
                value: {OmsReturnNumber: '#1042-R1', Success: true, ErrorcCode: ''}
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400':
          description: Invalid body or unknown return; nothing applied.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                returnNotFound:
                  value: {ErrorCode: Exception, Message: 'Return not found: #9999-R1'}
                notBoolean:
                  value: {ErrorCode: Exception, Message: 'Success must be a boolean for #1042-R1'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Return/Update:
    post:
      operationId: reportReturnOutcome
      summary: Report the inspection outcome of a return
      tags: [Returns]
      description: |
        Per-line outcome once the parcel has been received and inspected. Alias: `POST /V1/Returns/Update`.

        - `ExternalReference1` must carry the `OmsReturnNumber` of the expected return.
        - The return is **compliant** when exactly the expected items and quantities arrived and every received line is
          `Sellable`; Nucleo records the positive outcome and forwards it for the customer refund. Otherwise it is
          **not compliant** and goes to the merchant's customer service. A line without `Classification` makes it not compliant.
        - `ReturnCode` is stored on the line and, when the merchant mapped it, sets the return reason.
        - If `ExternalReference1` matches no expected return of that order (a parcel arrived unannounced), Nucleo creates an
          **unannounced return** on the order and alerts the merchant.
        - If the return had been cancelled or already closed, the goods are recorded, the status does not change and the
          merchant is alerted.
        - `AquisitionDateTime` is spelt as in the T-Data contract (no "c").

        **Idempotent**: a second call on the same return recomputes the outcome; repeating the same unannounced return does
        not create a second one.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/ReturnUpdate'
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400': {$ref: '#/components/responses/ReturnUpdateBadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Returns/Update:
    post:
      operationId: reportReturnOutcomeAlias
      summary: Report the inspection outcome (alias)
      tags: [Returns]
      description: |
        Alias of `POST /V1/Return/Update`, same body, behaviour and responses.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/ReturnUpdate'
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400': {$ref: '#/components/responses/ReturnUpdateBadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Returns/Canceled:
    post:
      operationId: reportReturnCanceled
      summary: Cancel an expected return
      tags: [Returns]
      description: |
        The expected return will not arrive. It moves to **Cancelled** and the cancellation is forwarded to the merchant's
        returns provider. If the parcel arrives anyway, a later `Return/Update` records the goods and alerts the merchant.

        **Idempotent**: repeating the call has no further effect.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ReturnCanceled'}
            examples:
              canceled:
                value: {OmsReturnNumber: '#1042-R1', EventDateTime: '2026-10-08T10:00:00+02:00', Reason: The customer did not ship the parcel}
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400':
          description: Invalid body or unknown return.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                returnNotFound:
                  value: {ErrorCode: Exception, Message: 'Return not found: #9999-R1'}
                invalidJson:
                  value: {ErrorCode: Exception, Message: Invalid JSON body}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Labels/Get:
    get:
      operationId: getLabels
      summary: Download courier labels generated by Nucleo
      tags: [Labels and documents]
      description: |
        **Optional, off by default.** Available only when the merchant set the connection to have courier labels managed by
        Nucleo; otherwise it answers `404 Labels are managed by the WMS`.

        Label numbering: `IdLabel` 0 is the return label (`Position` `Internal`, to put inside the parcel), 1 is the first
        parcel (generated when the order is first returned by `Orders/New`), 2–10 are additional parcels (created by
        `Labels/Add` or by `Orders/Processed` with `ParcelQty` > 1).

        Without `IdLabel` all labels of the order are returned. If any requested label is still being generated the answer is
        the business error `LabelNotReady` (HTTP 200): retry a little later. `FileType` is always `Zpl`; `Content` is base64.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      parameters:
        - name: OmsOrderNumber
          in: query
          required: true
          description: Order name, `#` encoded as `%23`.
          schema: {type: string}
          example: '#1042'
        - name: IdLabel
          in: query
          required: false
          description: A single label, 0–10.
          schema: {type: integer, minimum: 0, maximum: 10}
          example: 1
      responses:
        '200':
          description: Labels, or the business error `LabelNotReady`.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/FilesResponse'
                  - $ref: '#/components/schemas/BusinessError'
              examples:
                labels:
                  value:
                    Status: success
                    Message: File retrieved successfully.
                    Data:
                      - {IdLabel: 0, idParcel: null, CourierLabelId: GLS0000000099, Position: Internal, FileType: Zpl, Content: XlhBXkZPNTAsNTBeQURO...}
                      - {IdLabel: 1, idParcel: WmsId.0AA01, CourierLabelId: GLS0000000001, Position: External, FileType: Zpl, Content: XlhBXkZPNTAsNTBeQURO...}
                notReady:
                  value: {ErrorCode: LabelNotReady, success: false}
        '400':
          description: Unknown order or label.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                orderNotFound:
                  value: {ErrorCode: Exception, Message: Order not found}
                labelNotFound:
                  value: {ErrorCode: Exception, Message: Label not found}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '404':
          description: Labels are produced by the warehouse for this connection (default).
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              example: {ErrorCode: Exception, Message: Labels are managed by the WMS}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Labels/Add:
    post:
      operationId: addLabel
      summary: Request an additional parcel label
      tags: [Labels and documents]
      description: |
        **Optional, off by default** (see `Labels/Get`). Generates label `IdLabel` (0–10) for the order. Then download it with
        `Labels/Get`.

        Business errors (HTTP 200): `LabelAlreadyAdded` when that label exists already, `LabelNotReady` when the carrier did not
        return it yet (retry `Labels/Get` later).

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [OmsOrderNumber, IdLabel]
              properties:
                OmsOrderNumber: {type: string, example: '#1042'}
                IdLabel: {type: integer, minimum: 0, maximum: 10, example: 2}
            example: {OmsOrderNumber: '#1042', IdLabel: 2}
      responses:
        '200':
          description: Label generated, or a business error.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Ok'
                  - $ref: '#/components/schemas/BusinessError'
              examples:
                ok:
                  value: {Success: true, ErrorMessage: ''}
                alreadyAdded:
                  value: {ErrorCode: LabelAlreadyAdded, success: false}
                notReady:
                  value: {ErrorCode: LabelNotReady, success: false}
        '400':
          description: Missing or invalid fields.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                required:
                  value: {ErrorCode: Exception, Message: OmsOrderNumber and IdLabel are required}
                range:
                  value: {ErrorCode: Exception, Message: IdLabel must be between 0 and 10}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '404':
          description: Labels are produced by the warehouse for this connection (default).
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              example: {ErrorCode: Exception, Message: Labels are managed by the WMS}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Documents/Get:
    get:
      operationId: getDocuments
      summary: Download documents to print for an order
      tags: [Labels and documents]
      description: |
        **Optional, off by default**: answers `404 Documents are not enabled` until the merchant enables documents.

        Returns the documents to print for the order: the merchant's active templates in the destination language (for example
        a packing note) and the order's own documents (for example invoices for non-EU destinations). `Position` says where the
        document goes (`Internal` inside the parcel, `External` on the outside). `Content` is a base64 file.

        When documents are enabled, an order whose destination needs external documents (by default CH and GB) appears in
        `Orders/New` with `HasExternalDocuments: "true"` only once they are ready; until then this endpoint answers the business
        error `DocumentNotReady` (HTTP 200).

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      parameters:
        - name: OmsOrderNumber
          in: query
          required: true
          description: Order name, `#` encoded as `%23`.
          schema: {type: string}
          example: '#1042'
      responses:
        '200':
          description: Documents, or the business error `DocumentNotReady`.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/FilesResponse'
                  - $ref: '#/components/schemas/BusinessError'
              examples:
                documents:
                  value:
                    Status: success
                    Message: File retrieved successfully.
                    Data:
                      - {IdDocument: PackingNote, Position: Internal, FileType: A4, Content: JVBERi0xLjQK...}
                      - {IdDocument: Invoice1042, Position: External, FileType: A4, Content: JVBERi0xLjQK...}
                notReady:
                  value: {ErrorCode: DocumentNotReady, success: false}
        '400':
          description: Unknown order.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              example: {ErrorCode: Exception, Message: Order not found}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '404':
          description: Documents are not enabled for this connection (default).
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              example: {ErrorCode: Exception, Message: Documents are not enabled}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
  /V1/Catalogue:
    post:
      operationId: registerCatalogue
      summary: Register WMS item codes against EANs
      tags: [Labels and documents]
      description: |
        **Optional, off by default**: answers `404 Catalogue is not enabled` unless the merchant identifies items by a WMS code
        instead of the EAN. Upserts the WMS item registry (`Sku` ↔ `EAN`, title, category, weight and dimensions). An EAN not
        found in the merchant catalogue is stored and the merchant is alerted.

        **Idempotent**: items are upserted by `Sku`.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/Catalogue'}
            example:
              IdWarehouse: '001'
              ProductList:
                - {Sku: TEE-BLK-M, EAN: '8000000000017', Title: T-shirt Black M, Category: T-shirts, WeightGr: 180, HeightMm: 20, WidthMm: 250, DepthMm: 300}
      responses:
        '200': {$ref: '#/components/responses/Ok'}
        '400':
          description: Invalid body or item.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              examples:
                skuRequired:
                  value: {ErrorCode: Exception, Message: 'ProductList[0]: Sku is required'}
                invalidJson:
                  value: {ErrorCode: Exception, Message: Invalid JSON body}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '403': {$ref: '#/components/responses/Forbidden'}
        '404':
          description: Catalogue registry not enabled for this connection (default).
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Exception'}
              example: {ErrorCode: Exception, Message: Catalogue is not enabled}
        '429': {$ref: '#/components/responses/TooManyRequests'}
        '500': {$ref: '#/components/responses/ServerError'}
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-Api-Key
      description: |
        48-character API key of the warehouse connection (recommended). Issued once by the merchant in
        Commerce › Orders › Channels and logistics, together with the username and password.
    bearerKey:
      type: http
      scheme: bearer
      description: 'The same API key sent as `Authorization: Bearer <key>`.'
    basicAuth:
      type: http
      scheme: basic
      description: Username and password of the warehouse connection, sent preemptively on every call.
  requestBodies:
    OrderCanceled:
      required: true
      content:
        application/json:
          schema:
            type: object
            required: [OmsOrderNumber]
            properties:
              OmsOrderNumber: {type: string, description: Order name., example: '#1042'}
              EventDateTime: {type: string, format: date-time, description: When the warehouse declared it unfulfillable. Defaults to now., example: '2026-10-05T11:20:00+02:00'}
          example: {OmsOrderNumber: '#1043', EventDateTime: '2026-10-05T11:20:00+02:00'}
    StockUpdate:
      required: true
      content:
        application/json:
          schema: {$ref: '#/components/schemas/StockUpdate'}
          examples:
            delta:
              summary: Two classifications of one item and a zeroed item
              value:
                IdWarehouse: '001'
                ProductList:
                  - {Sku: '8000000000017', StockLevel: 100, Classification: Sellable, EventDateTime: '2026-10-05T10:00:00.408+02:00'}
                  - {Sku: '8000000000017', StockLevel: 3, Classification: Unsellable, EventDateTime: '2026-10-05T10:00:00+02:00'}
                  - {Sku: '8000000000024', StockLevel: 0, Classification: Sellable, EventDateTime: '2026-10-05T10:00:00+02:00'}
    ReturnUpdate:
      required: true
      content:
        application/json:
          schema: {$ref: '#/components/schemas/ReturnUpdate'}
          examples:
            compliant:
              summary: Expected return, everything sellable
              value:
                OmsOrderNumber: '#1042'
                EventDateTime: '2026-10-09T16:54:40.408+02:00'
                AquisitionDateTime: '2026-10-09T09:10:00+02:00'
                ExternalReference1: '#1042-R1'
                ExternalReference2: ''
                ProductList:
                  - {Sku: '8000000000017', QuantityReturned: 1, Classification: Sellable, ReturnCode: ''}
            notCompliant:
              summary: Damaged item
              value:
                OmsOrderNumber: '#1042'
                ExternalReference1: '#1042-R1'
                ProductList:
                  - {Sku: '8000000000017', QuantityReturned: 1, Classification: Unsellable, ReturnCode: A}
  responses:
    Ok:
      description: Accepted.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Ok'}
          example: {Success: true, ErrorMessage: ''}
    OrderBadRequest:
      description: Invalid body (not a JSON object) or order unknown / not allocated to this warehouse.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Exception'}
          examples:
            orderNotFound:
              value: {ErrorCode: Exception, Message: 'Order not found: #9999'}
            invalidJson:
              value: {ErrorCode: Exception, Message: Invalid JSON body}
    ReturnUpdateBadRequest:
      description: Invalid body, unknown order or item without `Sku`.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Exception'}
          examples:
            orderNotFound:
              value: {ErrorCode: Exception, Message: 'Order not found: #9999'}
            skuRequired:
              value: {ErrorCode: Exception, Message: 'ProductList[0]: Sku is required'}
            invalidJson:
              value: {ErrorCode: Exception, Message: Invalid JSON body}
    Unauthorized:
      description: Missing or wrong credentials. No `WWW-Authenticate` header is sent.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Exception'}
          example: {ErrorCode: Exception, Message: Unauthorized}
    Forbidden:
      description: The caller IP is not in the connection allowlist (only when the merchant set one).
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Exception'}
          example: {ErrorCode: Exception, Message: Forbidden}
    MethodNotAllowed:
      description: Known path called with the wrong method.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Exception'}
          example: {ErrorCode: Exception, Message: Method not allowed}
    TooManyRequests:
      description: |
        More than 600 calls in a minute on this connection, or more than 30 failed authentications in 5 minutes from this IP.
        No `Retry-After` or `X-RateLimit-*` headers are sent.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Exception'}
          example: {ErrorCode: Exception, Message: Too many requests}
    ServerError:
      description: Connection paused or still a draft (nothing read or written, retry later), or unexpected error with a reference.
      content:
        application/json:
          schema: {$ref: '#/components/schemas/Exception'}
          examples:
            paused:
              value: {ErrorCode: Exception, Message: service paused}
            draft:
              value: {ErrorCode: Exception, Message: service not active}
            internal:
              value: {ErrorCode: Exception, Message: Internal error (ref 3f2b8c1e-5a7d-4e2f-9b61-0c8d4a2e7f10)}
  schemas:
    Ok:
      type: object
      required: [Success, ErrorMessage]
      properties:
        Success: {type: boolean, const: true}
        ErrorMessage: {type: string, example: ''}
    Exception:
      type: object
      required: [ErrorCode, Message]
      properties:
        ErrorCode: {type: string, const: Exception}
        Message: {type: string, example: 'Order not found: #9999'}
    BusinessError:
      type: object
      description: Business error, returned with HTTP 200. Note the lowercase `success`.
      required: [ErrorCode, success]
      properties:
        ErrorCode: {type: string, enum: [LabelNotReady, LabelAlreadyAdded, DocumentNotReady]}
        success: {type: boolean, const: false}
    OrdersNewResponse:
      type: object
      required: [Content, StatusCode, Success, Message]
      properties:
        Content:
          type: array
          maxItems: 500
          items: {$ref: '#/components/schemas/NewOrder'}
        StatusCode: {type: integer, const: 201}
        Success: {type: boolean, const: true}
        Message: {type: string}
    NewOrder:
      type: object
      required: [OrderData, ShippingData, ProductList]
      properties:
        OrderData:
          type: object
          properties:
            OmsOrderNumber: {type: string, description: Channel order name; the key for every later call., example: '#1042'}
            OmsCustomerId: {type: [string, 'null'], description: Numeric part of the channel customer id; null for guest orders., example: '7712345678'}
            OrderDateTime: {type: string, format: date-time, description: 'Order date, UTC.', example: '2026-10-05T08:12:30Z'}
            HasExternalDocuments: {type: string, enum: ['true', 'false'], description: 'String, as in the T-Data contract. Always "false" while documents are off.'}
            PreparationType: {type: string, description: Preparation code from the merchant's order-tag mapping; default "001"., example: '001'}
            OrderType: {type: string, const: B2C}
            SelectedCourier: {type: string, description: Carrier chosen by the merchant's carrier rules in Nucleo., example: GLS}
            Priority: {type: integer, description: 'Order priority: merchant-set, by destination country, or 1 for express and 3 for standard by default.', example: 3}
            UserLanguage: {type: string, description: 'Language from the shipping country (GB en-US, DE/AT de-DE, ES/PT es-ES, FR/CH/BE fr-FR, IT it-IT; others en-US unless the merchant changed it).', example: it-IT}
            ShipmentType: {type: integer, enum: [1, 2], description: '1 standard, 2 express.'}
        ShippingData:
          type: object
          properties:
            FirstName: {type: string}
            LastName: {type: string}
            EmailAddress: {type: string, format: email}
            PhoneNumber: {type: string, description: 'Shipping address phone, else the customer phone.'}
            Address:
              type: object
              properties:
                City: {type: string}
                CountryCode: {type: string, description: ISO 3166-1 alpha-2., example: IT}
                PostalCode: {type: string}
                StateOrProvinceCode: {type: string}
                Street: {type: string, description: Address lines 1 and 2 joined by a space.}
                Notes: {type: string, description: 'Notes for the warehouse; when the address has a company, "Company: <name>" is appended.'}
        ProductList:
          type: array
          description: Only the lines allocated to this warehouse, with the open quantity.
          items:
            type: object
            required: [Sku, Quantity]
            properties:
              Sku: {type: string, description: Item key (EAN by default)., example: '8000000000017'}
              Quantity: {type: integer, minimum: 1}
    OrderAcknowledge:
      type: object
      required: [OmsOrderNumber, Success]
      properties:
        OmsOrderNumber: {type: string, example: '#1042'}
        WmsOrderNumber: {type: string, description: 'Warehouse order reference, stored and shown on the order.', example: WMS-778812}
        Success:
          description: true = taken over, false = rejected. Accepts true/false, "True"/"False", 1/0.
          oneOf:
            - {type: boolean}
            - {type: string, enum: ['true', 'false', 'True', 'False', '1', '0', 'yes', 'no']}
            - {type: integer, enum: [0, 1]}
        ErrorcCode: {type: string, description: Rejection code when Success is false. `ErrorCode` is accepted too., example: '002'}
    OrderProcessed:
      type: object
      required: [OmsOrderNumber]
      properties:
        OmsOrderNumber: {type: string, example: '#1042'}
        EventDateTime: {type: string, format: date-time, description: End of picking. Defaults to now.}
        ParcelQty: {type: integer, minimum: 1, description: Number of parcels. Defaults to the size of ParcelList.}
        ProductList:
          type: array
          items:
            type: object
            required: [Sku, Quantity]
            properties:
              Sku: {type: string}
              Quantity: {type: integer, minimum: 0}
              LotNumber: {type: string}
              LotExpirationDate: {type: string}
              SerialNumber: {type: string}
        ParcelList:
          type: array
          items:
            type: object
            properties:
              idParcel: {type: string, description: Warehouse parcel id.}
              CourierLabelId: {type: string, description: Parcel tracking number.}
              ParcelWeightGr: {type: number}
              ParcelHeightMm: {type: number}
              ParcelWidthMm: {type: number}
              ParcelDepthMm: {type: number}
    OrderShipped:
      type: object
      required: [OmsOrderNumber]
      properties:
        OmsOrderNumber: {type: string, example: '#1042'}
        EventDateTime: {type: string, format: date-time, description: Shipping time. Defaults to now.}
        CourierCode: {type: string, description: Carrier actually used. Required when the courier is managed by the warehouse., example: GLS}
        CourierWaybill: {type: string, description: Waybill / tracking number. Required when the courier is managed by the warehouse.}
        ReturnCourierCode: {type: string}
        ReturnCourierWaybill: {type: string}
        NeedsCloseWorkDay: {description: Recorded; relevant only with Nucleo-managed labels., oneOf: [{type: boolean}, {type: string}]}
        ShipmentTotalWeightGr: {type: number}
        ShipmentTotalVolumeCm3: {type: number}
        TrackingUrl: {type: string, description: Empty = Nucleo builds the link from the carrier when it can.}
    StockUpdate:
      type: object
      required: [ProductList]
      properties:
        IdWarehouse: {type: string, description: Warehouse code. Defaults to the code configured on the connection (001)., example: '001'}
        ProductList:
          description: Array of items, or an object wrapping the array.
          type: array
          items:
            type: object
            required: [Sku, StockLevel]
            properties:
              Sku: {type: string, example: '8000000000017'}
              StockLevel: {type: number, description: Absolute quantity (integer part is used)., example: 100}
              Classification: {type: string, description: Sellable (default) or another class such as Unsellable. Case-insensitive., example: Sellable}
              EventDateTime: {type: string, format: date-time, description: When the level was computed; older values than the last applied are ignored.}
    ReturnsNewResponse:
      type: object
      required: [Content, Success, ErrorMessage]
      properties:
        Content:
          type: array
          maxItems: 500
          items:
            type: object
            properties:
              OmsOrderNumber: {type: string, example: '#1042'}
              OmsReturnNumber: {type: string, example: '#1042-R1'}
              EventDateTime: {type: string, format: date-time, description: 'Return creation time, UTC.'}
              ProductList:
                type: array
                items:
                  type: object
                  properties:
                    Sku: {type: string}
                    QuantityReturned: {type: integer, minimum: 1}
        Success: {type: boolean, const: true}
        ErrorMessage: {type: string}
    ReturnAcknowledge:
      type: object
      required: [OmsReturnNumber, Success]
      properties:
        OmsReturnNumber: {type: string, example: '#1042-R1'}
        Success:
          description: Accepts true/false, "True"/"False", 1/0.
          oneOf:
            - {type: boolean}
            - {type: string}
            - {type: integer, enum: [0, 1]}
        ErrorcCode: {type: string, description: Rejection code. `ErrorCode` is accepted too.}
    ReturnUpdate:
      type: object
      required: [OmsOrderNumber, ProductList]
      properties:
        OmsOrderNumber: {type: string, example: '#1042'}
        EventDateTime: {type: string, format: date-time, description: End of inspection.}
        AquisitionDateTime: {type: string, format: date-time, description: Parcel reception (spelling as in the T-Data contract).}
        ExternalReference1: {type: string, description: OmsReturnNumber of the expected return; unknown = unannounced return., example: '#1042-R1'}
        ExternalReference2: {type: string}
        ProductList:
          description: Array of items, or an object wrapping the array (a single object is also accepted).
          type: array
          items:
            type: object
            required: [Sku]
            properties:
              Sku: {type: string}
              QuantityReturned: {type: integer, minimum: 0, description: '`Quantity` is accepted too.'}
              Classification: {type: string, description: Sellable or another class. Missing = not compliant.}
              ReturnCode: {type: string, description: Return reason code from the parcel form.}
    ReturnCanceled:
      type: object
      required: [OmsReturnNumber]
      properties:
        OmsReturnNumber: {type: string, example: '#1042-R1'}
        EventDateTime: {type: string, format: date-time}
        Reason: {type: string}
    FilesResponse:
      type: object
      required: [Status, Message, Data]
      properties:
        Status: {type: string, const: success}
        Message: {type: string, example: File retrieved successfully.}
        Data:
          type: array
          items:
            type: object
            properties:
              IdLabel: {type: integer, description: 'Labels only. 0 return label, 1 first parcel, 2–10 additional parcels.'}
              idParcel: {type: [string, 'null'], description: Labels only.}
              CourierLabelId: {type: [string, 'null'], description: Labels only. Tracking number.}
              IdDocument: {type: string, description: Documents only.}
              Position: {type: string, enum: [Internal, External]}
              FileType: {type: string, description: Zpl for labels; A4 (or the stored type) for documents.}
              Content: {type: string, contentEncoding: base64}
    Catalogue:
      type: object
      required: [ProductList]
      properties:
        IdWarehouse: {type: string}
        ProductList:
          type: array
          items:
            type: object
            required: [Sku]
            properties:
              Sku: {type: string, description: WMS item code.}
              EAN: {type: string}
              Title: {type: string}
              Category: {type: string}
              WeightGr: {type: number}
              HeightMm: {type: number}
              WidthMm: {type: number}
              DepthMm: {type: number}
