openapi: 3.1.0
info:
  title: Nucleo WMS FFW API
  version: '2026-10'
  summary: FFW / Smart Shop compatible XML web services that a warehouse management system calls to fulfil Nucleo Commerce orders and returns.
  description: |
    The **WMS FFW facade** lets a warehouse or 3PL management system (WMS) fulfil the orders of a Nucleo Commerce merchant
    with the FFW / Smart Shop web services: same paths, same query parameters, same XML envelopes (`doc_ordini`,
    `doc_giacenze`, `doc_resi`, `doc_gestione_etichette_corrieri`, `doc_recupero_documenti`). A WMS that already speaks FFW
    only changes the base URL and the credentials.

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

    **Authentication.** HTTP Basic on every call, with the username and password of one warehouse connection. The credentials
    alone identify the merchant and the warehouse; there is no store identifier in the path.

    ## Integration flow

    1. **Download orders** – `GET /api/admin/ws/ws_orders?last_upd=…` every 1–5 minutes, with state codes 17 (to fulfil),
       2 (cancelled) and 31 (shipped). There is **no explicit acknowledgement**: an order returned with state 17 is taken over
       by the warehouse, as in FFW.
    2. **Follow changes** – the same call returns again an order the merchant changed (address, notes, cancelled lines) with
       state 17 and fresh data, and an order cancelled after you downloaded it with state **2**: stop picking it.
    3. **Stock** – `POST /api/aggiorna-giacenze-impegni` with absolute levels, at most 500 items per call.
    4. **Shipment** – `POST /api/admin/spedizioni/notify-spedizione` with carrier, tracking and parcels (Nucleo extension,
       not part of the original FFW services). Nucleo creates the fulfilment on the sales channel and the order comes back in
       `ws_orders` with state **31**.
       When the merchant has Nucleo generate courier labels instead, use `get-etichette-corriere` → (`del-etichette-corriere`)
       → `ws-close-bordero`; these are switched off by default.
    5. **Returns** – `GET /api/admin/resi/list?last_upd=…` for expected returns, then `POST /api/admin/resi/notify-reso` with
       the items actually received.
    6. **Documents** – `GET /api/admin/documenti/get-order-docs` for invoices and receipts of an order, as base64 PDF.

    ## Conventions

    - **XML out**: `<?xml version="1.0" encoding="iso-8859-1"?>`, HTTP header `Content-Type: text/xml; charset=iso-8859-1`,
      pretty-printed. Characters outside ISO-8859-1 are written as numeric entities (`&#321;` for `Ł`); free text is in `CDATA`.
    - **XML in**: every POST accepts the XML either as the form field `xml` (`application/x-www-form-urlencoded` or multipart)
      or as the raw request body, in ISO-8859-1 or UTF-8. Documents containing `<!DOCTYPE>` or entity declarations, or another
      encoding, are rejected with the endpoint's `KO` envelope.
    - **Paths** are exactly FFW's, case-sensitive. The leading double slash written in the FFW spec
      (`//api/admin/spedizioni/ws-close-bordero`) is accepted on every path.
    - **Dates are Italian local time** (Europe/Rome), in FFW's per-service formats: `ws_orders` `data_dal`/`data_al`
      `dd/mm/yyyy HH:MM:SS`; `ws_orders` `last_upd` and `ord_DataUltimaModifica` `YYYYMMDDHHMMSS`; `resi/list`
      `data_dal`/`data_al` `yyyy-mm-dd`; `resi/list` `last_upd` `dd/mm/yyyy hh:mm:ss`; `ord_data`, `reso_data` `dd/mm/yyyy`;
      `ord_datetime`, `reso_datetime`, `resi_data_ultima_modifica`, `data_spedizione` `yyyy-mm-dd HH:MM:SS`.
      URL-encode spaces and slashes in query strings.
    - **Amounts**: dot decimal, two digits (`147.54`). Line prices are net of VAT, `importo_totale_riga` is gross.
    - **Item code**: `barcode` and `IDArtCod` are the variant barcode (EAN).
    - **Keys**: `ord_ID` and `reso_ID` are numeric ids assigned by Nucleo, progressive per merchant (a merchant migrating from
      FFW continues after the last FFW id). `ord_num_doc` is the channel order name (`#1042`).
    - **Codes** (`documenti_stati_ID`, `resi_stati_ID`, `resi_causali_ID`, `nazioni_ID`) are the FFW defaults listed per
      operation; the merchant can remap them per connection.
    - **Errors keep FFW's envelopes**, different per service, and validation errors are answered with **HTTP 200** and a `KO`
      envelope, as in FFW. Always read `ack`/`ACK`/`result`, not only the HTTP status.

    ## Errors common to every service

    | HTTP | Message in the endpoint envelope | When |
    |---|---|---|
    | 401 | `Unauthorized` | missing or wrong credentials (no `WWW-Authenticate` header is sent) |
    | 403 | `Forbidden` | caller IP not in the connection allowlist (only when the merchant set one) |
    | 429 | `Too many requests` | rate limit exceeded |
    | 200 | `service paused` / `service not active` | connection paused by the merchant, or still a draft: `KO`, nothing read or written, retry later |
    | 500 | `Internal error (ref <uuid>)` | unexpected error; quote the `ref` to support |

    ## Limits and monitoring

    - **600 calls per minute per connection** (all services 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, service or `ord_ID`.
    - 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 FFW in **Commerce › Orders › Channels and logistics**. Nucleo shows the
    endpoint, username and password **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-ffw
  module: commerce
  audience: [warehouse]
  stability: beta
  format: xml
  order: 41
servers:
  - url: https://{host}
    description: Production. Use the endpoint shown when the credentials were issued.
    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:
  - basicAuth: []
tags:
  - name: Orders
    description: Download orders (implicit acknowledgement) and order documents.
  - name: Stock
    description: Push stock levels.
  - name: Shipping
    description: Shipment notification, and the optional Nucleo-generated courier labels and bordereau.
  - name: Returns
    description: Download expected returns and report what was received.
paths:
  /api/admin/ws/ws_orders:
    get:
      operationId: listOrders
      summary: Download orders
      tags: [Orders]
      description: |
        Orders allocated to this warehouse, in FFW `details=medium` format. At least one of `last_upd`, `data_dal`, `data_al`
        is required.

        **States** (`documenti_stati_ID`, FFW defaults): `17` confirmed, to fulfil (Nucleo states ready, exported, label
        created); `2` cancelled; `31` shipped (shipped, delivered). Without a filter all three are returned. `documenti_tipo_ID`
        `13` (web order) is the only type served here; any other type returns an empty `<ordini/>`.

        **Implicit acknowledgement**: an order returned with state 17 is taken over by the warehouse (moves to **Exported**).
        Until then Nucleo keeps subtracting it from your stock before publishing availability.

        **Incremental reading with `last_upd`** (`>` filter on the last change): use as next `last_upd` either the last
        `ord_DataUltimaModifica` received or the time of your previous call. Nucleo stamps every change a few seconds in the future
        and returns only changes already in the past, so no change ever falls before a `last_upd` you already used. `last_upd`
        cannot be older than one month.

        **Date range** (`data_dal`/`data_al`, on the order date): at most one month; without `data_dal` it is one month before
        `data_al`; without `data_al` it is now. Both filters can be combined.

        - At most 500 orders per call (merchant-configurable), ordered by last change then `ord_ID`; the limit never splits a
          second, so a page can be slightly larger. If you receive a full page, call again at once with `last_upd` = the last
          `ord_DataUltimaModifica`.
        - An order the merchant changes after you read it (address, notes, cancelled lines) comes back with state 17 and the new
          data: update it in the WMS by `ord_ID`.
        - Cancelled before you read it: never returned. Cancelled after: returned with state **2**; Nucleo considers the
          cancellation done once you have read it with state 2.
        - Only orders allocated to this warehouse's locations, and for split orders only your lines.
        - Re-reading the same window returns the same orders without further effects.
        - The tag list of `details=medium` follows the FFW naming; `details` is accepted and ignored (always `medium`).

        **Errors**: validation errors answer HTTP 200 with `<result><ack>KO</ack><error_message>`.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      parameters:
        - name: last_upd
          in: query
          description: Orders changed strictly after this instant, `YYYYMMDDHHMMSS`, Italian time, not older than one month.
          schema: {type: string, pattern: '^\d{14}$'}
          example: '20261005100000'
        - name: data_dal
          in: query
          description: Order date from, `dd/mm/yyyy HH:MM:SS`, Italian time.
          schema: {type: string}
          example: 01/10/2026 00:00:00
        - name: data_al
          in: query
          description: Order date to, `dd/mm/yyyy HH:MM:SS`, Italian time. At most one month after `data_dal`.
          schema: {type: string}
          example: 05/10/2026 23:59:59
        - name: documenti_stati_ID
          in: query
          description: A single state code (`17`, `2` or `31`). For several, use `documenti_stati_ID[]`.
          schema: {type: string, enum: ['17', '2', '31']}
          example: '17'
        - name: documenti_stati_ID[]
          in: query
          description: Several state codes, repeated (`documenti_stati_ID[]=17&documenti_stati_ID[]=2`).
          style: form
          explode: true
          schema:
            type: array
            items: {type: string, enum: ['17', '2', '31']}
          example: ['17', '2']
        - name: documenti_tipo_ID
          in: query
          description: Document type. `13` = web order (also the default). Other types return an empty list. `documenti_tipo_ID[]` is accepted too.
          schema: {type: string}
          example: '13'
        - name: details
          in: query
          description: Accepted for compatibility; the answer is always `medium`.
          schema: {type: string, enum: [medium]}
          example: medium
      responses:
        '200':
          description: |
            Orders (`<ordini>`), possibly empty (`<ordini/>`), or a `KO` envelope for validation errors and for a paused or
            draft connection.
          content:
            application/xml:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Orders'
                  - $ref: '#/components/schemas/ResultErrorMessage'
              examples:
                orders:
                  summary: One order to fulfil
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <ordini>
                      <ordine>
                        <ord_ID>51042</ord_ID>
                        <ord_num_doc>#1042</ord_num_doc>
                        <ord_data>05/10/2026</ord_data>
                        <ord_datetime>2026-10-05 10:12:30</ord_datetime>
                        <ord_DataUltimaModifica>20261005101534</ord_DataUltimaModifica>
                        <documenti_tipo_ID>13</documenti_tipo_ID>
                        <documenti_stati_ID>17</documenti_stati_ID>
                        <stato_descr><![CDATA[Confermato]]></stato_descr>
                        <order_b2b>0</order_b2b>
                        <IDMagazzino>10</IDMagazzino>
                        <valuta>EUR</valuta>
                        <cambio>1.00000000</cambio>
                        <cliente>
                          <nome><![CDATA[Giulia]]></nome>
                          <cognome><![CDATA[Bianchi]]></cognome>
                          <email>giulia.bianchi@example.com</email>
                          <telefono>+39 333 0000000</telefono>
                        </cliente>
                        <spedizione>
                          <nome><![CDATA[Giulia]]></nome>
                          <cognome><![CDATA[Bianchi]]></cognome>
                          <azienda><![CDATA[Acme Apparel Srl]]></azienda>
                          <indirizzo><![CDATA[Via Roma 1]]></indirizzo>
                          <indirizzo2><![CDATA[Scala B]]></indirizzo2>
                          <cap>20121</cap>
                          <citta><![CDATA[Milano]]></citta>
                          <provincia>MI</provincia>
                          <nazione_iso>IT</nazione_iso>
                          <nazioni_ID>1</nazioni_ID>
                          <telefono>+39 333 0000000</telefono>
                        </spedizione>
                        <fatturazione>
                          <nome><![CDATA[Giulia]]></nome>
                          <cognome><![CDATA[Bianchi]]></cognome>
                          <azienda><![CDATA[]]></azienda>
                          <indirizzo><![CDATA[Via Roma 1]]></indirizzo>
                          <indirizzo2><![CDATA[]]></indirizzo2>
                          <cap>20121</cap>
                          <citta><![CDATA[Milano]]></citta>
                          <provincia>MI</provincia>
                          <nazione_iso>IT</nazione_iso>
                          <nazioni_ID>1</nazioni_ID>
                        </fatturazione>
                        <metodo_spedizione><![CDATA[Standard]]></metodo_spedizione>
                        <corriere>BRT</corriere>
                        <servizio_corriere></servizio_corriere>
                        <note><![CDATA[Leave with the concierge]]></note>
                        <articoli>
                          <articolo>
                            <barcode>8000000000017</barcode>
                            <sku>TEE-BLK-M</sku>
                            <descrizione><![CDATA[T-shirt Black M]]></descrizione>
                            <qta>1</qta>
                            <art_prezzo_listino>24.59</art_prezzo_listino>
                            <art_prezzo_internet>24.59</art_prezzo_internet>
                            <art_prezzo_finale_scontato>24.59</art_prezzo_finale_scontato>
                            <iva_aliquota>22.00</iva_aliquota>
                            <documenti_dettaglio_totale_iva>5.41</documenti_dettaglio_totale_iva>
                            <importo_totale_riga>30.00</importo_totale_riga>
                          </articolo>
                        </articoli>
                        <ord_totale_merce>30.00</ord_totale_merce>
                        <ord_sconti>0.00</ord_sconti>
                        <ord_spese_spedizione>5.00</ord_spese_spedizione>
                        <ord_totale_iva>6.31</ord_totale_iva>
                        <ord_totale>35.00</ord_totale>
                        <ord_totale_merce_euro>30.00</ord_totale_merce_euro>
                        <ord_sconti_euro>0.00</ord_sconti_euro>
                        <ord_spese_spedizione_euro>5.00</ord_spese_spedizione_euro>
                        <ord_totale_iva_euro>6.31</ord_totale_iva_euro>
                        <ord_totale_euro>35.00</ord_totale_euro>
                      </ordine>
                    </ordini>
                empty:
                  summary: Nothing new
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <ordini/>
                missingFilter:
                  summary: KO, no date filter
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error_message><![CDATA[E' obbligatorio passare almeno uno dei parametri data_dal / data_al o last_upd.]]></error_message>
                    </result>
                rangeTooWide:
                  summary: KO, range over one month
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error_message><![CDATA[L'intervallo data_dal / data_al non può essere maggiore di un mese.]]></error_message>
                    </result>
                lastUpdTooOld:
                  summary: KO, last_upd older than one month
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error_message><![CDATA[last upd deve essere una data successiva al 2026-09-05 10:00:00.]]></error_message>
                    </result>
                paused:
                  summary: KO, connection paused
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error_message><![CDATA[service paused]]></error_message>
                    </result>
          x-error-messages:
            - E' obbligatorio passare almeno uno dei parametri data_dal / data_al o last_upd.
            - 'Formato data non valido: usare dd/mm/yyyy HH:MM:SS.'
            - data_dal deve essere precedente a data_al.
            - L'intervallo data_dal / data_al non può essere maggiore di un mese.
            - 'Formato last_upd non valido: usare YYYYMMDDHHMMSS.'
            - last upd deve essere una data successiva al <yyyy-mm-dd HH:MM:SS>.
        '4XX': {$ref: '#/components/responses/ResultErrorMessageRejected'}
        '500': {$ref: '#/components/responses/ResultErrorMessageServerError'}
  /api/aggiorna-giacenze-impegni:
    post:
      operationId: updateStock
      summary: Push stock levels
      tags: [Stock]
      description: |
        Absolute stock per item and warehouse code, already net of the warehouse's commitments (orders already downloaded).

        - Root `<giacenze>`, one `<articolo>` per item and warehouse: `IDArtCod` (EAN) and `giacenza` (integer) required,
          `IDMagazzino` optional (defaults to the code configured on the connection, `1` unless the merchant changed it).
        - The same EAN on several warehouse codes mapped to the same Nucleo location is **summed**.
        - Nucleo subtracts the paid orders you have not downloaded yet, then publishes availability to the sales channels.
        - **At most 500 `<articolo>` per call** (merchant-configurable): above the limit the call is `KO` and **nothing** is
          processed. Send batches of 500.
        - A malformed item makes the whole call `KO` (nothing processed).
        - An unknown EAN is skipped and the merchant is alerted; the rest is processed and the answer is `OK`. A warehouse code
          not mapped to a Nucleo location is stored but not published, and the merchant is alerted.
        - Items not sent are **not zeroed**: send `giacenza` 0 to zero an item.
        - Optional query `?full=1` marks a full snapshot: Nucleo republishes every item received, even unchanged ones.
        - `OK` means the levels are stored; publication to the channels follows asynchronously.

        Official input is the form field `xml`; the raw body is accepted too.

        **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"}
      parameters:
        - name: full
          in: query
          required: false
          description: Any non-empty value marks a full snapshot and forces republication of all items received.
          schema: {type: string}
          example: '1'
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [xml]
              properties:
                xml:
                  type: string
                  description: The `<giacenze>` document.
            example:
              xml: <giacenze><articolo><IDArtCod>8000000000017</IDArtCod><giacenza>10</giacenza><IDMagazzino>10</IDMagazzino></articolo></giacenze>
          multipart/form-data:
            schema:
              type: object
              required: [xml]
              properties:
                xml: {type: string}
          application/xml:
            schema: {$ref: '#/components/schemas/StockRequest'}
            example: |
              <giacenze>
                <articolo><IDArtCod>8000000000017</IDArtCod><giacenza>10</giacenza><IDMagazzino>10</IDMagazzino></articolo>
                <articolo><IDArtCod>8000000000017</IDArtCod><giacenza>3</giacenza><IDMagazzino>20</IDMagazzino></articolo>
                <articolo><IDArtCod>8000000000024</IDArtCod><giacenza>0</giacenza><IDMagazzino>10</IDMagazzino></articolo>
              </giacenze>
      responses:
        '200':
          description: '`OK`, or `KO` with the reason (nothing processed). Also `KO` when the connection is paused or a draft.'
          content:
            application/xml:
              schema: {$ref: '#/components/schemas/StockAck'}
              examples:
                ok:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <giacenze>
                      <ack>OK</ack>
                    </giacenze>
                tooManyItems:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <giacenze>
                      <ack>KO</ack>
                      <error>Numero massimo di articoli per chiamata superato (612 &gt; 500).</error>
                    </giacenze>
                malformed:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <giacenze>
                      <ack>KO</ack>
                      <error>xml parameter malformed</error>
                    </giacenze>
                invalidItem:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <giacenze>
                      <ack>KO</ack>
                      <error>Articolo 3 non valido: IDArtCod e giacenza intera obbligatori.</error>
                    </giacenze>
        '4XX': {$ref: '#/components/responses/StockRejected'}
        '500': {$ref: '#/components/responses/StockServerError'}
  /api/admin/resi/list:
    get:
      operationId: listReturns
      summary: Download returns
      tags: [Returns]
      description: |
        Returns of orders fulfilled by this warehouse, or routed to one of its locations. Returns created by the warehouse itself
        (parcels that arrived unannounced) are not listed. At least one of `data_dal`, `data_al`, `last_upd`, `resi_ID` is required.

        **States** (`resi_stati_ID`, FFW defaults): `1` in progress (`RESO IN CORSO`), `2` confirmed (`RESO CONFERMATO`),
        `3` anomalous (`RESO ANOMALO`), `4` cancelled (`RESO ANNULLATO`).

        **Reasons** (`resi_causali_ID`, FFW defaults): 1 wrong size · 2 not as pictured · 3 fit · 4 defective · 5 wrong item ·
        6 other · 7 arrived late · 8 changed mind · 9 claim · 10 return to sender · 11 manual return. The description
        (`stato_descr`) is in Italian, as in FFW.

        - `last_upd` is a `>=` filter on the last change: a return can come back twice, update it by `reso_ID`.
        - Date range on the creation date, at most one month; without `data_dal` it is one month before `data_al`.
        - `resi_stati_ID` or `resi_causali_ID` require `data_dal` or `limit`.
        - At most 1000 returns without `limit`. Default order: creation date descending (`ASC` for ascending); `orderBy=resi_id`
          sorts by `reso_ID` ascending.
        - `scontotot` reproduces the FFW sample: the VAT of the returned lines, negative.
        - Always XML (`type=csv` is not supported). Reading has no side effect.

        **Errors**: validation errors answer HTTP 200 with `<result><ack>KO</ack><error_message>`.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      parameters:
        - name: data_dal
          in: query
          description: Creation date from, `yyyy-mm-dd`.
          schema: {type: string, format: date}
          example: '2026-10-01'
        - name: data_al
          in: query
          description: Creation date to, `yyyy-mm-dd`. Defaults to today.
          schema: {type: string, format: date}
          example: '2026-10-07'
        - name: last_upd
          in: query
          description: Returns changed from this instant (`>=`), `dd/mm/yyyy hh:mm:ss`, Italian time, not older than one month.
          schema: {type: string}
          example: 07/10/2026 09:00:00
        - name: resi_ID
          in: query
          description: One return by id.
          schema: {type: integer}
          example: 7014
        - name: resi_stati_ID
          in: query
          description: State filter. Requires `data_dal` or `limit`.
          schema: {type: string, enum: ['1', '2', '3', '4']}
        - name: resi_causali_ID
          in: query
          description: Reason filter. Requires `data_dal` or `limit`.
          schema: {type: string, enum: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']}
        - name: magazzini_ID_web
          in: query
          description: Only returns routed to the location with this warehouse code.
          schema: {type: string}
          example: '10'
        - name: limit
          in: query
          description: Maximum number of returns. Default 1000.
          schema: {type: integer, minimum: 0}
          example: 50
        - name: orderBy
          in: query
          description: '`resi_id` sorts by `reso_ID` ascending; otherwise by creation date.'
          schema: {type: string, enum: [resi_id]}
        - name: ASC
          in: query
          description: When present (any value), the creation-date order is ascending.
          schema: {type: string}
          allowEmptyValue: true
      responses:
        '200':
          description: Returns (`<resi>`), possibly empty (`<resi/>`), or a `KO` envelope.
          content:
            application/xml:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Returns'
                  - $ref: '#/components/schemas/ResultErrorMessage'
              examples:
                returns:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <resi>
                      <reso>
                        <reso_ID>7014</reso_ID>
                        <reso_data>07/10/2026</reso_data>
                        <reso_datetime>2026-10-07 09:41:02</reso_datetime>
                        <resi_data_ultima_modifica>2026-10-07 09:41:02</resi_data_ultima_modifica>
                        <ord_ID>51042</ord_ID>
                        <ord_num_doc>#1042</ord_num_doc>
                        <nazioni_ID>1</nazioni_ID>
                        <resi_stati_ID>1</resi_stati_ID>
                        <stato_descr><![CDATA[RESO IN CORSO]]></stato_descr>
                        <resi_causali_ID>1</resi_causali_ID>
                        <reso_descr><![CDATA[Too small]]></reso_descr>
                        <reso_prezzotot>30.00</reso_prezzotot>
                        <reso_prezzototale_euro>30.00</reso_prezzototale_euro>
                        <scontotot>-5.41</scontotot>
                        <articoli>
                          <articolo>
                            <barcode>8000000000017</barcode>
                            <qta>1</qta>
                            <art_prezzo_listino>24.59</art_prezzo_listino>
                            <art_prezzo_internet>24.59</art_prezzo_internet>
                            <art_prezzo_finale_scontato>24.59</art_prezzo_finale_scontato>
                            <documenti_dettaglio_totale_iva>5.41</documenti_dettaglio_totale_iva>
                            <iva_aliquota>22.00</iva_aliquota>
                            <importo_totale_riga>30.00</importo_totale_riga>
                          </articolo>
                        </articoli>
                      </reso>
                    </resi>
                filterNeedsLimit:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error_message><![CDATA[Con resi_stati_ID o resi_causali_ID è obbligatorio impostare data_dal o limit.]]></error_message>
                    </result>
          x-error-messages:
            - Con resi_stati_ID o resi_causali_ID è obbligatorio impostare data_dal o limit.
            - E' obbligatorio passare almeno uno dei parametri data_dal / data_al o last_upd.
            - limit deve essere un numero intero.
            - 'Formato data non valido: usare yyyy-mm-dd.'
            - L'intervallo di tempo richiesto deve essere inferiore o uguale a un mese.
            - 'Formato last_upd non valido: usare gg/mm/aaaa hh:mm:ss.'
            - last upd deve essere una data successiva al <yyyy-mm-dd HH:MM:SS>.
        '4XX': {$ref: '#/components/responses/ResultErrorMessageRejected'}
        '500': {$ref: '#/components/responses/ResultErrorMessageServerError'}
  /api/admin/resi/notify-reso:
    post:
      operationId: notifyReturnReceived
      summary: Report items received for returns
      tags: [Returns]
      description: |
        Items and quantities actually received for one or more returns (`<reso>` repeated).

        - **All or nothing**: the whole call is validated first; an unknown `reso_ID` or a malformed item rejects it and no return
          is updated.
        - The return is **confirmed** (state 2) when exactly the expected items and quantities arrived; otherwise it is
          **anomalous** (state 3) and goes to the merchant's customer service. An item not in the return makes it anomalous.
        - If the return had been cancelled (or already closed) and the parcel arrives anyway, the goods are recorded, the merchant
          is alerted and the answer is `OK`.

        Official input is the raw body; the form field `xml` is accepted too.

        **Idempotent**: a second call on the same return recomputes the outcome.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        required: true
        content:
          application/xml:
            schema: {$ref: '#/components/schemas/ReturnsReceived'}
            example: |
              <resi>
                <reso>
                  <reso_ID>7014</reso_ID>
                  <articoli>
                    <articolo><barcode>8000000000017</barcode><qta>1</qta><note>intact</note></articolo>
                  </articoli>
                </reso>
              </resi>
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [xml]
              properties:
                xml: {type: string, description: The `<resi>` document.}
      responses:
        '200':
          description: '`OK`, or `KO` with the reason (nothing applied). Also `KO` when the connection is paused or a draft.'
          content:
            application/xml:
              schema: {$ref: '#/components/schemas/ResultMessage'}
              examples:
                ok:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>OK</ack>
                      <message></message>
                    </result>
                malformed:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <message>XML is wrong or malformed</message>
                    </result>
                notFound:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <message>Reso 9999 non trovato</message>
                    </result>
        '4XX': {$ref: '#/components/responses/ResultMessageRejected'}
        '500': {$ref: '#/components/responses/ResultMessageServerError'}
  /api/admin/spedizioni/notify-spedizione:
    post:
      operationId: notifyShipment
      summary: Report shipment with carrier and tracking
      tags: [Shipping]
      description: |
        **Nucleo extension** (not part of the original FFW services, beta): tells Nucleo that orders left the warehouse, with
        carrier, tracking and parcels, so Nucleo can create the fulfilment on the sales channel and the customer receives the
        tracking. One or more `<spedizione>`.

        - `ord_ID` required; the order must be allocated to this warehouse and already downloaded (state 17), otherwise
          `Invalid order status: <status>`.
        - `data_spedizione` `yyyy-mm-dd HH:MM:SS` Italian time; missing or unreadable = now.
        - `corriere`, `servizio`: default to the order's carrier and service. `tracking_url` empty = Nucleo builds the link
          from the carrier when it can.
        - `colli/collo`: one `tracking` per parcel, weight in grams.
        - `articoli/articolo`: shipped items; when missing, everything allocated to this warehouse is shipped. Items not
          declared stay open for the merchant's customer service.
        - The order moves to **Shipped** and comes back in `ws_orders` with state **31**. If it was being cancelled, the shipment
          is recorded and the merchant is alerted.
        - **All or nothing**: an unknown order rejects the whole call.

        **JSON alternative**: with `Content-Type: application/json` (or a body starting with `{`), the call takes the fields of
        the T-Data `Orders/Shipped` contract (`OmsOrderNumber` = order name or `ord_ID`, `EventDateTime`, `CourierCode`,
        `CourierWaybill`, `TrackingUrl`, `ShipmentTotalWeightGr`, `ShipmentTotalVolumeCm3`) and answers in T-Data JSON. Authentication
        and rate-limit errors keep the XML envelope.

        **Idempotent**: the same notification repeated with the same first tracking 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/xml:
            schema: {$ref: '#/components/schemas/ShipmentNotification'}
            example: |
              <spedizioni>
                <spedizione>
                  <ord_ID>51042</ord_ID>
                  <data_spedizione>2026-10-05 16:30:00</data_spedizione>
                  <corriere>BRT</corriere>
                  <servizio>STANDARD</servizio>
                  <tracking_url></tracking_url>
                  <colli>
                    <collo><tracking>BRT0000000000001</tracking><peso_gr>1200</peso_gr></collo>
                    <collo><tracking>BRT0000000000002</tracking><peso_gr>800</peso_gr></collo>
                  </colli>
                  <articoli>
                    <articolo><barcode>8000000000017</barcode><qta>1</qta></articolo>
                  </articoli>
                </spedizione>
              </spedizioni>
          application/x-www-form-urlencoded:
            schema:
              type: object
              required: [xml]
              properties:
                xml: {type: string, description: The `<spedizioni>` document.}
          application/json:
            schema:
              type: object
              required: [OmsOrderNumber]
              properties:
                OmsOrderNumber: {type: string, description: Order name or ord_ID.}
                EventDateTime: {type: string, format: date-time}
                CourierCode: {type: string}
                CourierWaybill: {type: string}
                TrackingUrl: {type: string}
                ShipmentTotalWeightGr: {type: number}
                ShipmentTotalVolumeCm3: {type: number}
            example:
              OmsOrderNumber: '51042'
              EventDateTime: '2026-10-05T16:30:00+02:00'
              CourierCode: BRT
              CourierWaybill: BRT0000000000001
              TrackingUrl: ''
              ShipmentTotalWeightGr: 2000
      responses:
        '200':
          description: '`OK` or `KO` (XML), or T-Data JSON success for a JSON request.'
          content:
            application/xml:
              schema: {$ref: '#/components/schemas/ResultMessage'}
              examples:
                ok:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>OK</ack>
                      <message></message>
                    </result>
                notFound:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <message>Order not found: 59999</message>
                    </result>
                malformed:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <message>XML is wrong or malformed</message>
                    </result>
            application/json:
              schema:
                type: object
                properties:
                  Success: {type: boolean, const: true}
                  ErrorMessage: {type: string}
              example: {Success: true, ErrorMessage: ''}
        '400':
          description: JSON request only, invalid body or unknown order.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ErrorCode: {type: string, const: Exception}
                  Message: {type: string}
              examples:
                notFound:
                  value: {ErrorCode: Exception, Message: 'Order not found: 59999'}
                invalidJson:
                  value: {ErrorCode: Exception, Message: Invalid JSON body}
        '4XX': {$ref: '#/components/responses/ResultMessageRejected'}
        '500': {$ref: '#/components/responses/ResultMessageServerError'}
  /api/admin/documenti/get-order-docs:
    get:
      operationId: getOrderDocuments
      summary: Download order documents
      tags: [Orders]
      description: |
        Fiscal documents of an order (invoice, receipt, credit note) as base64 PDF. Alias: `GET /api/documenti/admin/get-order-docs`.

        - `type` FFW codes in `<type>` (defaults): `3` invoice, `5` credit note, `31` receipt, `32` receipt variation.
        - No documents, unknown order or invalid id: `<ACK>ERROR</ACK>` with an empty `<documents/>`, HTTP 200.
        - Every error of this service (including authentication) uses the same envelope without a message: rely on the HTTP status.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      parameters:
        - $ref: '#/components/parameters/DocumentsOrderId'
        - $ref: '#/components/parameters/DocumentsType'
      responses:
        '200': {$ref: '#/components/responses/Documents'}
        '4XX': {$ref: '#/components/responses/DocumentsRejected'}
        '500': {$ref: '#/components/responses/DocumentsServerError'}
  /api/documenti/admin/get-order-docs:
    get:
      operationId: getOrderDocumentsAlias
      summary: Download order documents (alias)
      tags: [Orders]
      description: |
        Alias of `GET /api/admin/documenti/get-order-docs`, same parameters, behaviour and responses.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      parameters:
        - $ref: '#/components/parameters/DocumentsOrderId'
        - $ref: '#/components/parameters/DocumentsType'
      responses:
        '200': {$ref: '#/components/responses/Documents'}
        '4XX': {$ref: '#/components/responses/DocumentsRejected'}
        '500': {$ref: '#/components/responses/DocumentsServerError'}
  /api/admin/spedizioni/get-etichette-corriere:
    post:
      operationId: getCourierLabels
      summary: Get courier labels generated by Nucleo
      tags: [Shipping]
      description: |
        **Optional, off by default.** Only when the merchant has Nucleo generate courier labels; otherwise HTTP 404
        `<etichette><result>courier labels are managed by the warehouse</result></etichette>`.

        For each `<order>` (`id` = `ord_ID`, `colli` = number of parcels, default 1) Nucleo creates the shipment with the
        order's carrier and returns one PDF label per parcel, base64 in `CDATA`. The order must have been downloaded (state 17);
        it moves to **Label created**. Per-order problems are reported in that order's `<error>` (`Order not found`,
        `Invalid order status: <status>`, or the carrier's message) while `<result>` stays `OK`.

        Official input is the form field `xml`; the raw body is accepted too.

        **Idempotent**: while a shipment with labels is active for the order, the same labels are returned and no new shipment
        is created. Use `del-etichette-corriere` to start over.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/LabelOrders'
      responses:
        '200':
          description: Labels per order, or `xml parameter malformed`.
          content:
            application/xml:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/Labels'
                  - $ref: '#/components/schemas/LabelsError'
              examples:
                labels:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <etichette>
                      <result>OK</result>
                      <order>
                        <id>51042</id>
                        <error></error>
                        <shipper>BRT</shipper>
                        <type>PDF</type>
                        <labels>
                          <label><![CDATA[JVBERi0xLjQKJcfsj6IK...]]></label>
                          <label><![CDATA[JVBERi0xLjQKJcfsj6IK...]]></label>
                        </labels>
                      </order>
                      <order>
                        <id>59999</id>
                        <error>Order not found</error>
                        <shipper></shipper>
                        <type></type>
                        <labels></labels>
                      </order>
                    </etichette>
                malformed:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <etichette>
                      <result>xml parameter malformed</result>
                    </etichette>
        '404': {$ref: '#/components/responses/LabelsDormant'}
        '4XX': {$ref: '#/components/responses/LabelsRejected'}
        '500': {$ref: '#/components/responses/LabelsServerError'}
  /api/admin/spedizioni/del-etichette-corriere:
    post:
      operationId: deleteCourierLabels
      summary: Void courier labels generated by Nucleo
      tags: [Shipping]
      description: |
        **Optional, off by default** (see `get-etichette-corriere`). For each `<order>` (`id` = `ord_ID`) voids the active
        shipment at the carrier and puts the order back to **Exported**, ready for new labels. Per-order problems are reported in
        `<error>` (`Order not found` when there is no active shipment, or the carrier's message).

        **Idempotent**: a second call finds no active shipment and answers `Order not found` for that order.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      requestBody:
        $ref: '#/components/requestBodies/LabelOrders'
      responses:
        '200':
          description: Outcome per order, or `xml parameter malformed`.
          content:
            application/xml:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/LabelsVoided'
                  - $ref: '#/components/schemas/LabelsError'
              examples:
                voided:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <etichette>
                      <result>OK</result>
                      <order>
                        <id>51042</id>
                        <error></error>
                      </order>
                      <order>
                        <id>59999</id>
                        <error>Order not found</error>
                      </order>
                    </etichette>
        '404': {$ref: '#/components/responses/LabelsDormant'}
        '4XX': {$ref: '#/components/responses/LabelsRejected'}
        '500': {$ref: '#/components/responses/LabelsServerError'}
  /api/admin/spedizioni/ws-close-bordero:
    post:
      operationId: closeBordereau
      summary: Close the bordereau of the day
      tags: [Shipping]
      description: |
        **Optional, off by default** (see `get-etichette-corriere`); otherwise HTTP 404
        `<result><ack>KO</ack><error>courier labels are managed by the warehouse</error></result>`.

        Confirms to the carriers every shipment of this connection with labels created and not yet confirmed (one manifest per
        carrier). Each order moves to **Shipped**, Nucleo creates the fulfilment on the sales channel and the order comes back in
        `ws_orders` with state 31. No body is needed. The FFW spelling `//api/admin/spedizioni/ws-close-bordero` is accepted.

        With nothing to confirm the answer is `KO` `No shipping to be confirmed` (HTTP 200), which also makes a repeated call harmless.

        **Rate limit:** shared 600 calls/minute per connection.
      x-rate-limit: {limit: 600, window: "1m", scope: "per key"}
      responses:
        '200':
          description: '`OK`, or `KO` when there is nothing to confirm.'
          content:
            application/xml:
              schema: {$ref: '#/components/schemas/ResultError'}
              examples:
                ok:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>OK</ack>
                      <error></error>
                    </result>
                nothingToConfirm:
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error>No shipping to be confirmed</error>
                    </result>
        '404':
          description: Courier labels are managed by the warehouse for this connection (default).
          content:
            application/xml:
              schema: {$ref: '#/components/schemas/ResultError'}
              example: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <result>
                  <ack>KO</ack>
                  <error>courier labels are managed by the warehouse</error>
                </result>
        '4XX':
          description: Authentication, IP allowlist or rate limit (401, 403, 429).
          content:
            application/xml:
              schema: {$ref: '#/components/schemas/ResultError'}
              examples:
                unauthorized:
                  summary: HTTP 401
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error>Unauthorized</error>
                    </result>
                tooManyRequests:
                  summary: HTTP 429
                  value: |
                    <?xml version="1.0" encoding="iso-8859-1"?>
                    <result>
                      <ack>KO</ack>
                      <error>Too many requests</error>
                    </result>
        '500':
          description: Unexpected error, with a reference to quote to support.
          content:
            application/xml:
              schema: {$ref: '#/components/schemas/ResultError'}
              example: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <result>
                  <ack>KO</ack>
                  <error>Internal error (ref 3f2b8c1e-5a7d-4e2f-9b61-0c8d4a2e7f10)</error>
                </result>
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
      description: |
        Username and password of the warehouse connection, issued once by the merchant in Commerce › Orders › Channels and
        logistics. Send them preemptively on every call.
  parameters:
    DocumentsOrderId:
      name: documenti_testa_ID
      in: query
      required: true
      description: The order's `ord_ID`.
      schema: {type: string, pattern: '^\d+$'}
      example: '51042'
    DocumentsType:
      name: type
      in: query
      required: false
      description: '`sell` (sale documents), `return` (return documents), empty = all.'
      schema: {type: string, enum: ['', sell, return]}
      example: sell
  requestBodies:
    LabelOrders:
      required: true
      content:
        application/x-www-form-urlencoded:
          schema:
            type: object
            required: [xml]
            properties:
              xml: {type: string, description: The `<spedizioni>` document.}
          example:
            xml: <spedizioni><order><id>51042</id><colli>2</colli></order></spedizioni>
        application/xml:
          schema: {$ref: '#/components/schemas/LabelOrders'}
          example: |
            <spedizioni>
              <order><id>51042</id><colli>2</colli></order>
            </spedizioni>
  responses:
    ResultErrorMessageRejected:
      description: Authentication, IP allowlist or rate limit (401, 403, 429), in the `<result>/<error_message>` envelope.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/ResultErrorMessage'}
          examples:
            unauthorized:
              summary: HTTP 401
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <result>
                  <ack>KO</ack>
                  <error_message><![CDATA[Unauthorized]]></error_message>
                </result>
            forbidden:
              summary: HTTP 403
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <result>
                  <ack>KO</ack>
                  <error_message><![CDATA[Forbidden]]></error_message>
                </result>
            tooManyRequests:
              summary: HTTP 429
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <result>
                  <ack>KO</ack>
                  <error_message><![CDATA[Too many requests]]></error_message>
                </result>
    ResultErrorMessageServerError:
      description: Unexpected error, with a reference to quote to support.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/ResultErrorMessage'}
          example: |
            <?xml version="1.0" encoding="iso-8859-1"?>
            <result>
              <ack>KO</ack>
              <error_message><![CDATA[Internal error (ref 3f2b8c1e-5a7d-4e2f-9b61-0c8d4a2e7f10)]]></error_message>
            </result>
    StockRejected:
      description: Authentication, IP allowlist or rate limit (401, 403, 429), in the `<giacenze>` envelope.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/StockAck'}
          examples:
            unauthorized:
              summary: HTTP 401
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <giacenze>
                  <ack>KO</ack>
                  <error>Unauthorized</error>
                </giacenze>
            tooManyRequests:
              summary: HTTP 429
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <giacenze>
                  <ack>KO</ack>
                  <error>Too many requests</error>
                </giacenze>
    StockServerError:
      description: Unexpected error, with a reference to quote to support.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/StockAck'}
          example: |
            <?xml version="1.0" encoding="iso-8859-1"?>
            <giacenze>
              <ack>KO</ack>
              <error>Internal error (ref 3f2b8c1e-5a7d-4e2f-9b61-0c8d4a2e7f10)</error>
            </giacenze>
    ResultMessageRejected:
      description: Authentication, IP allowlist or rate limit (401, 403, 429), in the `<result>/<message>` envelope.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/ResultMessage'}
          examples:
            unauthorized:
              summary: HTTP 401
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <result>
                  <ack>KO</ack>
                  <message>Unauthorized</message>
                </result>
            tooManyRequests:
              summary: HTTP 429
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <result>
                  <ack>KO</ack>
                  <message>Too many requests</message>
                </result>
    ResultMessageServerError:
      description: Unexpected error, with a reference to quote to support.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/ResultMessage'}
          example: |
            <?xml version="1.0" encoding="iso-8859-1"?>
            <result>
              <ack>KO</ack>
              <message>Internal error (ref 3f2b8c1e-5a7d-4e2f-9b61-0c8d4a2e7f10)</message>
            </result>
    LabelsDormant:
      description: Courier labels are managed by the warehouse for this connection (default).
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/LabelsError'}
          example: |
            <?xml version="1.0" encoding="iso-8859-1"?>
            <etichette>
              <result>courier labels are managed by the warehouse</result>
            </etichette>
    LabelsRejected:
      description: Authentication, IP allowlist or rate limit (401, 403, 429), in the `<etichette>` envelope.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/LabelsError'}
          examples:
            unauthorized:
              summary: HTTP 401
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <etichette>
                  <result>Unauthorized</result>
                </etichette>
            tooManyRequests:
              summary: HTTP 429
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <etichette>
                  <result>Too many requests</result>
                </etichette>
    LabelsServerError:
      description: Unexpected error, with a reference to quote to support.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/LabelsError'}
          example: |
            <?xml version="1.0" encoding="iso-8859-1"?>
            <etichette>
              <result>Internal error (ref 3f2b8c1e-5a7d-4e2f-9b61-0c8d4a2e7f10)</result>
            </etichette>
    Documents:
      description: Documents, or `ERROR` with no documents.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/Documents'}
          examples:
            documents:
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <response>
                  <ACK>OK</ACK>
                  <documents>
                    <document>
                      <id>5</id>
                      <type>31</type>
                      <number>1</number>
                      <ref>R0002</ref>
                      <content><![CDATA[JVBERi0xLjQKJcfsj6IK...]]></content>
                    </document>
                  </documents>
                </response>
            none:
              value: |
                <?xml version="1.0" encoding="iso-8859-1"?>
                <response>
                  <ACK>ERROR</ACK>
                  <documents/>
                </response>
    DocumentsRejected:
      description: Authentication, IP allowlist or rate limit (401, 403, 429). Same envelope as "no documents"; check the HTTP status.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/Documents'}
          example: |
            <?xml version="1.0" encoding="iso-8859-1"?>
            <response>
              <ACK>ERROR</ACK>
              <documents/>
            </response>
    DocumentsServerError:
      description: Unexpected error. Same envelope as "no documents"; check the HTTP status.
      content:
        application/xml:
          schema: {$ref: '#/components/schemas/Documents'}
          example: |
            <?xml version="1.0" encoding="iso-8859-1"?>
            <response>
              <ACK>ERROR</ACK>
              <documents/>
            </response>
  schemas:
    Orders:
      type: object
      xml: {name: ordini}
      properties:
        ordine:
          type: array
          items: {$ref: '#/components/schemas/Order'}
    Order:
      type: object
      xml: {name: ordine}
      properties:
        ord_ID: {type: integer, description: Nucleo order id; key for every later call.}
        ord_num_doc: {type: string, description: Channel order name., example: '#1042'}
        ord_data: {type: string, description: Order date dd/mm/yyyy.}
        ord_datetime: {type: string, description: Order date yyyy-mm-dd HH:MM:SS.}
        ord_DataUltimaModifica: {type: string, description: Last change YYYYMMDDHHMMSS; reusable as last_upd.}
        documenti_tipo_ID: {type: string, example: '13'}
        documenti_stati_ID: {type: string, enum: ['17', '2', '31']}
        stato_descr: {type: string, description: State label (CDATA).}
        order_b2b: {type: integer, enum: [0, 1]}
        IDMagazzino: {type: string, description: Warehouse code of the location the order is allocated to.}
        valuta: {type: string, description: ISO 4217 currency., example: EUR}
        cambio: {type: string, description: Exchange rate to EUR.}
        cliente: {$ref: '#/components/schemas/Customer'}
        spedizione: {$ref: '#/components/schemas/ShippingAddress'}
        fatturazione: {$ref: '#/components/schemas/BillingAddress'}
        metodo_spedizione: {type: string, description: Shipping method title (CDATA).}
        corriere: {type: string, description: Carrier chosen by the merchant's carrier rules.}
        servizio_corriere: {type: string}
        note: {type: string, description: Notes for the warehouse (CDATA).}
        articoli:
          type: object
          properties:
            articolo:
              type: array
              items: {$ref: '#/components/schemas/OrderLine'}
        ord_totale_merce: {type: string}
        ord_sconti: {type: string}
        ord_spese_spedizione: {type: string}
        ord_totale_iva: {type: string}
        ord_totale: {type: string}
        ord_totale_merce_euro: {type: string}
        ord_sconti_euro: {type: string}
        ord_spese_spedizione_euro: {type: string}
        ord_totale_iva_euro: {type: string}
        ord_totale_euro: {type: string}
    Customer:
      type: object
      properties:
        nome: {type: string}
        cognome: {type: string}
        email: {type: string}
        telefono: {type: string}
    BillingAddress:
      type: object
      properties:
        nome: {type: string}
        cognome: {type: string}
        azienda: {type: string}
        indirizzo: {type: string}
        indirizzo2: {type: string}
        cap: {type: string}
        citta: {type: string}
        provincia: {type: string}
        nazione_iso: {type: string, description: ISO 3166-1 alpha-2.}
        nazioni_ID: {type: string, description: FFW country code; empty when the merchant has not mapped that country.}
    ShippingAddress:
      allOf:
        - $ref: '#/components/schemas/BillingAddress'
        - type: object
          properties:
            telefono: {type: string}
    OrderLine:
      type: object
      xml: {name: articolo}
      properties:
        barcode: {type: string, description: EAN.}
        sku: {type: string}
        descrizione: {type: string}
        qta: {type: integer, description: Quantity allocated to this warehouse and still active.}
        art_prezzo_listino: {type: string, description: List price net of VAT.}
        art_prezzo_internet: {type: string, description: Same as the list price.}
        art_prezzo_finale_scontato: {type: string, description: Final unit price net of VAT.}
        iva_aliquota: {type: string, description: VAT rate.}
        documenti_dettaglio_totale_iva: {type: string, description: VAT amount of the line.}
        importo_totale_riga: {type: string, description: Gross line total.}
    StockRequest:
      type: object
      xml: {name: giacenze}
      properties:
        articolo:
          type: array
          maxItems: 500
          items:
            type: object
            xml: {name: articolo}
            required: [IDArtCod, giacenza]
            properties:
              IDArtCod: {type: string, description: EAN.}
              giacenza: {type: integer, description: Absolute quantity.}
              IDMagazzino: {type: string, description: Warehouse code. Defaults to the connection's code.}
    StockAck:
      type: object
      xml: {name: giacenze}
      properties:
        ack: {type: string, enum: [OK, KO]}
        error: {type: string}
    Returns:
      type: object
      xml: {name: resi}
      properties:
        reso:
          type: array
          items: {$ref: '#/components/schemas/Return'}
    Return:
      type: object
      xml: {name: reso}
      properties:
        reso_ID: {type: integer}
        reso_data: {type: string, description: dd/mm/yyyy.}
        reso_datetime: {type: string, description: yyyy-mm-dd HH:MM:SS.}
        resi_data_ultima_modifica: {type: string, description: yyyy-mm-dd HH:MM:SS.}
        ord_ID: {type: integer}
        ord_num_doc: {type: string}
        nazioni_ID: {type: string}
        resi_stati_ID: {type: string, enum: ['1', '2', '3', '4']}
        stato_descr: {type: string}
        resi_causali_ID: {type: string}
        reso_descr: {type: string, description: Customer reason text (CDATA).}
        reso_prezzotot: {type: string}
        reso_prezzototale_euro: {type: string}
        scontotot: {type: string, description: VAT of the returned lines with negative sign.}
        articoli:
          type: object
          properties:
            articolo:
              type: array
              items:
                type: object
                xml: {name: articolo}
                properties:
                  barcode: {type: string}
                  qta: {type: integer}
                  art_prezzo_listino: {type: string}
                  art_prezzo_internet: {type: string}
                  art_prezzo_finale_scontato: {type: string}
                  documenti_dettaglio_totale_iva: {type: string}
                  iva_aliquota: {type: string}
                  importo_totale_riga: {type: string}
    ReturnsReceived:
      type: object
      xml: {name: resi}
      properties:
        reso:
          type: array
          items:
            type: object
            xml: {name: reso}
            required: [reso_ID]
            properties:
              reso_ID: {type: integer}
              articoli:
                type: object
                properties:
                  articolo:
                    type: array
                    items:
                      type: object
                      xml: {name: articolo}
                      required: [barcode, qta]
                      properties:
                        barcode: {type: string}
                        qta: {type: integer, minimum: 0}
                        note: {type: string}
    ResultMessage:
      type: object
      xml: {name: result}
      properties:
        ack: {type: string, enum: [OK, KO]}
        message: {type: string}
    ResultErrorMessage:
      type: object
      xml: {name: result}
      properties:
        ack: {type: string, const: KO}
        error_message: {type: string, description: Message in CDATA.}
    ResultError:
      type: object
      xml: {name: result}
      properties:
        ack: {type: string, enum: [OK, KO]}
        error: {type: string}
    ShipmentNotification:
      type: object
      xml: {name: spedizioni}
      properties:
        spedizione:
          type: array
          items:
            type: object
            xml: {name: spedizione}
            required: [ord_ID]
            properties:
              ord_ID: {type: integer}
              data_spedizione: {type: string, description: yyyy-mm-dd HH:MM:SS Italian time.}
              corriere: {type: string}
              servizio: {type: string}
              tracking_url: {type: string}
              colli:
                type: object
                properties:
                  collo:
                    type: array
                    items:
                      type: object
                      xml: {name: collo}
                      properties:
                        tracking: {type: string}
                        peso_gr: {type: integer}
              articoli:
                type: object
                properties:
                  articolo:
                    type: array
                    items:
                      type: object
                      xml: {name: articolo}
                      properties:
                        barcode: {type: string}
                        qta: {type: integer}
    LabelOrders:
      type: object
      xml: {name: spedizioni}
      properties:
        order:
          type: array
          items:
            type: object
            xml: {name: order}
            required: [id]
            properties:
              id: {type: integer, description: ord_ID.}
              colli: {type: integer, minimum: 1, description: Parcels (default 1). Ignored by del-etichette-corriere.}
    Labels:
      type: object
      xml: {name: etichette}
      properties:
        result: {type: string, const: OK}
        order:
          type: array
          items:
            type: object
            xml: {name: order}
            properties:
              id: {type: string}
              error: {type: string, description: Empty on success.}
              shipper: {type: string, description: Carrier display name.}
              type: {type: string, enum: [PDF, '']}
              labels:
                type: object
                properties:
                  label:
                    type: array
                    items: {type: string, contentEncoding: base64, xml: {name: label}}
    LabelsVoided:
      type: object
      xml: {name: etichette}
      properties:
        result: {type: string, const: OK}
        order:
          type: array
          items:
            type: object
            xml: {name: order}
            properties:
              id: {type: string}
              error: {type: string}
    LabelsError:
      type: object
      xml: {name: etichette}
      properties:
        result: {type: string}
    Documents:
      type: object
      xml: {name: response}
      properties:
        ACK: {type: string, enum: [OK, ERROR]}
        documents:
          type: object
          properties:
            document:
              type: array
              items:
                type: object
                xml: {name: document}
                properties:
                  id: {type: string}
                  type: {type: string, description: FFW document type code.}
                  number: {type: string}
                  ref: {type: string}
                  content: {type: string, contentEncoding: base64, description: PDF in CDATA.}
