{
  "openapi": "3.1.0",
  "info": {
    "title": "officina.it API",
    "version": "2026-10-31",
    "description": "API REST pubblica di officina.it: consente di leggere e scrivere i dati\ndella propria officina da altri programmi (gestionali contabili, sistemi\ndi flotta, strumenti di analisi).\n\nL'autenticazione avviene con una chiave API (`Authorization: Bearer\nofk_live_…`) generata da Impostazioni → Accesso API. Ogni chiave porta\npermessi per risorsa e appartiene a una singola officina.\n\nOgni richiesta deve indicare la revisione del contratto con l'header\n`X-API-Version`. Il percorso `/v1` identifica la versione maggiore, che\ncambia solo per modifiche non retrocompatibili; la revisione identifica\nla data del contratto. La documentazione completa è su\nhttps://officina.it/api.\n\n## Revisioni\n\nOgni revisione dentro `/v1` è additiva: dichiarare una revisione più\nvecchia continua a funzionare, semplicemente senza i campi più recenti.\nI campi e gli endpoint introdotti da una revisione riportano\n`x-since: 'AAAA-MM-GG'` (schemi) o `x-changes` (operazioni).\n\n| Revisione | Novità | Azione |\n|---|---|---|\n| 2026-10-31 | Ogni numero in scrittura segue le regole dei moduli web (`422` sul campo, con il messaggio del web). Righe di schede e preventivi: `quantity` e `unit_price` anche negativi, `unit_discount_rate` intero 0–100 (12.5 veniva salvato come 12). Importi arrotondati al centesimo; negativi, zero e decimali rifiutati dove il campo non li ammette. `PATCH /v1/expenses/{id}` su una spesa con righe: importi diversi rispondono `422` invece di `200` ignorandoli. | Nessuna |\n| 2026-10-30 | IBAN di fornitori e officina salvato maiuscolo e senza spazi; in scrittura deve superare il controllo IBAN, altrimenti `422` su `/bank_account_iban`. `DELETE /v1/financial-accounts/{id}`: `409` per un conto con movimenti o per l'ultimo conto Banca o Cassa. | Nessuna |\n| 2026-10-29 | Fatture: nuovo `POST /v1/invoices/{id}/unissue` (Annulla emissione): riporta in `draft` l'ultima fattura emessa del sezionale nel suo anno, senza nota di credito né fattura elettronica inviata (una bozza non inviata viene eliminata), e il numero torna libero; altrimenti `409`. | Opzionale |\n| 2026-10-28 | Preventivi: nuovo stato `draft` (bozza). Ogni preventivo nuovo nasce in bozza e passa a `waiting` quando lo si segna come inviato o quando parte un messaggio al cliente; filtro `status=draft` e contatore `draft` in `status-counts`. `POST /v1/quotes/{id}/convert` accetta `{\"mark_accepted\": false}`. Le revisioni precedenti vedono le bozze come `waiting` e creano preventivi `waiting`. | Opzionale |\n| 2026-10-27 | Preventivi: nuovo campo `expires_at` (data scadenza, `YYYY-MM-DD`, `null` = nessuna) nelle risposte e in `POST /v1/quotes` / `PATCH /v1/quotes/{id}`; non può precedere la data del preventivo (`422` su `/expires_at`). | Opzionale |\n| 2026-10-26 | Visura targa nell'app mobile (non esposta nell'API pubblica): il server attende HaynesPro fino a 60 secondi invece di 17. Nessun endpoint pubblico cambia. | Nessuna |\n| 2026-10-25 | Marcatempo in sola lettura: `GET /v1/time-types` (i tipi di tempo, `is_wait` per l'attesa), `GET /v1/time-entries` (i tempi di un periodo, filtri `from`/`to` in giorni di Roma inclusi, `worksheet_id`, `user_id`, paginazione `limit`/`starting_after`) e `GET /v1/worksheets/{id}/time` (i tempi di una scheda con ore lavorate, ore fatturate, differenza, resa, ricavo per ora lavorata, ore per collaboratore e per tipo). Serve il modulo Marcatempo attivo (`403` altrimenti) e lo scope `worksheets.read`. Avviare e fermare i timer resta nell'app. | Opzionale |\n| 2026-10-24 | Visura targa nell'app mobile (non esposta nell'API pubblica): `502 provider_unavailable` e `504 provider_timeout` quando il servizio targhe di HaynesPro non risponde correttamente, al posto di `503 service_unavailable`. Nessun endpoint pubblico cambia. | Nessuna |\n| 2026-10-23 | `version` nel dettaglio degli scontrini. Da questa revisione `PATCH /v1/receipts/{id}` richiede l'header `If-Match` con la `version` letta: senza risponde `428 precondition_required`, con una versione superata `409 conflict` e non salva nulla (`If-Match: *` scrive comunque). Le revisioni precedenti lo accettano facoltativo. Campi personalizzati di tipo `list` (menu a tendina): `options` sulla definizione in `PUT`/`GET /v1/{customer,vehicle}-field-settings` e sugli elementi di `custom_field_values`; il valore salvato è una delle opzioni. | Opzionale |\n| 2026-10-22 | Modifiche concorrenti: `version` nel dettaglio di schede lavoro, preventivi, accettazioni, spese, fatture, note di credito, appuntamenti, fornitori e treni gomme (come già clienti, veicoli e articoli). Da questa revisione `If-Match` con la `version` letta è obbligatorio su `PATCH` di questi documenti e sul batch delle righe: senza l'header 428 `precondition_required`, con una versione superata 409 e nulla salvato (`If-Match: *` scrive comunque). Le revisioni precedenti lo accettano facoltativo. | Opzionale |\n| 2026-10-19 | Report v2: `GET /v1/reports/{widget}` (card Officina ed Economico, gli stessi dati della pagina Report), report Fiscale (`GET /v1/reports/fiscal`, `PUT /v1/reports/fiscal/profile`, `GET /v1/reports/fiscal/{widget}`, `GET /v1/reports/fiscal/export.zip`). `GET /v1/report/{widget}` è deprecato: da questa revisione risponde `410 endpoint_retired`. | Opzionale |\n| 2026-10-15 | Costi di lavorazione: `unit_cost` sulle righe delle schede lavoro e nel batch delle righe, `work_cost_manual`/`article_cost_manual` sulla scheda, `total_work_cost`/`total_article_cost` impostabili (o `null` = automatici) con `PATCH /v1/worksheets/{id}`. I costi letti sono corretti su tutte le revisioni. | Opzionale |\n| 2026-10-14 | Tipi di riga: `line_type` (`item`, `note`, `separator`) sulle righe di schede lavoro, preventivi, fatture e scontrini, e in `add`/`update` del batch delle righe (note e separatori senza importi). | Opzionale |\n| 2026-10-13 | Paginazione vera (`limit`, `next_starting_after`) su `GET /v1/calendar-entries` (intervallo, cliente e veicolo), `/v1/receipts` (filtri e conteggi sul database), `/v1/articles/{id}/movements` e `/v1/vehicles/{id}/{worksheets,quotes,checkins}`. Le revisioni precedenti ricevono gli elenchi interi. | Opzionale |\n| 2026-10-06 | Smaltimento rifiuti: `GET`/`POST /v1/{worksheets,quotes}/{id}/waste-disposal` (anteprima e applicazione, `409 waste_disposal_not_applicable` con `error.reason`), impostazioni `waste_disposal_*` su `GET /v1/company`. | Opzionale |\n| 2026-10-04 | Recupero per numero (`GET /v1/{risorsa}/by-number/{year}/{number}`, `409 ambiguous_number`), finestra di `issued_at` sull'emissione fattura; categorie sugli articoli e sul listino manodopera (`work_category_ids` / `work_categories`, filtro `work_category_id`). | Opzionale |\n| 2026-10-03 | Più categorie per documento: `work_category_ids` e `work_categories` su schede lavoro, preventivi, accettazioni e appuntamenti (lettura e scrittura), `work_category_names` sulle fatture. `work_category_id` resta valido e riporta il primo dell'elenco. | Opzionale |\n| 2026-09-28 | Campi personalizzati dei clienti (`/v1/customer-field-settings`, `custom_fields`). | Opzionale |\n| 2026-09-05 | Concessionaria: dettaglio e creazione stock, download allegati, dettaglio vendita arricchito. | Opzionale |\n| 2026-07-20 | `GET /v1/checkins/status-counts`, `sort`/`direction` su `GET /v1/checkins`. | Opzionale |\n| 2026-07-19 | `frozen` sul dettaglio scheda lavoro; solo una fattura elettronica trasmessa blocca le modifiche. | Nessuna |\n| 2026-07-15 | Revisione di lancio (minima supportata). | — |\n\n### Correzioni valide su ogni revisione\n\n- **2026-10-29** — Una scheda lavoro ha al massimo una fattura viva:\n  `POST /v1/invoices` su una scheda che ne ha già una risponde `409\n  conflict` (prima ne creava una seconda). `DELETE /v1/invoices/{id}` su\n  una bozza elimina anche i suoi pagamenti e i relativi allegati; una\n  fattura emessa resta non eliminabile.\n\n## Revisioni deprecate e non supportate\n\nLa revisione più recente è quella stabile. Ogni revisione precedente\nancora supportata è **deprecata**: le chiamate funzionano come prima, ma\nogni risposta riporta l'header `Deprecation` (RFC 9745, con la data\ndell'ultima revisione) e `Link: \u003chttps://officina.it/api#deprecazione\u003e;\nrel=\"deprecation\"`. Quando viene fissata la data di ritiro, le risposte\nriportano anche `Sunset` (RFC 8594), con almeno 90 giorni di preavviso.\nDa quella data la revisione è **non supportata**: `400\napi_version_retired`. Lo stato attuale (revisione minima, ritiri\nprogrammati) è su https://officina.it/api#deprecazione.\n\n## Partita IVA e numeri IVA UE\n\nVale per tutte le revisioni supportate: lo schema non cambia, cambia solo\nil controllo. Il `tax_id` dei clienti (`POST`/`PATCH /v1/customers`) è\nverificato con le regole ufficiali del paese, cifre di controllo comprese:\n\n- **Italia**: partita IVA di 11 cifre, cifra di controllo corretta e\n  codice ufficio esistente, per ogni tipo di cliente. I clienti creati\n  via API sono italiani.\n- **Altri paesi UE**, per i clienti di tipo `company`, `ente` e\n  `public_administration`: formato e cifre di controllo del numero IVA\n  del paese. Paesi con controllo attivo: AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GR (prefisso EL), HR, HU, IE, IT, LT, LU, LV, MT, NL, PL, PT, RO, SE, SI, SK.\n- Privati esteri e paesi fuori dall'UE: testo libero.\n\nIl numero è salvato e restituito senza prefisso paese, spazi o punti\n(`\"IT 0015 956 0366\"` → `\"00159560366\"`, `\"DE 136.695.976\"` →\n`\"136695976\"`). Un numero non valido riceve `422 parameter_invalid` con\n`param: \"/tax_id\"` e un messaggio che dice se è sbagliato il formato o la\ncifra di controllo. Un `PATCH` controlla il numero solo quando cambia\n`tax_id` o `customer_type`: i clienti salvati prima del controllo restano\nmodificabili.\n\nDate e orari viaggiano in RFC 3339 (UTC). Le date scritte dal client\ndevono cadere fra il 1° gennaio 1900 e il 31 dicembre 2100: fuori da\nquesto intervallo la risposta è `422 parameter_invalid` con `param` sul\ncampo. È lo stesso intervallo accettato dai selettori di data\ndell'applicazione web, così un record creato via API resta modificabile\nanche dall'interfaccia. I timestamp generati dal server (`created_at`,\n`updated_at`, …) non hanno questo vincolo.\n",
    "contact": {
      "name": "officina.it engineering",
      "email": "assistenza@officina.it"
    }
  },
  "servers": [
    {
      "url": "https://api.officina.it",
      "description": "Production"
    },
    {
      "url": "http://localhost:8084",
      "description": "Local development"
    }
  ],
  "security": [
    {
      "PublicAPIKey": []
    }
  ],
  "tags": [
    {
      "name": "Receipts",
      "description": "Scontrini e corrispettivi."
    },
    {
      "name": "Expenses",
      "description": "Spese e categorie di spesa."
    },
    {
      "name": "Suppliers",
      "description": "Fornitori, ordini e documenti di trasporto."
    },
    {
      "name": "TyreSets",
      "description": "Deposito gomme."
    },
    {
      "name": "ElectronicInvoicing",
      "description": "Fatturazione elettronica (sola lettura)."
    },
    {
      "name": "Reports",
      "description": "Report e statistiche."
    },
    {
      "name": "Dealership",
      "description": "Concessionaria: stock e vendite veicoli."
    },
    {
      "name": "Settings",
      "description": "Impostazioni aziendali, aliquote IVA, sezionali, team."
    },
    {
      "name": "Authentication",
      "description": "Endpoints that exchange credentials for a mobile bearer JWT.\nNone of these require auth — they issue it.\n"
    },
    {
      "name": "Customers",
      "description": "The workshop's clients. Multi-tenant: every endpoint is scoped to\nthe authenticated user's company. Cross-company access returns 404,\nnever 403 (don't leak the existence of other workshops' customers).\n"
    },
    {
      "name": "Vehicles",
      "description": "Vehicles owned (or managed) by workshop clients. Multi-tenant:\nevery endpoint is scoped to the authenticated user's company.\nCross-company access returns 404, never 403.\n\nRental setup (is_rental, rental_*) is managed through a\nseparate flow; those fields are read-only on this resource.\n"
    },
    {
      "name": "Articles",
      "description": "Read-only catalog for the line-item editor: articles back both\nthe magazzino (parts) and the listino manodopera (labor), plus\nthe company VAT rates its IVA picker offers. Multi-tenant:\nscoped to the authenticated user's company.\n"
    },
    {
      "name": "Worksheets",
      "description": "Work orders / job cards. Multi-tenant: every endpoint is scoped\nto the authenticated user's company. Cross-company access\nreturns 404, never 403. Status changes go through the\n`/v1/worksheets/{id}/transitions` RPC instead of PATCH so the\nlifecycle is explicit in the URL surface.\n"
    },
    {
      "name": "TimeTracking",
      "description": "Marcatempo: i tempi di lavoro e di attesa registrati sulle schede\nlavoro, le ore lavorate a confronto con le ore fatturate e la resa.\nIn sola lettura: avviare e fermare i timer resta nell'app.\n"
    },
    {
      "name": "Quotes",
      "description": "Customer-facing job estimates. Multi-tenant: every endpoint is\nscoped to the authenticated user's company. Status flows\nwaiting → accepted / rejected through the\n`/v1/quotes/{id}/transitions` RPC.\n"
    },
    {
      "name": "Invoices",
      "description": "Workshop invoices. Multi-tenant: every endpoint is scoped to the\nauthenticated user's company. Cross-company access returns 404,\nnever 403. Invoices flow through a draft → issued lifecycle;\nonce issued, the `seq_number` is assigned atomically per-company\nand `issued_at` is stamped. Issued invoices remain patchable for\na limited subset of fields (the service layer is the source of\ntruth on what's mutable post-issuance).\n"
    },
    {
      "name": "CreditNotes",
      "description": "Credit notes are derivative documents — each one is created\nFROM an existing invoice. Multi-tenant: every endpoint is\nscoped to the authenticated user's company. Cross-company\naccess returns 404, never 403. Deleting a credit note with an\nalready-transmitted SDI electronic counterpart returns 409.\n"
    },
    {
      "name": "Checkins",
      "description": "Vehicle intake / inspection snapshots. Each checkin captures the\nvehicle's state on arrival (fuel level, kilometres, free-form\nsection notes) and progresses through an `active` → `completed`\nlifecycle. Multi-tenant: every endpoint is scoped to the\nauthenticated user's company. Cross-company access returns 404.\n\nStatus transitions live behind their own verb\n(`POST /v1/checkins/{id}/transitions`) so the server can stamp\nlifecycle timestamps and run side effects centrally — flipping\n`status` through PATCH is intentionally not supported. Line-item\nmutation is out of scope for v1 PATCH and will land as a dedicated\nsub-resource later.\n"
    },
    {
      "name": "Calendar",
      "description": "Workshop appointments and reminders. Each entry belongs to\nexactly one company (the caller's) and is owned by the user\nwho created it. Entries may reference an optional Customer,\nVehicle, work category, and parent Calendar. The parent\nCalendar container is not exposed on this tier — only entries.\n"
    },
    {
      "name": "CashBook",
      "description": "Cash-flow ledger derived from payments. Each entry is a row in\nthe workshop's cashbook: an income (from an invoice or worksheet)\nor an expense (from a supplier bill), carrying a cumulative\n`running_balance`. Read-only — the cashbook is a projection, not\na first-class resource; to mutate it, create or delete the\nunderlying payment/expense. Multi-tenant: every entry is scoped\nto the authenticated user's company.\n"
    }
  ],
  "x-doc-groups": [
    {
      "slug": "clienti",
      "name": "Clienti",
      "tags": [
        "Customers"
      ],
      "ready": true,
      "description": "Anagrafiche, documenti allegati e storico."
    },
    {
      "slug": "veicoli",
      "name": "Veicoli",
      "tags": [
        "Vehicles"
      ],
      "ready": true,
      "description": "Veicoli, marche e documenti allegati."
    },
    {
      "slug": "schede-lavoro",
      "name": "Schede lavoro",
      "tags": [
        "Worksheets",
        "TimeTracking"
      ],
      "ready": true,
      "description": "Schede, righe, pagamenti, stati di lavorazione e marcatempo."
    },
    {
      "slug": "preventivi",
      "name": "Preventivi",
      "tags": [
        "Quotes"
      ],
      "ready": true,
      "description": "Preventivi, righe e conversione in scheda lavoro."
    },
    {
      "slug": "fatture",
      "name": "Fatture",
      "tags": [
        "Invoices",
        "CreditNotes"
      ],
      "ready": true,
      "description": "Fatture, note di credito, pagamenti ed emissione."
    },
    {
      "slug": "scontrini",
      "name": "Scontrini",
      "tags": [
        "Receipts"
      ],
      "ready": true,
      "description": "Scontrini e relativi pagamenti."
    },
    {
      "slug": "accettazioni",
      "name": "Accettazioni",
      "tags": [
        "Checkins"
      ],
      "ready": true,
      "description": "Accettazioni, conversioni e documenti."
    },
    {
      "slug": "agenda",
      "name": "Agenda",
      "tags": [
        "Calendar"
      ],
      "ready": true,
      "description": "Calendari e appuntamenti."
    },
    {
      "slug": "magazzino",
      "name": "Magazzino",
      "tags": [
        "Articles"
      ],
      "ready": true,
      "description": "Articoli, listino manodopera e movimenti."
    },
    {
      "slug": "deposito-gomme",
      "name": "Deposito gomme",
      "tags": [
        "TyreSets"
      ],
      "ready": true,
      "description": "Pneumatici in deposito e passaggi di stato."
    },
    {
      "slug": "fornitori",
      "name": "Fornitori",
      "tags": [
        "Suppliers"
      ],
      "ready": true,
      "description": "Fornitori, ordini e documenti di trasporto."
    },
    {
      "slug": "spese",
      "name": "Spese",
      "tags": [
        "Expenses"
      ],
      "ready": true,
      "description": "Spese, pagamenti e categorie."
    },
    {
      "slug": "prima-nota",
      "name": "Prima nota",
      "tags": [
        "CashBook"
      ],
      "ready": true,
      "description": "Movimenti di cassa e conti finanziari."
    },
    {
      "slug": "impostazioni",
      "name": "Impostazioni",
      "tags": [
        "Settings",
        "Cestino"
      ],
      "ready": true,
      "description": "Dati azienda, aliquote IVA, listini, collaboratori, cestino."
    },
    {
      "slug": "fatturazione-elettronica",
      "name": "Fatturazione elettronica",
      "tags": [
        "ElectronicInvoicing"
      ],
      "ready": true,
      "description": "Fatture elettroniche emesse e ricevute, in sola lettura."
    },
    {
      "slug": "concessionaria",
      "name": "Concessionaria",
      "tags": [
        "Dealership"
      ],
      "ready": true,
      "description": "Stock veicoli, vendite e report."
    },
    {
      "slug": "report",
      "name": "Report",
      "tags": [
        "Reports"
      ],
      "ready": true,
      "description": "Statistiche e contatori della tua officina."
    },
    {
      "slug": "autenticazione",
      "name": "Autenticazione",
      "tags": [
        "Authentication"
      ],
      "ready": true,
      "description": "Verifica della chiave e dei permessi."
    }
  ],
  "paths": {
    "/v1/articles": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovi campi `work_category_ids` e `work_categories` su ogni articolo (le categorie per cui si usa) e filtro `work_category_id` (ripetibile: articoli con almeno uno dei tipi indicati).",
            "en": "New fields `work_category_ids` and `work_categories` on every article (the work categories it is used for) and the `work_category_id` filter (repeatable: articles with any of the given categories)."
          }
        ],
        "tags": [
          "Articles"
        ],
        "summary": "Search the article/labor catalog",
        "x-summary-it": "Cerca nel magazzino e nel listino",
        "x-scope": "articles.read",
        "x-description-it": "Il catalogo dietro «Cerca da magazzino». Contiene sia i ricambi\n(`kind: item`) sia le voci di manodopera (`kind: work`).\n\nOgni parola di `q` deve comparire nel nome, nel codice o nelle note.\nFiltra per tipo con `kind`. Passa `customer_id` per ottenere il\n`sale_price` già scontato secondo il listino di quel cliente.\n",
        "description": "The catalog behind the line-item editor's \"Cerca da magazzino\".\nArticles back both the magazzino (parts — `kind: item`) and the\nlistino manodopera (labor — `kind: work`).\n\nEvery whitespace-separated term in `?q=`, in any order, must\nmatch the name or comments (case and accents ignored) or the code\ncompared on letters and digits only (`c39181` finds `COV C39/181`).\nWhen no article matches, terms of 4+ letters also match close\nspellings in the name (`pressone` finds `PRESSIONE`). Results are\nrelevance-ranked while searching (exact code first), name-sorted\nwhile browsing. Pass `?customer_id=` so\n`sale_price` reflects the customer's price-list overrides.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "description": "Restrict to parts (\"item\") or labor (\"work\"); omitted = both.",
            "schema": {
              "type": "string",
              "enum": [
                "item",
                "work"
              ]
            }
          },
          {
            "name": "customer_id",
            "in": "query",
            "description": "Apply this customer's price-list overrides to sale_price.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25,
              "maximum": 50
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Filtra per fornitore.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "work_category_id",
            "in": "query",
            "required": false,
            "x-since": "2026-10-04",
            "description": "Only articles tagged with this work category (\"Categoria\").\nRepeatable (`?work_category_id=a\u0026work_category_id=b`): articles\nwith ANY of them. An unknown id matches nothing. The list echoes\nthe filter as `work_category_filter`.\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "form",
            "explode": true
          }
        ],
        "responses": {
          "200": {
            "description": "One page of catalog rows.",
            "x-description-it": "Gli articoli che soddisfano la ricerca.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url",
                    "work_category_filter"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Article"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "work_category_filter": {
                      "$ref": "#/components/schemas/WorkCategoryFilter"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "article",
                      "id": "f18c4b62-9d07-4e53-a294-6b1f8d3c5e70",
                      "code": "FO-2231",
                      "name": "Filtro olio",
                      "kind": "item",
                      "unit_type": "pz",
                      "stock": 12,
                      "sale_price": 14.9,
                      "vat_code": "22",
                      "unit_discount_rate": 0,
                      "work_category_ids": [
                        "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                      ],
                      "work_categories": [
                        {
                          "object": "work_category",
                          "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                          "name": "Tagliando"
                        }
                      ]
                    },
                    {
                      "object": "article",
                      "id": "2c9e5a71-4b38-40df-9126-7e3d1f6a8c25",
                      "code": null,
                      "name": "Manodopera meccanica",
                      "kind": "work",
                      "unit_type": "h",
                      "stock": 0,
                      "sale_price": 40.0,
                      "vat_code": "22",
                      "unit_discount_rate": 0,
                      "work_category_ids": [],
                      "work_categories": []
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/articles"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Accetta `work_category_ids` (massimo 10, nell'ordine scelto): assente lascia invariate le categorie dell'articolo, `[]` o `null` li rimuove. La risposta include `work_category_ids` e `work_categories`.",
            "en": "Accepts `work_category_ids` (at most 10, in the order given): left out keeps the article's work categories unchanged, `[]` or `null` removes them. The response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Articles"
        ],
        "summary": "Create an article",
        "x-summary-it": "Crea un articolo",
        "x-scope": "articles.write",
        "x-description-it": "Crea un ricambio o una voce di manodopera. `unit_type` deve essere\ncoerente con il tipo: `kg`, `L`, `kit`, `pz` per il magazzino;\n`gg`, `h`, `lav`, `min` per la manodopera. `vat_code` deve essere una\ndelle aliquote attive dell'azienda (vedi `GET /v1/vat-rates`).\n",
        "operationId": "articleCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "unit_type",
                  "vat_code"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "unit_type": {
                    "type": "string",
                    "description": "Magazzino: `kg`, `L`, `kit`, `pz`. Manodopera: `gg`, `h`,\n`lav`, `min`.\n"
                  },
                  "stock": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "minimum_stock": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "purchase_price": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "sale_price": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "vat_code": {
                    "type": "string",
                    "description": "Deve essere una delle aliquote attive dell'azienda."
                  },
                  "unit_discount_rate": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "location": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "supplier_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "work_category_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "maxItems": 10,
                    "items": {
                      "type": "string"
                    },
                    "x-since": "2026-10-04",
                    "description": "The work categories (\"Categoria\") the article is\nused for, in the order given. Omitted → unchanged (none\non create); `[]` or `null` → removes every category.\nTrimmed and de-duplicated; each id must be one of the\ncompany's work categories (`GET /v1/work-categories`).\n"
                  }
                }
              },
              "example": {
                "name": "Filtro olio",
                "code": "FO-2231",
                "unit_type": "pz",
                "stock": 12,
                "purchase_price": 6.4,
                "sale_price": 14.9,
                "vat_code": "22",
                "supplier_id": null
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "L'articolo creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "article",
                  "id": "f18c4b62-9d07-4e53-a294-6b1f8d3c5e70",
                  "code": "FO-2231",
                  "name": "Filtro olio",
                  "kind": "item",
                  "unit_type": "pz",
                  "stock": 12,
                  "sale_price": 14.9,
                  "vat_code": "22",
                  "unit_discount_rate": 0,
                  "minimum_stock": 4,
                  "purchase_price": 6.4,
                  "location": "Scaffale A2",
                  "comments": "Compatibile con motori 1.2 FIRE",
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/ArticleCodeTaken"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/articles/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Articles"
        ],
        "summary": "Delete article",
        "x-summary-it": "Elimina un articolo",
        "x-scope": "articles.write",
        "x-description-it": "Eliminazione logica: l'articolo sparisce dal catalogo. Le righe dei\ndocumenti che lo citano restano intatte.\n",
        "operationId": "articleDelete",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "L'articolo eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "article",
                  "id": "f18c4b62-9d07-4e53-a294-6b1f8d3c5e70",
                  "code": "FO-2231",
                  "name": "Filtro olio",
                  "kind": "item",
                  "unit_type": "pz",
                  "stock": 12,
                  "sale_price": 14.9,
                  "vat_code": "22",
                  "unit_discount_rate": 0,
                  "minimum_stock": 4,
                  "purchase_price": 6.4,
                  "location": "Scaffale A2",
                  "comments": "Compatibile con motori 1.2 FIRE",
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: le categorie per cui si usa l'articolo.",
            "en": "New fields `work_category_ids` and `work_categories`: the work categories the article is used for."
          }
        ],
        "tags": [
          "Articles"
        ],
        "summary": "Retrieve a article",
        "x-summary-it": "Recupera un articolo",
        "x-scope": "articles.read",
        "x-description-it": "Restituisce l'articolo con i campi di dettaglio che l'elenco omette:\nsoglia di riordino, prezzo di acquisto, posizione, note e fornitore.\n",
        "operationId": "articleGet",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "L'articolo richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "article",
                  "id": "f18c4b62-9d07-4e53-a294-6b1f8d3c5e70",
                  "code": "FO-2231",
                  "name": "Filtro olio",
                  "kind": "item",
                  "unit_type": "pz",
                  "stock": 12,
                  "sale_price": 14.9,
                  "vat_code": "22",
                  "unit_discount_rate": 0,
                  "minimum_stock": 4,
                  "purchase_price": 6.4,
                  "location": "Scaffale A2",
                  "comments": "Compatibile con motori 1.2 FIRE",
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          },
          {
            "since": "2026-10-04",
            "it": "Accetta `work_category_ids` (massimo 10, nell'ordine scelto): assente lascia invariate le categorie dell'articolo, `[]` o `null` li rimuove. La risposta include `work_category_ids` e `work_categories`.",
            "en": "Accepts `work_category_ids` (at most 10, in the order given): left out keeps the article's work categories unchanged, `[]` or `null` removes them. The response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Articles"
        ],
        "summary": "Update article",
        "x-summary-it": "Aggiorna un articolo",
        "x-scope": "articles.write",
        "x-description-it": "Il corpo è lo stesso della creazione: va inviato l'oggetto completo,\nnon solo i campi da cambiare. I campi omessi vengono azzerati, non\nlasciati invariati.\n\nPer correggere la giacenza senza annullare le vendite registrate nel\nfrattempo, invia insieme a `stock` la giacenza letta prima della\nmodifica in `loaded_stock`: viene applicata la differenza\n`stock - loaded_stock`, registrata come movimento di magazzino.\nSenza `loaded_stock`, `stock` è la nuova giacenza.\n\nHeader `If-Match` con il campo `version` letto nell'articolo:\nobbligatorio dalla revisione 2026-10-22, e prima con `loaded_stock` (altrimenti 422). Se\nl'articolo è stato salvato nel frattempo (anche da una ripetizione di\nquesta stessa richiesta) la modifica è rifiutata con 409 e nulla viene\nsalvato. Le vendite e gli altri movimenti di magazzino non cambiano\n`version`; il carico di un ordine fornitore sì, perché aggiorna anche\nil prezzo d'acquisto.\n\nUn articolo resta del suo `kind` per sempre: un `unit_type` dell'altro\ntipo (es. da `pz` a `h`) è rifiutato con 422 su `/unit_type`.\n",
        "operationId": "articlePatch",
        "description": "Full-form update (the mobile edit sends every field, matching the web\neditor). Fields left out are cleared, not kept.\n\nTo correct the stock without undoing sales recorded meanwhile, send\nthe stock you read before the edit in `loaded_stock` along with\n`stock`: the difference `stock - loaded_stock` is applied and\nrecorded as a stock movement. Without `loaded_stock`, `stock` is the\nnew stock.\n\n`If-Match` header with the article's `version` as read: required\nfrom revision 2026-10-22, and before it with `loaded_stock` (422 otherwise). If the article was saved\nmeanwhile (a repeat of this same request included) the update is\nrefused with 409 and nothing is written. Sales and other stock\nmovements do not change `version`; receiving a supplier order does,\nsince it also updates the purchase price.\n\nAn article keeps its `kind` for life: a `unit_type` of the other kind\n(e.g. `pz` → `h`) is refused with 422 on `/unit_type`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Il corpo è lo stesso della creazione: va inviato\nl'oggetto completo, non solo i campi da cambiare.\n",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "unit_type",
                  "vat_code"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "unit_type": {
                    "type": "string",
                    "description": "Magazzino: `kg`, `L`, `kit`, `pz`. Manodopera: `gg`, `h`,\n`lav`, `min`.\n"
                  },
                  "stock": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "loaded_stock": {
                    "type": "number",
                    "description": "The stock read before the edit; requires `If-Match`. The\nsave applies `stock - loaded_stock` (as a stock movement),\nso sales recorded meanwhile are kept.\n"
                  },
                  "minimum_stock": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "purchase_price": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "sale_price": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "vat_code": {
                    "type": "string",
                    "description": "Deve essere una delle aliquote attive dell'azienda."
                  },
                  "unit_discount_rate": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "location": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "supplier_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "work_category_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "maxItems": 10,
                    "items": {
                      "type": "string"
                    },
                    "x-since": "2026-10-04",
                    "description": "The work categories (\"Categoria\") the article is\nused for, in the order given. Omitted → unchanged (none\non create); `[]` or `null` → removes every category.\nTrimmed and de-duplicated; each id must be one of the\ncompany's work categories (`GET /v1/work-categories`).\n"
                  }
                }
              },
              "example": {
                "name": "Filtro olio",
                "code": "FO-2231",
                "unit_type": "pz",
                "stock": 12,
                "purchase_price": 6.4,
                "sale_price": 14.9,
                "vat_code": "22",
                "supplier_id": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "L'articolo aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "article",
                  "id": "f18c4b62-9d07-4e53-a294-6b1f8d3c5e70",
                  "code": "FO-2231",
                  "name": "Filtro olio",
                  "kind": "item",
                  "unit_type": "pz",
                  "stock": 12,
                  "sale_price": 14.9,
                  "vat_code": "22",
                  "unit_discount_rate": 0,
                  "minimum_stock": 4,
                  "purchase_price": 6.4,
                  "location": "Scaffale A2",
                  "comments": "Compatibile con motori 1.2 FIRE",
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/ArticleCodeTaken"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/articles/{id}/movements": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-13",
            "it": "Paginazione: `limit` (predefinito 20, massimo 100) e `starting_after`; le voci sono ordinate dalla più recente (per data e id) e `next_starting_after` porta alla pagina successiva. Prima di questa revisione l'elenco torna intero.",
            "en": "Pagination: `limit` (default 20, max 100) and `starting_after`; entries come newest first (by date, then id) and `next_starting_after` leads to the next page. Before this revision the list comes back whole."
          }
        ],
        "tags": [
          "Articles"
        ],
        "summary": "Article movements",
        "x-summary-it": "Movimenti di un articolo",
        "x-scope": "articles.read",
        "x-description-it": "Lo storico di carico e scarico dell'articolo. `quantity` è positiva\nper i carichi e negativa per gli scarichi; `source` indica il\ndocumento che ha generato il movimento e `source_id` lo identifica.\n",
        "operationId": "articleMovements",
        "description": "The \"Storico lavorazioni\" for an article / lavorazione: the sale\nmovements where the item was used (worksheet line items, order items,\nstandalone receipts), newest first — same set as the web service-catalog\ndetail (sale-type only). Paged from revision 2026-10-13 (`limit`, `starting_after` → `next_starting_after`); older revisions get every row (`has_more` false).\n",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size from revision 2026-10-13 (ignored before).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "The `next_starting_after` of the previous page (revision 2026-10-13).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I movimenti dell'articolo.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ArticleMovement"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "article_movement",
                      "id": "8b4d2f06-1c73-4a95-b028-5e7f3a1d9c64",
                      "quantity": -4,
                      "source": "worksheet",
                      "source_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "seq_number": 412,
                      "document_date": "2026-07-15",
                      "customer_name": "Mario Rossi",
                      "vehicle_name": "Fiat Panda",
                      "total": 74.0,
                      "created_at": "2026-07-15T08:31:00Z"
                    },
                    {
                      "object": "article_movement",
                      "id": "3f7a9c25-6d81-4e40-9b53-2a8c1f4d7e06",
                      "quantity": 20,
                      "source": "delivery_note",
                      "source_id": "9d1e6b48-5c20-4f37-a814-3b7e2d9f5a61",
                      "seq_number": 77,
                      "document_date": "2026-07-02",
                      "customer_name": null,
                      "vehicle_name": null,
                      "total": 128.0,
                      "created_at": "2026-07-02T11:14:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/articles/f18c4b62-9d07-4e53-a294-6b1f8d3c5e70/movements"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/calendar-entries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-13",
            "it": "Paginazione vera: `limit` (predefinito 20, massimo 100) vale anche per l'intervallo `from`/`to`, le voci sono ordinate per inizio e id e `next_starting_after` porta alla pagina successiva, anche filtrando per cliente o veicolo. Prima di questa revisione l'intervallo torna intero (al massimo 2000 voci) e il cursore è ignorato.",
            "en": "Real pagination: `limit` (default 20, max 100) applies to the `from`/`to` window too, entries are ordered by start and id, and `next_starting_after` leads to the next page, also when filtering by customer or vehicle. Before this revision the window comes back whole (at most 2000 entries) and the cursor is ignored."
          },
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Calendar"
        ],
        "summary": "List calendar entries",
        "x-summary-it": "Elenca gli appuntamenti",
        "x-scope": "calendar.read",
        "x-description-it": "Gli appuntamenti in agenda. Filtra per intervallo con `from` / `to`,\nper calendario con `calendar_id`, per cliente con `customer_id` o per\nveicolo con `vehicle_id`.\nOgni voce include cliente, veicolo e calendario già risolti, senza\nchiamate aggiuntive, e l'eventuale scheda lavoro collegata in\n`worksheet_id`.\n",
        "description": "Returns calendar entries scoped to the authenticated user's\ncompany.\n\nFilter dimensions are mutually exclusive — the first match\nwins, in order: `customer_id`, `vehicle_id`, then the default\ntimeframe. When no filter is supplied, returns entries from\nmidnight today to midnight 30 days later (Europe/Rome) across all\ncalendars.\n\nTimeframe: `from` / `to` select the entries that overlap the\ninstant window [`from`, `to`) — an entry ending exactly at `from`\nis not included.\n\nPagination (revision 2026-10-13 and later): every filter pages.\nEntries are ordered by `start_time`, then `id`; `limit` sets the\npage size (default 20, max 100) and `next_starting_after`, present\nwhile `has_more` is true, is the opaque cursor to send back as\n`starting_after`. The cursor marks a position, so entries created\nor deleted while paging don't shift the remaining pages. A cursor\nthat is not one this endpoint issued → 422 on `/starting_after`.\n\nOlder revisions: the timeframe query ignores `limit` and returns\nthe whole window, stopping at 2000 entries with `has_more`; the\n`customer_id` / `vehicle_id` lists return the first `limit` entries\nwith `has_more` but no cursor. `starting_after` is validated and\nignored.\n",
        "parameters": [
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "vehicle_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "calendar_id",
            "in": "query",
            "required": false,
            "description": "Restrict the timeframe listing to one parent calendar. Send it\nempty (`calendar_id=`) or omit it for \"all calendars\" — this tier\nstores no selection between requests. The effective selection is\nechoed in `calendar_filter`. Ignored when filtering by\n`customer_id` / `vehicle_id`.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of calendar entries.",
            "x-description-it": "Gli appuntamenti nell'intervallo richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CalendarEntry"
                          }
                        },
                        "calendar_filter": {
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/CalendarFilter"
                            }
                          ],
                          "description": "The effective calendar selection that was applied.\nPresent only on the default timeframe listing;\nomitted when filtering by `customer_id` / `vehicle_id`.\n"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "calendar_entry",
                      "id": "c72e9a41-8b35-4d07-9e62-1f4a8c3d5b90",
                      "title": "Tagliando Fiat Panda",
                      "description": "Cliente lascia l'auto alle 8:30.",
                      "start_time": "2026-08-12T08:30:00Z",
                      "end_time": "2026-08-12T10:00:00Z",
                      "color": "#2563eb",
                      "calendar_id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                      "user_id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "quote_id": null,
                      "checkin_id": null,
                      "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "work_category_name": "Tagliando",
                      "work_category_ids": [
                        "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                      ],
                      "work_categories": [
                        {
                          "object": "work_category",
                          "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                          "name": "Tagliando"
                        }
                      ],
                      "email_reminder": false,
                      "auto_reminder_enabled": false,
                      "auto_reminder_channel": null,
                      "auto_reminder_sent_at": null,
                      "customer": {
                        "object": "customer",
                        "id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                        "full_name": "Mario Rossi"
                      },
                      "vehicle": {
                        "object": "vehicle",
                        "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                        "model": "Panda",
                        "license": "AB123CD"
                      },
                      "calendar": {
                        "object": "calendar",
                        "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                        "name": "Officina 1"
                      },
                      "user": {
                        "object": "user",
                        "id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                        "name": "Luca Bianchi"
                      },
                      "deleted_at": null,
                      "created_at": "2026-08-01T09:00:00Z",
                      "updated_at": "2026-08-01T09:00:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/calendar-entries"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids` per impostare più categorie (massimo 10); la risposta include `work_category_ids` e `work_categories`. `work_category_id` resta valido con il significato di sempre.",
            "en": "Accepts `work_category_ids` to set several work categories (at most 10); the response includes `work_category_ids` and `work_categories`. `work_category_id` still works with its usual meaning."
          }
        ],
        "tags": [
          "Calendar"
        ],
        "summary": "Create a calendar entry",
        "x-summary-it": "Crea un appuntamento",
        "x-scope": "calendar.write",
        "x-description-it": "Fissa un appuntamento. Servono `start_time` e `end_time` in RFC 3339;\ncollega il lavoro con `customer_id`, `vehicle_id` e `worksheet_id`.\n\nCon `worksheet_id` l'appuntamento viene collegato alla scheda lavoro:\nse ometti `customer_id` o `vehicle_id` vengono presi dalla scheda; se\nli invii devono essere quelli della scheda (una scheda senza veicolo\nva bene per qualsiasi veicolo del suo cliente), altrimenti la\nrichiesta risponde `422` su `/worksheet_id`. Per inviare\n`worksheet_id` la chiave deve avere anche il permesso\n`worksheets.read`, altrimenti la richiesta risponde `403`.\n\nI promemoria automatici via SMS o WhatsApp non sono disponibili via\nAPI: inviano messaggi ai tuoi clienti e consumano crediti, quindi una\nrichiesta con `auto_reminder_enabled: true` e un canale a pagamento\nviene rifiutata con `403`. Il canale `email` resta ammesso perché usa\nil tuo mittente e non costa crediti.\n",
        "description": "Creates an appointment owned by the authenticated user inside\ntheir company. `end_time` must be strictly after `start_time`.\nThe response is the fully materialized entry (denormalized\ncustomer / vehicle / calendar / user fields included) so the\nclient can render the new row without a follow-up GET.\n\nForeign-key references in the body (`customer_id`, `vehicle_id`,\n`worksheet_id`, `work_category_id`, `calendar_id`) must resolve\nto resources owned by the authenticated user's company.\nUnreachable refs return 422 `parameter_invalid` pointing at the\noffending field.\n\n`worksheet_id` links the appointment to a scheda lavoro. Omit\n`customer_id` / `vehicle_id` to take them from the worksheet;\nwhen sent, they must be the worksheet's (a worksheet without a\nvehicle fits any vehicle of its customer), otherwise the request\nreturns 422 `parameter_invalid` on `/worksheet_id`. Sending\n`worksheet_id` needs `worksheets.read` (the key's scope, or the\napp user's permission); without it the request returns 403.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalendarEntryCreateRequest"
              },
              "example": {
                "title": "Tagliando Fiat Panda",
                "description": "Cambio olio + filtri.",
                "start_time": "2026-06-12T08:00:00Z",
                "end_time": "2026-06-12T10:00:00Z",
                "color": "#2563eb",
                "worksheet_id": "33333333-3333-3333-3333-333333333333",
                "email_reminder": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Calendar entry created.",
            "x-description-it": "L'appuntamento creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarEntry"
                },
                "example": {
                  "object": "calendar_entry",
                  "id": "c72e9a41-8b35-4d07-9e62-1f4a8c3d5b90",
                  "title": "Tagliando Fiat Panda",
                  "description": "Cliente lascia l'auto alle 8:30.",
                  "start_time": "2026-08-12T08:30:00Z",
                  "end_time": "2026-08-12T10:00:00Z",
                  "color": "#2563eb",
                  "calendar_id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                  "user_id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "quote_id": null,
                  "checkin_id": null,
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "email_reminder": false,
                  "auto_reminder_enabled": false,
                  "auto_reminder_channel": null,
                  "auto_reminder_sent_at": null,
                  "customer": {
                    "object": "customer",
                    "id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                    "full_name": "Mario Rossi"
                  },
                  "vehicle": {
                    "object": "vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "model": "Panda",
                    "license": "AB123CD"
                  },
                  "calendar": {
                    "object": "calendar",
                    "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                    "name": "Officina 1"
                  },
                  "user": {
                    "object": "user",
                    "id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                    "name": "Luca Bianchi"
                  },
                  "deleted_at": null,
                  "created_at": "2026-08-01T09:00:00Z",
                  "updated_at": "2026-08-01T09:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/calendar-entries/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Calendar entry id (UUID).",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Calendar"
        ],
        "summary": "Retrieve a calendar entry",
        "x-summary-it": "Recupera un appuntamento",
        "x-scope": "calendar.read",
        "x-description-it": "Restituisce il singolo appuntamento con cliente, veicolo, calendario\ne collaboratore già risolti.\n",
        "description": "Returns the entry if it belongs to the caller's company.\nUnknown, cross-tenant, and soft-deleted IDs all return 404 —\nnever 403 — to avoid leaking the existence of resources in\nother tenants.\n",
        "responses": {
          "200": {
            "description": "Calendar entry found.",
            "x-description-it": "L'appuntamento richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarEntry"
                },
                "example": {
                  "object": "calendar_entry",
                  "id": "c72e9a41-8b35-4d07-9e62-1f4a8c3d5b90",
                  "title": "Tagliando Fiat Panda",
                  "description": "Cliente lascia l'auto alle 8:30.",
                  "start_time": "2026-08-12T08:30:00Z",
                  "end_time": "2026-08-12T10:00:00Z",
                  "color": "#2563eb",
                  "calendar_id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                  "user_id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "quote_id": null,
                  "checkin_id": null,
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "email_reminder": false,
                  "auto_reminder_enabled": false,
                  "auto_reminder_channel": null,
                  "auto_reminder_sent_at": null,
                  "customer": {
                    "object": "customer",
                    "id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                    "full_name": "Mario Rossi"
                  },
                  "vehicle": {
                    "object": "vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "model": "Panda",
                    "license": "AB123CD"
                  },
                  "calendar": {
                    "object": "calendar",
                    "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                    "name": "Officina 1"
                  },
                  "user": {
                    "object": "user",
                    "id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                    "name": "Luca Bianchi"
                  },
                  "deleted_at": null,
                  "created_at": "2026-08-01T09:00:00Z",
                  "updated_at": "2026-08-01T09:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          },
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids`: sostituisce l'intero elenco delle categorie (`[]` o `null` li rimuove tutti). `work_category_id` continua a sostituire solo il primo dell'elenco. La risposta include `work_category_ids` e `work_categories`.",
            "en": "Accepts `work_category_ids`: replaces the whole work-category list (`[]` or `null` removes them all). `work_category_id` still replaces only the first in the list. The response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Calendar"
        ],
        "summary": "Update a calendar entry",
        "x-summary-it": "Aggiorna un appuntamento",
        "x-scope": "calendar.write",
        "x-description-it": "Aggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati.\n\n`worksheet_id` collega l'appuntamento a una scheda lavoro e `null`\nlo scollega. Cliente e veicolo risultanti devono essere quelli della\nscheda (una scheda senza veicolo va bene per qualsiasi veicolo del\nsuo cliente): quelli mancanti vengono presi dalla scheda, altrimenti\nla richiesta risponde `422` su `/worksheet_id`. Se cambi\n`customer_id` o `vehicle_id` senza inviare `worksheet_id` e la\nscheda collegata non corrisponde più, l'appuntamento viene\nscollegato e la risposta riporta `worksheet_id: null`. Per inviare\n`worksheet_id` la chiave deve avere anche il permesso\n`worksheets.read`, altrimenti la richiesta risponde `403`.\n\nValgono gli stessi limiti sui promemoria a pagamento della creazione.\nIn più, un appuntamento che ha già un promemoria automatico attivo\nnon è modificabile via API: la modifica potrebbe far partire un nuovo\nmessaggio al cliente, quindi la richiesta risponde `403` e va fatta\ndall'app.\n",
        "description": "Three-state PATCH semantics (omitted = unchanged, `null` =\nclear nullable, value = set).\n\nSending `null` on a non-nullable field (`title`, `color`,\n`start_time`, `end_time`, `email_reminder`,\n`auto_reminder_enabled`) returns 422 `parameter_invalid`. When\nthe patched pair of timestamps violates\n`end_time \u003e start_time` the request is rejected with 422.\n\n`user_id` (ownership), `company_id`, `auto_reminder_sent_at`,\nand the audit timestamps are not patchable through this\nendpoint.\n\nForeign-key references in the body (`customer_id`, `vehicle_id`,\n`worksheet_id`, `work_category_id`, `calendar_id`) must resolve\nto resources owned by the authenticated user's company.\nUnreachable refs return 422 `parameter_invalid` pointing at the\noffending field.\n\n`worksheet_id` links the appointment to a scheda lavoro and\n`null` unlinks it. The customer and vehicle left after the patch\nmust be the worksheet's (a worksheet without a vehicle fits any\nvehicle of its customer); the ones the entry is missing are\ntaken from the worksheet, and a mismatch returns 422\n`parameter_invalid` on `/worksheet_id`. A patch that moves\n`customer_id` or `vehicle_id` off the linked worksheet, without\nsending `worksheet_id`, unlinks it: the response then carries\n`worksheet_id: null`. Sending `worksheet_id` (value or `null`)\nneeds `worksheets.read` (the key's scope, or the app user's\npermission); without it the request returns 403.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalendarEntryPatchRequest"
              },
              "example": {
                "title": "Tagliando Fiat Panda — riprogrammato",
                "start_time": "2026-06-13T09:00:00Z",
                "end_time": "2026-06-13T11:00:00Z",
                "description": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calendar entry updated.",
            "x-description-it": "L'appuntamento aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarEntry"
                },
                "example": {
                  "object": "calendar_entry",
                  "id": "c72e9a41-8b35-4d07-9e62-1f4a8c3d5b90",
                  "title": "Tagliando Fiat Panda",
                  "description": "Cliente lascia l'auto alle 8:30.",
                  "start_time": "2026-08-12T08:30:00Z",
                  "end_time": "2026-08-12T10:00:00Z",
                  "color": "#2563eb",
                  "calendar_id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                  "user_id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "quote_id": null,
                  "checkin_id": null,
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "email_reminder": false,
                  "auto_reminder_enabled": false,
                  "auto_reminder_channel": null,
                  "auto_reminder_sent_at": null,
                  "customer": {
                    "object": "customer",
                    "id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                    "full_name": "Mario Rossi"
                  },
                  "vehicle": {
                    "object": "vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "model": "Panda",
                    "license": "AB123CD"
                  },
                  "calendar": {
                    "object": "calendar",
                    "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                    "name": "Officina 1"
                  },
                  "user": {
                    "object": "user",
                    "id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                    "name": "Luca Bianchi"
                  },
                  "deleted_at": null,
                  "created_at": "2026-08-01T09:00:00Z",
                  "updated_at": "2026-08-01T09:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Calendar"
        ],
        "summary": "Soft-delete a calendar entry",
        "x-summary-it": "Elimina un appuntamento",
        "x-scope": "calendar.write",
        "x-description-it": "Eliminazione logica dell'appuntamento.\n",
        "description": "Stamps `deleted_at` with NOW(). The row remains in the\ndatabase but is no longer addressable through the mobile API —\nsubsequent GET/PATCH/DELETE return 404. Returns the deleted\nentry (200) so clients can render an undo banner with the\npopulated `deleted_at`.\n",
        "responses": {
          "200": {
            "description": "Calendar entry soft-deleted.",
            "x-description-it": "L'appuntamento eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarEntry"
                },
                "example": {
                  "object": "calendar_entry",
                  "id": "c72e9a41-8b35-4d07-9e62-1f4a8c3d5b90",
                  "title": "Tagliando Fiat Panda",
                  "description": "Cliente lascia l'auto alle 8:30.",
                  "start_time": "2026-08-12T08:30:00Z",
                  "end_time": "2026-08-12T10:00:00Z",
                  "color": "#2563eb",
                  "calendar_id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                  "user_id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "quote_id": null,
                  "checkin_id": null,
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "email_reminder": false,
                  "auto_reminder_enabled": false,
                  "auto_reminder_channel": null,
                  "auto_reminder_sent_at": null,
                  "customer": {
                    "object": "customer",
                    "id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                    "full_name": "Mario Rossi"
                  },
                  "vehicle": {
                    "object": "vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "model": "Panda",
                    "license": "AB123CD"
                  },
                  "calendar": {
                    "object": "calendar",
                    "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                    "name": "Officina 1"
                  },
                  "user": {
                    "object": "user",
                    "id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                    "name": "Luca Bianchi"
                  },
                  "created_at": "2026-08-01T09:00:00Z",
                  "updated_at": "2026-08-01T09:00:00Z",
                  "deleted_at": "2026-08-05T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/calendars": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Calendar"
        ],
        "summary": "List calendars",
        "x-summary-it": "Elenca i calendari",
        "x-scope": "calendar.read",
        "x-description-it": "I calendari configurati dall'officina, da usare per valorizzare\n`calendar_id`. L'elenco è breve e non paginato.\n",
        "description": "Returns every calendar container in the company, unpaginated\n(workshops have a handful at most). The mobile calendar screen\nuses them for the \"Calendario\" filter sheet and to resolve\nentry colours; filter entries with `?calendar_id=` on\n`GET /v1/calendar-entries`.\n",
        "responses": {
          "200": {
            "description": "All calendars.",
            "x-description-it": "I calendari configurati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Calendar"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "calendar",
                      "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                      "name": "Officina 1",
                      "default_color": "#2563eb"
                    },
                    {
                      "object": "calendar",
                      "id": "6d2b9f14-7c58-4a03-b91e-3f5a8d2c6e47",
                      "name": "Carrozzeria",
                      "default_color": "#16a34a"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/calendars"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Calendar"
        ],
        "summary": "Create a calendar",
        "x-summary-it": "Crea un calendario",
        "x-scope": "calendar.write",
        "x-description-it": "Crea un calendario. `default_color` è il colore esadecimale usato in\nagenda per le voci che non ne specificano uno.\n",
        "operationId": "calendarCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "default_color": {
                    "type": "string",
                    "description": "Colore esadecimale usato in agenda, es. `#2563eb`."
                  }
                }
              },
              "example": {
                "name": "Officina 1",
                "default_color": "#2563eb"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "Il calendario creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "calendar",
                  "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                  "name": "Officina 1",
                  "default_color": "#2563eb"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/calendars/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Calendar"
        ],
        "summary": "Delete calendar",
        "x-summary-it": "Elimina un calendario",
        "x-scope": "calendar.write",
        "x-description-it": "Elimina il calendario. Gli appuntamenti che vi appartenevano\nvengono riassegnati a un altro calendario.\n",
        "operationId": "calendarDelete",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il calendario eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "calendar",
                  "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "tags": [
          "Calendar"
        ],
        "summary": "Update calendar",
        "x-summary-it": "Aggiorna un calendario",
        "x-scope": "calendar.write",
        "x-description-it": "Aggiorna nome e colore predefinito del calendario.\n",
        "operationId": "calendarPatch",
        "requestBody": {
          "required": true,
          "description": "Il corpo è lo stesso della creazione: va inviato\nl'oggetto completo, non solo i campi da cambiare.\n",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "default_color": {
                    "type": "string",
                    "description": "Colore esadecimale usato in agenda, es. `#2563eb`."
                  }
                }
              },
              "example": {
                "name": "Officina 1",
                "default_color": "#2563eb"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il calendario aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "calendar",
                  "id": "a18f3c65-2d90-4b47-8e13-6c9b2f5a7d04",
                  "name": "Officina 1",
                  "default_color": "#2563eb"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/cashbook-entries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "CashBook"
        ],
        "summary": "List cashbook entries",
        "x-summary-it": "Elenca i movimenti di cassa",
        "x-scope": "cashbook.read",
        "x-description-it": "I movimenti di prima nota, incassi e pagamenti insieme. Filtra per\nconto con `financial_account_id` e per periodo con `from` / `to`.\n\nOgni riga porta `running_balance`, il saldo progressivo dopo quel\nmovimento, e i riferimenti al documento che lo ha generato (fattura,\nscontrino, scheda lavoro o spesa).\n",
        "description": "Returns a paginated, filtered, ordered window of cashbook\nentries for the authenticated user's company.\n\nThe response envelope extends the standard `ListEnvelope`\nwith two cashbook-specific fields:\n\n- `opening_balance` — the running-balance anchor at the start\n  of the `from` window. Zero when no `from` is set (the\n  running balance simply accumulates from zero on the first\n  row).\n- `total_items` — the full filtered count, so clients can\n  render \"n of N\" without a follow-up request.\n\nPage rows carry `running_balance` already computed in\n`order_direction` from `opening_balance`. Do not re-sort\nclient-side without recomputing.\n",
        "parameters": [
          {
            "name": "tab",
            "in": "query",
            "required": false,
            "description": "Row-type filter. `tutto` (default) returns both income and\nexpense; `entrate` returns income only; `uscite` returns\nexpense only. The Italian tokens mirror the web app's URL\nparams so deep-links transfer 1:1.\n",
            "schema": {
              "type": "string",
              "enum": [
                "tutto",
                "entrate",
                "uscite"
              ],
              "default": "tutto"
            }
          },
          {
            "name": "financial_account_id",
            "in": "query",
            "required": false,
            "description": "Filter to a single financial account (UUID).",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `payment_date` (YYYY-MM-DD).\nWhen set, `opening_balance` in the response carries the\nrunning total of all earlier matching rows; the page\nitself starts from zero offset within the window.\n",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `payment_date` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on the counterparty name (`customer_name`\nfor income rows, `supplier_name` for expense rows).\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100. Out-of-range values clamp\nsilently — never error.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response's next-page link.\nPass verbatim. Omit on the first page.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order_direction",
            "in": "query",
            "required": false,
            "description": "Sort by `payment_date`. `desc` (default) lists\nnewest-first; `asc` lists oldest-first. Invalid values\nare rejected with 422 — silent clamping would silently\nflip the running balance.\n",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of cashbook entries.",
            "x-description-it": "I movimenti di prima nota.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CashBookListEnvelope"
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "cashbook_entry",
                      "id": "2f8b6d31-7a05-4c94-8e26-1d5f3a9c7b40",
                      "payment_type": "incasso",
                      "amount": 285.36,
                      "payment_date": "2026-07-17",
                      "financial_account_id": "d61c8b04-3f97-4a25-8e70-9b2d5f1a6c38",
                      "financial_account_name": "Cassa",
                      "customer_name": "Mario Rossi",
                      "supplier_name": null,
                      "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                      "invoice_seq_number": 214,
                      "invoice_issued_at": "2026-07-16T10:30:00Z",
                      "running_balance": 4821.9
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/cashbook-entries",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "List checkins",
        "x-summary-it": "Elenca le accettazioni",
        "x-scope": "checkins.read",
        "x-description-it": "Le accettazioni dell'officina, dalla più recente. Filtra per stato con\n`status` (`active` o `completed`), per testo con `q` e per periodo con\n`from` / `to`. Negli elenchi `line_items` è `null` per alleggerire la\nrisposta: le righe arrivano solo sul dettaglio.\n",
        "description": "Returns checkins in the authenticated user's company, newest\nfirst. Filter by status with `?status=active|completed`; search\nby customer name with `?q=`; bound by `created_at` with\n`?from=` / `?to=` / `?preset=`; paginate with `?limit=` +\n`?starting_after=`.\n\nThe date window defaults to the last 12 months; set it with\n`from`/`to`/`preset`. The applied range is echoed in\n`date_filter`. Pass `?preset=all`\nto list across all time.\n\nThe cursor is opaque — clients MUST NOT parse it. Pass back the\nvalue verbatim. `has_more` is `true` when more results exist\nbeyond this page.\n\nList responses omit `line_items` to keep payloads small. Fetch\nthe detail endpoint when you need the full shape.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on the linked customer's full name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by lifecycle state. Omit to return both.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "completed"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100. Out-of-range values clamp\nsilently to the defaults — never error.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response. Pass verbatim. Omit on\nthe first page.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Direzione di ordinamento: `asc` o `desc`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Colonna di ordinamento.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of checkins.",
            "x-description-it": "Una pagina di accettazioni.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "date_filter"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Checkin"
                          }
                        },
                        "date_filter": {
                          "$ref": "#/components/schemas/DateFilter"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "checkin",
                      "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                      "seq_number": 142,
                      "status": "active",
                      "title": "Accettazione tagliando",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "work_category_name": "Tagliando",
                      "work_category_ids": [
                        "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                      ],
                      "work_categories": [
                        {
                          "object": "work_category",
                          "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                          "name": "Tagliando"
                        }
                      ],
                      "km": 61240,
                      "fuel_level": 45,
                      "worksheet_id": null,
                      "quote_id": null,
                      "created_at": "2026-07-15T07:40:00Z",
                      "updated_at": "2026-07-15T07:55:00Z"
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJvIjoyMH0",
                  "url": "/v1/checkins",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids` per impostare più categorie (massimo 10); la risposta include `work_category_ids` e `work_categories`. `work_category_id` resta valido con il significato di sempre.",
            "en": "Accepts `work_category_ids` to set several work categories (at most 10); the response includes `work_category_ids` and `work_categories`. `work_category_id` still works with its usual meaning."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "Create a checkin",
        "x-summary-it": "Crea un'accettazione",
        "x-scope": "checkins.write",
        "x-description-it": "Registra l'accettazione di un veicolo. Il numero progressivo\n(`seq_number`) è assegnato dal server e resta stabile.\n\nI campi da `section1` a `section8` sono caselle di testo libero usate\nper le note di accettazione; l'API non impone loro alcun significato.\n`fuel_level` è un intero, per convenzione 0-100, senza vincoli.\n",
        "description": "Creates an intake/inspection record for an existing vehicle.\n\nRequired body fields: `customer_id`, `vehicle_id`. Everything\nelse is optional. The server assigns `id`, `company_id` (from\nthe authenticated user), `created_by`, `seq_number`, timestamps,\nand an initial `status` of `\"active\"`.\n\nLine-item creation is out of scope for v1 — a created checkin\nstarts with an empty `line_items` array. The response is the\nfully materialized post-create entity (same shape as\n`GET /v1/checkins/{id}`).\n\nForeign-key references in the body (`customer_id`, `vehicle_id`,\n`work_category_id`) must resolve to resources owned by the\nauthenticated user's company. Unreachable refs return 422\n`parameter_invalid` pointing at the offending field.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckinCreateRequest"
              },
              "example": {
                "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                "vehicle_id": "9181c4de-2a67-4b93-8f01-1e7d90a3c6b2",
                "title": "Intake — annual service",
                "fuel_level": 50,
                "km": 124301.5,
                "section1": "Front bumper has a scuff on the left side."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Checkin created.",
            "x-description-it": "L'accettazione creata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                },
                "example": {
                  "object": "checkin",
                  "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                  "seq_number": 142,
                  "status": "active",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "fuel_level": 45,
                  "worksheet_id": null,
                  "quote_id": null,
                  "created_at": "2026-07-15T07:40:00Z",
                  "updated_at": "2026-07-15T07:55:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Checkins"
        ],
        "summary": "Checkin status counts",
        "x-summary-it": "Conta le accettazioni per stato",
        "x-scope": "checkins.read",
        "x-description-it": "Quante accettazioni sono `active` e quante `completed`, con gli\nstessi filtri dell'elenco. Serve a popolare i contatori senza\nscaricare le accettazioni.\n",
        "operationId": "checkinStatusCounts",
        "description": "Returns how many accettazioni sit in each state, for the tab counters\non the checkins list (web checkin-list StatusCount parity). Accepts\nthe same filters as the list (minus status/pagination) so the numbers\nalways match what the list shows:\n\nq                   optional substring match on customer/vehicle/plate\nfrom, to, preset    date filter — same semantics as\nGET /v1/checkins; the applied range is echoed\nback in `date_filter`\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il numero di accettazioni per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinStatusCounts"
                },
                "example": {
                  "object": "checkin_status_counts",
                  "active": 4,
                  "completed": 87,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Checkin id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "Retrieve a checkin",
        "x-summary-it": "Recupera un'accettazione",
        "x-scope": "checkins.read",
        "x-description-it": "Restituisce l'accettazione con le righe (`line_items`). Se è già\nstata convertita, `worksheet_id` e `quote_id` puntano ai documenti\ngenerati.\n",
        "description": "Returns a single checkin by id, scoped to the authenticated\nuser's company. Unknown ids, ids from another company, and\nsoft-deleted rows all return 404 — never 403, never 200 with\ndeleted_at populated.\n\nDetail responses include `line_items`; list responses omit them.\n",
        "responses": {
          "200": {
            "description": "Checkin found.",
            "x-description-it": "L'accettazione richiesta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                },
                "example": {
                  "object": "checkin",
                  "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                  "seq_number": 142,
                  "status": "active",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "fuel_level": 45,
                  "worksheet_id": null,
                  "quote_id": null,
                  "created_at": "2026-07-15T07:40:00Z",
                  "updated_at": "2026-07-15T07:55:00Z",
                  "comments": "Cliente segnala rumore in frenata.",
                  "section1": "Carrozzeria senza danni evidenti.",
                  "section2": "Pneumatici anteriori da sostituire.",
                  "line_items": [
                    {
                      "object": "checkin_line_item",
                      "id": "5c1e8b73-2a94-4d06-b8f5-3e7a1d4c9601",
                      "description": "Controllare pastiglie anteriori",
                      "created_at": "2026-07-15T07:52:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          },
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids`: sostituisce l'intero elenco delle categorie (`[]` o `null` li rimuove tutti). `work_category_id` continua a sostituire solo il primo dell'elenco. La risposta include `work_category_ids` e `work_categories`.",
            "en": "Accepts `work_category_ids`: replaces the whole work-category list (`[]` or `null` removes them all). `work_category_id` still replaces only the first in the list. The response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "Update a checkin",
        "x-summary-it": "Aggiorna un'accettazione",
        "x-scope": "checkins.write",
        "x-description-it": "Aggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati, e `null` svuota il campo. Lo stato non si cambia da qui:\nusa `POST /v1/checkins/{id}/transitions`.\n",
        "description": "Three-state PATCH semantics (omitted = unchanged, `null` =\nclear, value = set).\n\nFields NOT patchable here: `status` (use the transitions\nendpoint), `customer_id`, `vehicle_id` (immutable),\n`seq_number`, `created_by`, timestamps, `line_items`.\n\nReturns the fully materialized post-update checkin.\n\nForeign-key references in the body (`work_category_id`,\n`worksheet_id`, `quote_id`) must resolve to resources owned by\nthe authenticated user's company. Unreachable refs return 422\n`parameter_invalid` pointing at the offending field.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckinPatchRequest"
              },
              "example": {
                "title": "Intake — updated note",
                "fuel_level": 75,
                "section1": null,
                "km": 124350
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkin updated.",
            "x-description-it": "L'accettazione aggiornata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                },
                "example": {
                  "object": "checkin",
                  "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                  "seq_number": 142,
                  "status": "active",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "fuel_level": 45,
                  "worksheet_id": null,
                  "quote_id": null,
                  "created_at": "2026-07-15T07:40:00Z",
                  "updated_at": "2026-07-15T07:55:00Z",
                  "comments": "Cliente segnala rumore in frenata.",
                  "section1": "Carrozzeria senza danni evidenti.",
                  "section2": "Pneumatici anteriori da sostituire.",
                  "line_items": [
                    {
                      "object": "checkin_line_item",
                      "id": "5c1e8b73-2a94-4d06-b8f5-3e7a1d4c9601",
                      "description": "Controllare pastiglie anteriori",
                      "created_at": "2026-07-15T07:52:00Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Checkins"
        ],
        "summary": "Soft-delete a checkin",
        "x-summary-it": "Elimina un'accettazione",
        "x-scope": "checkins.write",
        "x-description-it": "Eliminazione logica: l'accettazione sparisce dagli elenchi. I\ndocumenti eventualmente generati da essa restano intatti.\n",
        "description": "Stamps `deleted_at`; subsequent GET/PATCH/DELETE all 404.\nReturns the deleted resource (200) for consistency.\n",
        "responses": {
          "200": {
            "description": "Checkin soft-deleted.",
            "x-description-it": "L'accettazione eliminata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                },
                "example": {
                  "object": "checkin",
                  "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                  "seq_number": 142,
                  "status": "active",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "fuel_level": 45,
                  "worksheet_id": null,
                  "quote_id": null,
                  "created_at": "2026-07-15T07:40:00Z",
                  "updated_at": "2026-07-15T07:55:00Z",
                  "deleted_at": "2026-07-20T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera l'accettazione dal numero e dall'anno.",
            "en": "New: retrieve a check-in by its number and year."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "Retrieve a check-in by number",
        "x-summary-it": "Recupera l'accettazione dal numero",
        "x-scope": "checkins.read",
        "x-description-it": "Restituisce l'accettazione con il numero `number` dell'anno `year` (anno della data dell'accettazione): lo stesso numero mostrato nell'app, per esempio 12/2026.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the check-in carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026.\n\nThe response is identical to `GET /v1/checkins/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The check-in, exactly as retrieving it by id returns it.",
            "x-description-it": "L'accettazione, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                },
                "example": {
                  "object": "checkin",
                  "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                  "seq_number": 142,
                  "status": "active",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "fuel_level": 45,
                  "worksheet_id": null,
                  "quote_id": null,
                  "created_at": "2026-07-15T07:40:00Z",
                  "updated_at": "2026-07-15T07:55:00Z",
                  "comments": "Cliente segnala rumore in frenata.",
                  "section1": "Carrozzeria senza danni evidenti.",
                  "section2": "Pneumatici anteriori da sostituire.",
                  "line_items": [
                    {
                      "object": "checkin_line_item",
                      "id": "5c1e8b73-2a94-4d06-b8f5-3e7a1d4c9601",
                      "description": "Controllare pastiglie anteriori",
                      "created_at": "2026-07-15T07:52:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}/convert": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Checkin id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Il documento creato eredita l'elenco completo delle categorie, in ordine; la risposta include `work_category_ids` e `work_categories`.",
            "en": "The created document inherits the full ordered work-category list; the response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "Convert into a scheda lavoro",
        "x-summary-it": "Converti in scheda lavoro",
        "x-scope": "checkins.write",
        "x-description-it": "Genera una scheda lavoro dall'accettazione e collega i due documenti\n(`worksheet_id` sull'accettazione).\n\nAttenzione: la risposta è la **scheda lavoro** creata, non\nl'accettazione — l'oggetto restituito ha `object: worksheet`.\n",
        "description": "Creates a worksheet carrying the checkin's customer, vehicle, km,\ntitle, notes, work category and delivery date — the same conversion\nthe web performs (POST /hx/private/worksheets/checkins/{id}) — then\nlinks the checkin to it (status → `completed`, `worksheet_id` set).\nThe checkin is kept. A checkin already linked to a worksheet returns\n422; when the company requires km on vehicle-linked worksheets and\nthe checkin has none, 422 `parameter_missing` `/km`.\n\nNo request body.\n",
        "responses": {
          "201": {
            "description": "The freshly created scheda lavoro (full detail shape).",
            "x-description-it": "La scheda lavoro generata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "draft",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "total_after_tax": 0,
                  "frozen": false,
                  "created_at": "2026-07-15T08:12:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Checkins"
        ],
        "summary": "Create a checkin document",
        "x-summary-it": "Allega un documento all'accettazione",
        "x-scope": "checkins.write",
        "x-description-it": "Carica un file fra i documenti dell'accettazione. La richiesta è\n`multipart/form-data` con il file nel campo `file`, non JSON.\n",
        "operationId": "checkinDocumentCreate",
        "description": "multipart/form-data:\n\nfile         required — the document bytes (max 25MB)\ndescription  optional — display label; defaults to the file name\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "description": {
                    "type": "string",
                    "description": "Display label; defaults to the file name."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "Il documento allegato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinDocument"
                },
                "example": {
                  "object": "checkin_document",
                  "id": "7f2c6a18-9e40-4b53-8d21-4a6f1c9e3b75",
                  "name": "foto-danno.jpg",
                  "content_type": "image/jpeg",
                  "description": "Graffio parafango",
                  "file_size_bytes": 233104,
                  "url": "/v1/checkins/e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60/documents/7f2c6a18-9e40-4b53-8d21-4a6f1c9e3b75/download",
                  "created_at": "2026-07-15T07:58:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}/documents/{documentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Checkins"
        ],
        "summary": "Delete checkin document",
        "x-summary-it": "Elimina un documento",
        "x-scope": "checkins.write",
        "x-description-it": "Rimuove definitivamente il file allegato.\n",
        "operationId": "checkinDocumentDelete",
        "description": "CheckinDocumentDelete handles\nDELETE /v1/checkins/{id}/documents/{documentID}.\n",
        "responses": {
          "200": {
            "description": "success (the removed document)",
            "x-description-it": "Il documento eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckinDocument"
                },
                "example": {
                  "object": "checkin_document",
                  "id": "7f2c6a18-9e40-4b53-8d21-4a6f1c9e3b75",
                  "name": "foto-danno.jpg",
                  "content_type": "image/jpeg",
                  "description": "Graffio parafango",
                  "file_size_bytes": 233104,
                  "url": "/v1/checkins/e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60/documents/7f2c6a18-9e40-4b53-8d21-4a6f1c9e3b75/download",
                  "created_at": "2026-07-15T07:58:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}/documents/{documentID}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Checkins"
        ],
        "summary": "Download checkin document",
        "x-summary-it": "Scarica un documento",
        "x-scope": "checkins.read",
        "x-description-it": "Restituisce il contenuto del file, non JSON. È il percorso indicato\ndal campo `url` di ogni documento.\n",
        "operationId": "checkinDocumentDownload",
        "description": "CheckinDocumentDownload handles\nGET /v1/checkins/{id}/documents/{documentID}/download.\n",
        "responses": {
          "200": {
            "description": "success",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}/line-items/batch": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Checkin id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "Mutate checkin line items in a batch",
        "x-summary-it": "Modifica le righe in blocco",
        "x-scope": "checkins.write",
        "x-description-it": "Aggiunge, aggiorna, rimuove e riordina le righe in una sola\nrichiesta, applicata come singola transazione. Le righe di\nun'accettazione sono sole annotazioni: portano una `description` e\nnessun prezzo.\n",
        "description": "Adds, updates, and removes the checkin's line items in a single\natomic diff, then returns the full checkin with the updated\n`line_items` array. The top-level checkin PATCH is metadata-only;\nall line-item mutation flows through this endpoint.\n\nCheckin line items are description-only notes, so this batch is\nnarrower than the worksheet/quote one: the only field is\n`description`, and there is no `reorder` operation (the rows have\nno position). As with worksheets/quotes, `update` is applied as a\nremove+add internally, so an updated line receives a NEW id in the\nresponse. An id may appear in at most one of `remove`/`update`.\n\nAll arrays are optional; an empty body is a no-op.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckinLineItemsBatchRequest"
              },
              "example": {
                "add": [
                  {
                    "description": "Scratch on rear bumper"
                  },
                  {
                    "description": "Spare tyre missing"
                  }
                ],
                "update": [
                  {
                    "id": "4b1d8f2a-0c3e-4a6b-9d7c-1e2f3a4b5c6d",
                    "description": "Windshield chip (driver side)"
                  }
                ],
                "remove": [
                  "9f8e7d6c-5b4a-3c2d-1e0f-a9b8c7d6e5f4"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch applied. Returns the updated checkin (with `line_items`).",
            "x-description-it": "L'accettazione con le righe aggiornate.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                },
                "example": {
                  "object": "checkin",
                  "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                  "seq_number": 142,
                  "status": "active",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "fuel_level": 45,
                  "worksheet_id": null,
                  "quote_id": null,
                  "created_at": "2026-07-15T07:40:00Z",
                  "updated_at": "2026-07-15T07:55:00Z",
                  "comments": "Cliente segnala rumore in frenata.",
                  "section1": "Carrozzeria senza danni evidenti.",
                  "section2": "Pneumatici anteriori da sostituire.",
                  "line_items": [
                    {
                      "object": "checkin_line_item",
                      "id": "5c1e8b73-2a94-4d06-b8f5-3e7a1d4c9601",
                      "description": "Controllare pastiglie anteriori",
                      "created_at": "2026-07-15T07:52:00Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}/quotes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Checkin id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Il documento creato eredita l'elenco completo delle categorie, in ordine; la risposta include `work_category_ids` e `work_categories`.",
            "en": "The created document inherits the full ordered work-category list; the response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Checkins"
        ],
        "summary": "Create a preventivo from this accettazione",
        "x-summary-it": "Crea un preventivo dall'accettazione",
        "x-scope": "checkins.write",
        "x-description-it": "Genera un preventivo dall'accettazione e collega i due documenti\n(`quote_id` sull'accettazione).\n\nAttenzione: la risposta è il **preventivo** creato, non\nl'accettazione — l'oggetto restituito ha `object: quote`.\n",
        "description": "Creates a quote with the checkin's customer, vehicle, km, title,\nnotes, work category and delivery date. Each checkin line item\n(inconveniente) becomes a quote line at qty 1 pz with the company's\nfirst enabled VAT rate — the same conversion the web performs\n(POST /hx/private/quotes/checkins/{id}). Links the checkin\n(status → `completed`, `quote_id` set). The checkin is kept.\nAlready linked → 422.\n\nNo request body.\n",
        "responses": {
          "201": {
            "description": "The freshly created preventivo (full detail shape).",
            "x-description-it": "Il preventivo generato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "waiting",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "total_after_tax": 0,
                  "created_at": "2026-07-15T08:15:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/checkins/{id}/transitions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Checkin id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Checkins"
        ],
        "summary": "Transition a checkin's status",
        "x-summary-it": "Cambia stato all'accettazione",
        "x-scope": "checkins.write",
        "x-description-it": "Porta l'accettazione in `active` o `completed`. È l'unico modo di\ncambiare `status`: un valore diverso da questi due viene rifiutato con\n`422`.\n",
        "description": "Drives the lifecycle state machine. The body is\n`{\"action\": \"active\" | \"completed\"}`. Any other action value\nis a 422 `parameter_invalid`.\n\nStatus transitions live behind their own verb so the server\ncan stamp lifecycle timestamps and run side effects centrally\n— flipping `status` through PATCH is intentionally not\nsupported.\n\nReturns the fully materialized post-transition entity.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckinTransitionRequest"
              },
              "example": {
                "action": "completed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transition applied.",
            "x-description-it": "L'accettazione dopo la transizione.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checkin"
                },
                "example": {
                  "object": "checkin",
                  "id": "e4b7d290-6c15-4a83-9f27-1d8b3e5a7c60",
                  "seq_number": 142,
                  "status": "completed",
                  "title": "Accettazione tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "fuel_level": 45,
                  "worksheet_id": null,
                  "quote_id": null,
                  "created_at": "2026-07-15T07:40:00Z",
                  "updated_at": "2026-07-15T07:55:00Z",
                  "comments": "Cliente segnala rumore in frenata.",
                  "section1": "Carrozzeria senza danni evidenti.",
                  "section2": "Pneumatici anteriori da sostituire.",
                  "line_items": [
                    {
                      "object": "checkin_line_item",
                      "id": "5c1e8b73-2a94-4d06-b8f5-3e7a1d4c9601",
                      "description": "Controllare pastiglie anteriori",
                      "created_at": "2026-07-15T07:52:00Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/company": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Retrieve a company",
        "x-summary-it": "Recupera i dati azienda",
        "x-scope": "settings.read",
        "x-description-it": "L'anagrafica dell'officina e le preferenze che governano i documenti:\nmetodo di pagamento predefinito, colore, IBAN, se i prezzi sono IVA\ninclusa.\n\n`fiscal_fields_locked` indica che partita IVA e codice fiscale non\nsono più modificabili perché sono già stati usati su un documento\nfiscale.\n",
        "operationId": "companyGet",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I dati dell'azienda.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "company_profile",
                  "id": "1f8c4e73-2a95-4b06-9d31-7e5a2c8b6f40",
                  "name": "Officina Rossi S.r.l.",
                  "entity_type": "societa",
                  "company_type": "officina",
                  "tax_id": "01234567890",
                  "fiscal_code": "RSSMRA80A01F205X",
                  "email": "info@officinarossi.example.it",
                  "phone": "+390212345678",
                  "address": "Via Mecenate 12",
                  "city": "Milano",
                  "zip_code": "20138",
                  "province": "MI",
                  "fiscal_fields_locked": true,
                  "primary_color": "#2563eb",
                  "default_payment_method": "MP05",
                  "bank_account_holder": "Officina Rossi S.r.l.",
                  "line_items_including_vat": false,
                  "worksheet_km_required": true,
                  "waste_disposal_enabled": true,
                  "waste_disposal_rate": 1.2,
                  "waste_disposal_base": "articles",
                  "waste_disposal_description": "Smaltimento rifiuti"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/credit-notes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "CreditNotes"
        ],
        "summary": "List credit notes",
        "x-summary-it": "Elenca le note di credito",
        "x-scope": "invoices.read",
        "x-description-it": "Le note di credito emesse, dalla più recente. Stessa paginazione a\ncursore dell'elenco fatture.\n",
        "description": "Returns credit notes in the authenticated user's company,\nnewest first. Search with `?q=` (substring against customer /\ntitle), filter by SDI lifecycle status with `?sdi_status=`,\nand bound by `created_at` with `?from=` / `?to=` / `?preset=`.\nPaginate with cursor-based `?limit=` + `?starting_after=`.\n\nThe date window defaults to the last 12 months; set it with\n`from`/`to`/`preset`. The applied range is echoed in\n`date_filter`.\n\nThe cursor is opaque — pass it back verbatim.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on customer / title.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sdi_status",
            "in": "query",
            "required": false,
            "description": "Filter by SDI lifecycle status.",
            "schema": {
              "type": "string",
              "enum": [
                "to_send",
                "sent",
                "delivered",
                "rejected"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of credit notes.",
            "x-description-it": "Una pagina di note di credito.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "date_filter"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CreditNote"
                          }
                        },
                        "date_filter": {
                          "$ref": "#/components/schemas/DateFilter"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "credit_note",
                      "id": "9e5a3d78-1b46-4c02-8f93-7a2d5e1c8b40",
                      "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                      "seq_number": 12,
                      "title": "Storno parziale tagliando",
                      "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                      "sezionale_code": "A",
                      "customer_name": "Mario Rossi",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "sdi_status": "delivered",
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28,
                      "created_at": "2026-07-18T09:00:00Z",
                      "updated_at": "2026-07-18T09:00:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/credit-notes"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "CreditNotes"
        ],
        "summary": "Create a credit note from an invoice",
        "x-summary-it": "Crea una nota di credito",
        "x-scope": "invoices.write",
        "x-description-it": "Storna una fattura emessa: passa `invoice_id` e righe e totali\nvengono ripresi dalla fattura di origine. È il modo corretto di\nannullare una fattura già emessa, che non è eliminabile.\n",
        "description": "A credit note is a derivative document — every credit note\nis created FROM an existing invoice. The body carries the\nsource `invoice_id` (required) plus optional `title` and\n`sezionale_id`. All other fields (totals, customer, vehicle,\nseq number, timestamps) are derived from the source invoice\nor assigned server-side.\n\nThe source invoice MUST belong to the authenticated user's\ncompany. Unknown / cross-tenant invoice_ids return 404.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreditNoteCreateRequest"
              },
              "example": {
                "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                "title": "Storno parziale tagliando"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Credit note created.",
            "x-description-it": "La nota di credito creata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNote"
                },
                "example": {
                  "object": "credit_note",
                  "id": "9e5a3d78-1b46-4c02-8f93-7a2d5e1c8b40",
                  "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "seq_number": 12,
                  "title": "Storno parziale tagliando",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "customer_name": "Mario Rossi",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "sdi_status": "delivered",
                  "total_before_tax": 74.0,
                  "total_tax": 16.28,
                  "total_after_tax": 90.28,
                  "created_at": "2026-07-18T09:00:00Z",
                  "updated_at": "2026-07-18T09:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/credit-notes/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Credit note id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "CreditNotes"
        ],
        "summary": "Retrieve a credit note",
        "x-summary-it": "Recupera una nota di credito",
        "x-scope": "invoices.read",
        "x-description-it": "Restituisce la nota di credito e il riferimento alla fattura\nstornata (`invoice_id`).\n",
        "description": "Returns the credit note if it belongs to the authenticated\nuser's company. Unknown ids, ids from another company, and\nsoft-deleted rows all return 404 — never 403.\n",
        "responses": {
          "200": {
            "description": "Credit note found.",
            "x-description-it": "La nota di credito richiesta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNote"
                },
                "example": {
                  "object": "credit_note",
                  "id": "9e5a3d78-1b46-4c02-8f93-7a2d5e1c8b40",
                  "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "seq_number": 12,
                  "title": "Storno parziale tagliando",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "customer_name": "Mario Rossi",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "sdi_status": "delivered",
                  "total_before_tax": 74.0,
                  "total_tax": 16.28,
                  "total_after_tax": 90.28,
                  "created_at": "2026-07-18T09:00:00Z",
                  "updated_at": "2026-07-18T09:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "CreditNotes"
        ],
        "summary": "Update a credit note's title",
        "x-summary-it": "Aggiorna una nota di credito",
        "x-scope": "invoices.write",
        "x-description-it": "Aggiorna i campi descrittivi. Gli importi derivano dalla fattura di\norigine e non si modificano da qui.\n",
        "description": "`title` is the only mutable field on a credit note from the\nmobile API. All other fields are either derived from the\nsource invoice, immutable identity, or server-managed.\n\nThree-state PATCH semantics:\n\n- Omitted → leave the entity field unchanged.\n- Explicit JSON `null` → clear the title (SQL NULL).\n- Concrete value → set the title.\n\nSending any other key returns 400 `malformed_request` —\nsurfaces typos and stale schema knowledge.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreditNotePatchRequest"
              },
              "example": {
                "title": "Storno tagliando — corretto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Credit note updated.",
            "x-description-it": "La nota di credito aggiornata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNote"
                },
                "example": {
                  "object": "credit_note",
                  "id": "9e5a3d78-1b46-4c02-8f93-7a2d5e1c8b40",
                  "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "seq_number": 12,
                  "title": "Storno parziale tagliando",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "customer_name": "Mario Rossi",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "sdi_status": "delivered",
                  "total_before_tax": 74.0,
                  "total_tax": 16.28,
                  "total_after_tax": 90.28,
                  "created_at": "2026-07-18T09:00:00Z",
                  "updated_at": "2026-07-18T09:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "CreditNotes"
        ],
        "summary": "Soft-delete a credit note",
        "x-summary-it": "Elimina una nota di credito",
        "x-scope": "invoices.write",
        "x-description-it": "Eliminazione logica della nota di credito.\n\nUna nota di credito la cui controparte elettronica è già stata\ntrasmessa allo SdI non è eliminabile: la richiesta risponde\n`409`. Il documento è ormai un record fiscale e va gestito\ndal gestionale web.\n",
        "description": "Stamps `deleted_at`; the row stays in the database, and\nsubsequent GET/PATCH/DELETE all 404.\n\nIf the credit note has an already-transmitted SDI electronic\ncounterpart, deletion would orphan the electronic document\nand is refused with 409 `conflict`.\n\nReturns the deleted resource (200) for shape consistency\nand so the client can render an undo banner.\n",
        "responses": {
          "200": {
            "description": "Credit note soft-deleted.",
            "x-description-it": "La nota di credito eliminata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNote"
                },
                "example": {
                  "object": "credit_note",
                  "id": "9e5a3d78-1b46-4c02-8f93-7a2d5e1c8b40",
                  "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "seq_number": 12,
                  "title": "Storno parziale tagliando",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "customer_name": "Mario Rossi",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "sdi_status": "delivered",
                  "total_before_tax": 74.0,
                  "total_tax": 16.28,
                  "total_after_tax": 90.28,
                  "created_at": "2026-07-18T09:00:00Z",
                  "updated_at": "2026-07-18T09:00:00Z",
                  "deleted_at": "2026-07-20T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/credit-notes/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        },
        {
          "name": "sezionale_id",
          "in": "query",
          "required": false,
          "description": "Series the number belongs to: a sezionale id, or `default` for\nthe series without a sezionale. Omitted: every series is\nsearched, and a number used in more than one returns 409\n`ambiguous_number`.\n",
          "x-description-it": "Serie del numero: l'id del sezionale, oppure `default` per la\nnumerazione senza sezionale. Senza, la ricerca copre tutte le\nserie e un numero usato in più serie restituisce 409\n`ambiguous_number`.\n",
          "schema": {
            "type": "string"
          },
          "example": "default"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera la nota di credito dal numero e dall'anno.",
            "en": "New: retrieve a credit note by its number and year."
          }
        ],
        "tags": [
          "CreditNotes"
        ],
        "summary": "Retrieve a credit note by number",
        "x-summary-it": "Recupera la nota di credito dal numero",
        "x-scope": "invoices.read",
        "x-description-it": "Restituisce la nota di credito con il numero `number` dell'anno `year` (anno della nota di credito): lo stesso numero mostrato nell'app, per esempio 12/2026. Le fatture, le note di credito e gli scontrini sono numerati per sezionale: indica `sezionale_id` (l'id del sezionale, oppure `default` per la numerazione senza sezionale) per cercare in una sola serie; senza, la ricerca copre tutte le serie.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the credit note carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026. Numbers repeat across sezionali: pass `sezionale_id` (a sezionale id, or `default` for the series without one) to search one series; without it every series is searched.\n\nThe response is identical to `GET /v1/credit-notes/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The credit note, exactly as retrieving it by id returns it.",
            "x-description-it": "La nota di credito, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditNote"
                },
                "example": {
                  "object": "credit_note",
                  "id": "9e5a3d78-1b46-4c02-8f93-7a2d5e1c8b40",
                  "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "seq_number": 12,
                  "title": "Storno parziale tagliando",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "customer_name": "Mario Rossi",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "sdi_status": "delivered",
                  "total_before_tax": 74.0,
                  "total_tax": 16.28,
                  "total_after_tax": 90.28,
                  "created_at": "2026-07-18T09:00:00Z",
                  "updated_at": "2026-07-18T09:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/customer-field-settings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Retrieve customer field settings",
        "x-summary-it": "Recupera i campi cliente",
        "x-scope": "settings.read",
        "x-description-it": "Quali campi facoltativi della scheda cliente sono visibili (data di\nnascita e note interne), quali campi personalizzati sono definiti\n(con le `options` dei campi `list`), quali campi data generano un\nevento in agenda\n(`reminder_days_before`) e se il compleanno dei clienti viene messo\nin agenda (`birthday_calendar_event`).\n",
        "description": "Which optional customer fields are shown (birthday and internal\nnotes), which custom fields are defined (with the `options` of\n`list` fields), which date fields create an agenda event\n(`reminder_days_before`) and whether customers'\nbirthdays go in the agenda (`birthday_calendar_event`).\n",
        "operationId": "customerFieldSettingsGet",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "La configurazione dei campi cliente.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "customer_field_settings",
                  "field_visibility": {
                    "birthday": true,
                    "comments": true
                  },
                  "birthday_calendar_event": true,
                  "custom_fields": [
                    {
                      "id": "cf_4c1e7a",
                      "name": "Professione",
                      "type": "string",
                      "visible": true,
                      "order": 1,
                      "reminder_days_before": null
                    },
                    {
                      "id": "cf_9b20d3",
                      "name": "Scadenza patente",
                      "type": "datetime",
                      "visible": true,
                      "order": 2,
                      "reminder_days_before": 0
                    },
                    {
                      "id": "cf_7d14e2",
                      "name": "Fonte contatto",
                      "type": "list",
                      "options": [
                        "Passaparola",
                        "Google",
                        "Social",
                        "Volantino"
                      ],
                      "visible": true,
                      "order": 3,
                      "reminder_days_before": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "tags": [
          "Settings"
        ],
        "summary": "Replace customer field settings",
        "x-summary-it": "Imposta i campi cliente",
        "x-scope": "settings.write",
        "x-description-it": "Crea, modifica, riordina ed elimina i campi personalizzati dei\nclienti, aggiorna la visibilità dei campi facoltativi e l'evento di\ncompleanno in agenda, in un'unica chiamata.\n\n`custom_fields` è l'elenco completo: un campo con `id` modifica\nquello esistente (i valori salvati sui clienti restano), un campo\nsenza `id` ne crea uno nuovo, un campo non elencato viene eliminato.\nL'ordine dell'array è l'ordine di visualizzazione. Se\n`custom_fields` è assente la configurazione dei campi non cambia.\n\nCiò che il corpo omette viene conservato, non azzerato: una chiave\nassente in `field_visibility`, un `birthday_calendar_event` assente\ne, su un campo esistente, `visible` o `reminder_days_before` assenti\nmantengono l'impostazione salvata.\n\nSolo data di nascita e note interne possono essere nascoste: i campi\nfiscali e quelli legati a listini e fatturazione elettronica sono\nsempre visibili.\n\nUn campo nascosto non genera eventi in agenda, qualunque sia la sua\nimpostazione (che resta salvata e torna attiva quando il campo viene\nmostrato di nuovo): con `field_visibility.birthday` a `false` non\nvengono creati eventi di compleanno anche se\n`birthday_calendar_event` è `true`.\n\nGli eventi in agenda gia' creati non vengono toccati: spegnere un\nevento o nascondere un campo cambia solo cosa succede al prossimo\nsalvataggio di un cliente.\n\nUn campo `list` (dalla revisione 2026-10-22) è un menu a tendina:\n`options` elenca le voci nell'ordine scelto (da 1 a 20, ognuna di\n1–30 caratteri) e il valore salvato sul cliente è una di esse. Su\nun campo già `list`, `options` assente mantiene le voci salvate\n(come `visible`); un campo che diventa `list` deve indicarle; su\nun campo di altro tipo `options` è rifiutato.\n",
        "operationId": "customerFieldSettingsPut",
        "description": "Creates, updates, reorders and deletes customer custom field\ndefinitions, updates the optional-field visibility toggles and the\nbirthday agenda switch in one call.\n\n`custom_fields` is the complete list: an entry with `id` updates\nthat field (values stored on customers are kept), an entry without\n`id` creates one (the id is returned), an existing field left out\nis deleted. Array position is the display order. When\n`custom_fields` is absent the definitions are left unchanged.\n\nOmitted attributes are kept, never reset: an absent\n`field_visibility` key, an absent `birthday_calendar_event`, and an\nabsent `visible` or `reminder_days_before` on an existing field all\nkeep their stored value. New fields default to `visible: true` and\nno reminder.\n\nOnly the birthday and the internal notes can be hidden; fiscal,\nprice-list and e-invoicing fields always show.\n\nA hidden field creates no agenda events whatever its switch says;\nthe switch stays stored and applies again once the field is shown.\nWith `field_visibility.birthday` set to `false` no birthday events\nare created even when `birthday_calendar_event` is `true`. Events\nalready in the agenda are never touched here.\n\nA `list` field (from revision 2026-10-22) is a dropdown: `options`\nlists its choices in order (1 to 20, each 1–30 characters) and the\nvalue stored on the customer is one of them. On a field that is\nalready a `list` an absent `options` keeps the stored choices (the\nsame rule as `visible`); a field that becomes a `list` must carry\nthem; `options` on any other type is refused.\n\nReturns 422 `parameter_invalid` (with `param` pointing at the entry)\nfor an unknown or repeated `id`, a repeated name\n(case-insensitive), a name outside 2–30 characters, an unknown\n`type` (`/custom_fields/{i}/type`: \"type deve essere string,\ninteger, datetime o list.\"), invalid `options`\n(`/custom_fields/{i}/options`, or `/custom_fields/{i}/options/{j}`\nfor one choice; a `list` field sent without `options` and with none\nstored answers on `/custom_fields/{i}`) or more than 10 fields.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "field_visibility": {
                    "type": "object",
                    "description": "Quali campi facoltativi restano visibili nella scheda\ncliente. Le chiavi assenti mantengono il valore attuale.\n",
                    "properties": {
                      "birthday": {
                        "type": "boolean"
                      },
                      "comments": {
                        "type": "boolean",
                        "description": "Note interne."
                      }
                    }
                  },
                  "birthday_calendar_event": {
                    "type": "boolean",
                    "description": "Crea un evento in agenda il giorno del compleanno di ogni\ncliente con data di nascita. Conta solo se\n`field_visibility.birthday` è `true`. Assente: invariato.\n"
                  },
                  "custom_fields": {
                    "type": "array",
                    "maxItems": 10,
                    "description": "L'insieme completo dei campi personalizzati, nell'ordine\ndi visualizzazione. Quelli non elencati vengono rimossi;\nse il campo è assente la configurazione non cambia.\n",
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "type"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Id di un campo esistente (da GET) per modificarlo.\nOmettere per crearne uno nuovo: l'id viene generato\ne restituito. Un id sconosciuto o ripetuto è un 422.\n"
                        },
                        "name": {
                          "type": "string",
                          "minLength": 2,
                          "maxLength": 30,
                          "description": "Unico fra i campi (senza distinzione tra maiuscole e minuscole)."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "string",
                            "integer",
                            "datetime",
                            "list"
                          ],
                          "description": "`list` (dalla revisione 2026-10-22) è un menu a\ntendina: il valore è una delle `options`.\n"
                        },
                        "options": {
                          "type": "array",
                          "x-since": "2026-10-23",
                          "minItems": 1,
                          "maxItems": 20,
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 30
                          },
                          "description": "Le voci del menu a tendina, nell'ordine scelto. Solo\nper i campi `list`: da 1 a 20 voci di 1–30 caratteri\n(lettere, numeri, spazi e `- / . , ' + ( ) \u0026`), senza\ndoppioni (maiuscole e minuscole non contano); ogni\nvoce viene ripulita (spazi ai bordi tolti, spazi\ninterni ridotti a uno). Assente: invariato per un\ncampo che è già `list`; un campo che diventa `list`\ndeve indicarle. Su un campo di altro tipo è\nrifiutato. Errori: 422 `parameter_invalid` con\n`param` `/custom_fields/{i}/options` (l'elenco) o\n`/custom_fields/{i}/options/{j}` (una voce); un\ncampo `list` senza `options` e senza opzioni salvate\nrisponde su `/custom_fields/{i}`.\n"
                        },
                        "visible": {
                          "type": "boolean",
                          "description": "Mostra il campo nei moduli e nei dettagli cliente\n(`custom_field_values`). Assente: invariato per un\ncampo esistente, `true` per uno nuovo.\n"
                        },
                        "reminder_days_before": {
                          "type": "integer",
                          "nullable": true,
                          "description": "Genera un evento in agenda per la data di questo\ncampo. `null` nessun evento, `0` il giorno stesso\ndella scadenza. Solo per i campi `datetime`: sugli\naltri tipi viene ignorato. Assente: invariato per un\ncampo esistente, nessun evento per uno nuovo.\n\nOggi l'anticipo non e' configurabile: un valore\ndiverso da zero viene normalizzato a `0`.\n"
                        },
                        "order": {
                          "type": "integer",
                          "description": "Ignorato: l'ordine è la posizione nell'array. Accettato\ncosì che il corpo di una GET possa essere rispedito.\n"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "field_visibility": {
                  "birthday": true,
                  "comments": true
                },
                "birthday_calendar_event": false,
                "custom_fields": [
                  {
                    "id": "cf_4c1e7a",
                    "name": "Professione",
                    "type": "string"
                  },
                  {
                    "name": "Scadenza patente",
                    "type": "datetime",
                    "reminder_days_before": 0
                  },
                  {
                    "name": "Fonte contatto",
                    "type": "list",
                    "options": [
                      "Passaparola",
                      "Google",
                      "Social",
                      "Volantino"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "La configurazione aggiornata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "customer_field_settings",
                  "field_visibility": {
                    "birthday": true,
                    "comments": true
                  },
                  "birthday_calendar_event": false,
                  "custom_fields": [
                    {
                      "id": "cf_4c1e7a",
                      "name": "Professione",
                      "type": "string",
                      "visible": true,
                      "order": 1,
                      "reminder_days_before": null
                    },
                    {
                      "id": "cf_9b20d3",
                      "name": "Scadenza patente",
                      "type": "datetime",
                      "visible": true,
                      "order": 2,
                      "reminder_days_before": 0
                    },
                    {
                      "id": "cf_7d14e2",
                      "name": "Fonte contatto",
                      "type": "list",
                      "options": [
                        "Passaparola",
                        "Google",
                        "Social",
                        "Volantino"
                      ],
                      "visible": true,
                      "order": 3,
                      "reminder_days_before": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/customers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers",
        "x-scope": "customers.read",
        "x-summary-it": "Elenca i clienti",
        "x-description-it": "Restituisce i clienti della tua officina, dal più recente.\nCerca per nome con `?q=`. Impagina con `?limit=` e `?starting_after=`.\n\nIl cursore è opaco: non interpretarlo, rimandalo così com'è.\nUsa `has_more` per sapere se c'è un'altra pagina, non la lunghezza di\n`data`: l'ultima pagina può essere piena.\n",
        "description": "Returns customers in the authenticated user's company, newest\nfirst. Search by name with `?q=`; paginate with cursor-based\n`?limit=` + `?starting_after=`.\n\nThe cursor is opaque — clients MUST NOT parse it. Pass back\nthe value verbatim. Today it encodes an offset; tomorrow it\nmay encode a `(created_at, id)` tuple without a client-visible\nchange.\n\n`has_more` is `true` when more results exist beyond this page.\nDrive the \"load more\" UI off this flag, not off `data.length`\n— the last page may still contain a full `limit` worth of\nresults.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on the customer's full name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key (default `date` = creation date). Named keys are\nmapped server-side to whitelisted columns.\n",
            "schema": {
              "type": "string",
              "enum": [
                "date",
                "name",
                "vehicle",
                "spend"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction (default `desc`).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100. Out-of-range values clamp\nsilently to the defaults — never error.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response's next-page link. Pass\nverbatim. Omit on the first page.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of customers.",
            "x-description-it": "Una pagina di clienti.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Customer"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "customer",
                      "id": "8f173fd2-6fa4-46e5-a8e3-23dfff31a0a1",
                      "customer_type": "private",
                      "full_name": "Mario Rossi",
                      "email": "mario.rossi@example.com",
                      "phone": "+393331234567",
                      "city": "Milano"
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJvIjoyMH0",
                  "url": "/v1/customers"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Create a customer",
        "x-scope": "customers.write",
        "x-summary-it": "Crea un cliente",
        "x-description-it": "Crea un cliente nella tua officina. L'azienda e l'autore sono sempre\nricavati dalla chiave API: non puoi impostarli dal corpo della richiesta.\n\nObbligatorio: `customer_type`. Tutto il resto è facoltativo.\n\nI riferimenti ad altre risorse (per esempio `price_list_id`) devono\nappartenere alla tua officina, altrimenti ricevi 422 `parameter_invalid`\ncon l'indicazione del campo.\n\n`custom_fields` contiene i valori dei campi personalizzati, con chiave\nl'`id` definito in `GET /v1/customer-field-settings`.\n\nCome dal gestionale, vengono creati in agenda l'evento del compleanno\n(se `birthday` è impostato e l'officina lo tiene attivo) e un evento\nper ogni campo data personalizzato che lo prevede.\n\n`tax_id` è controllato come nel gestionale, in tutte le revisioni: per\nun cliente italiano (tutti i clienti creati via API) serve una partita\nIVA di 11 cifre con cifra di controllo corretta. Il numero è salvato\nsenza prefisso `IT`, spazi o punti. Se non è valido ricevi 422\n`parameter_invalid` con `param: \"/tax_id\"`.\n",
        "description": "Creates a customer in the authenticated user's company.\n`company_id` and `created_by` are always derived from the\nbearer token — neither can be set from the body.\n\nRequired: `customer_type`. Everything else is optional.\n\nReturns the fully materialized customer (re-fetched so\ncomputed fields like `last_vehicle_*` and `next_appointment`\npopulate consistently with GET).\n\nForeign-key references in the body (`price_list_id`, etc.) must\nresolve to resources owned by the authenticated user's company.\nUnreachable refs return 422 `parameter_invalid` pointing at the\noffending field.\n\n`custom_fields` carries custom field values keyed by the ids from\n`GET /v1/customer-field-settings` (see `CustomerCustomFieldValues`).\n\nAs on the web, creating a customer schedules its agenda events: the\nbirthday (when `birthday` is set and the company keeps birthday\nevents on) and each opted-in custom date field.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerCreateRequest"
              },
              "example": {
                "customer_type": "private",
                "full_name": "Mario Rossi",
                "phone": "+39 333 1234567",
                "email": "mario.rossi@example.com"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Customer created.",
            "x-description-it": "Cliente creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "object": "customer",
                  "id": "8f173fd2-6fa4-46e5-a8e3-23dfff31a0a1",
                  "customer_type": "private",
                  "full_name": "Mario Rossi",
                  "email": "mario.rossi@example.com",
                  "phone": "+393331234567",
                  "city": "Milano",
                  "created_at": "2026-08-03T14:32:11Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/customers/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Creation year (year of created_at).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The sequence number (seq_number).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 123
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Retrieve a customer by number",
        "x-summary-it": "Recupera un cliente per numero e anno",
        "x-scope": "customers.read",
        "description": "Returns the same detail response as GET /v1/customers/{id}, scoped to\nthe authenticated company. The number is unique within the creation\nyear. Deleted records and records from other companies return 404.\nAvailable on existing supported API revisions; no new revision is required.\n",
        "responses": {
          "200": {
            "description": "Customer found, with the same body as retrieval by id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Multiple records match; the error contains candidate ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/customers/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Customer id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Retrieve a customer",
        "x-scope": "customers.read",
        "x-summary-it": "Recupera un cliente",
        "x-description-it": "Restituisce un singolo cliente della tua officina.\n\nUn id sconosciuto, di un'altra officina o di un cliente eliminato\nrestituisce sempre 404: mai 403, mai 200 con il record eliminato.\n",
        "description": "Returns a single customer by id, scoped to the authenticated\nuser's company. Unknown ids, ids from another company, and\nsoft-deleted rows all return 404 — never 403, never 200 with\ndeleted_at populated. This prevents cross-tenant and\npost-deletion existence leaks.\n",
        "responses": {
          "200": {
            "description": "Customer found.",
            "x-description-it": "Cliente trovato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "object": "customer",
                  "id": "8f173fd2-6fa4-46e5-a8e3-23dfff31a0a1",
                  "customer_type": "private",
                  "full_name": "Mario Rossi",
                  "email": "mario.rossi@example.com",
                  "phone": "+393331234567",
                  "city": "Milano",
                  "created_at": "2026-08-03T14:32:11Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "Customers"
        ],
        "summary": "Update a customer",
        "x-scope": "customers.write",
        "x-summary-it": "Aggiorna un cliente",
        "x-description-it": "Header `If-Match` (obbligatorio dalla revisione 2026-10-22) con il campo `version` letto nel\ncliente: se è stato salvato nel frattempo la modifica è rifiutata con\n409 e nulla viene salvato.\n\nAggiorna solo i campi presenti nel corpo della richiesta: quelli omessi\nrestano invariati. Per svuotare un campo inviagli esplicitamente `null`.\n\n`custom_fields` sostituisce l'intero oggetto dei valori personalizzati\n(i campi nascosti conservano il loro valore se la chiave non è\ninviata).\n\nGli eventi in agenda generati dal cliente seguono le date: se cambi la\ndata di nascita o una data personalizzata l'evento si sposta, se la\nsvuoti l'evento viene rimosso.\n\n`tax_id` è controllato come nel gestionale, in tutte le revisioni, solo\nquando cambiano `tax_id` o `customer_type`. Per un cliente italiano\nserve una partita IVA valida (11 cifre, cifra di controllo); per\nun'azienda, un ente o una PA di un altro paese UE il numero IVA del\npaese, con formato e cifre di controllo verificati (AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GR, HR, HU, IE, IT, LT, LU, LV, MT, NL, PL, PT, RO, SE, SI, SK;\nper la Grecia il prefisso è EL). Il numero è salvato senza prefisso\npaese, spazi o punti. Se non è valido ricevi 422 `parameter_invalid` con\n`param: \"/tax_id\"`.\n",
        "description": "`If-Match` header (required from revision 2026-10-22) with the `version` you read: if the\nrecord was saved meanwhile the update is refused with 409 and\nnothing is written.\n\nPartial update following the design philosophy:\n\n- **Omitted JSON keys** leave the corresponding entity fields\n  untouched. The mobile UI can send only the keys it actually\n  changed.\n- **Explicit JSON `null`** clears the field (sets the column\n  to SQL NULL in the database). Use this to wipe an optional\n  field — there is no `?clear=phone` query param.\n- **Concrete values** overwrite the entity field with the value.\n\n`customer_type` is NOT NULL in the schema; sending it as\n`null` is a 422 `parameter_invalid` error.\n\n`custom_fields` replaces the whole values object (omitted = leave,\n`null` = clear); a hidden field keeps its value unless its key is\nsent. See `CustomerCustomFieldValues`.\n\nThe customer's generated agenda events follow its dates: changing\nthe birthday or a custom date moves its event, clearing it removes\nthe event, and a patch that touches no date leaves them alone.\n\nReturns the fully materialized post-update customer.\n\nForeign-key references in the body (`price_list_id`, etc.) must\nresolve to resources owned by the authenticated user's company.\nUnreachable refs return 422 `parameter_invalid` pointing at the\noffending field.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerPatchRequest"
              },
              "example": {
                "full_name": "Mario Rossi",
                "phone": null,
                "email": "mario.rossi@example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Customer updated. Returns the full post-update entity.",
            "x-description-it": "Cliente aggiornato. Restituisce la scheda completa.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "object": "customer",
                  "id": "8f173fd2-6fa4-46e5-a8e3-23dfff31a0a1",
                  "customer_type": "private",
                  "full_name": "Mario Rossi",
                  "email": "mario.rossi@example.com",
                  "phone": null,
                  "city": "Milano",
                  "created_at": "2026-08-03T14:32:11Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Soft-delete a customer",
        "x-scope": "customers.write",
        "x-summary-it": "Elimina un cliente",
        "x-description-it": "Eliminazione logica: il cliente sparisce dagli elenchi ma i documenti\ngià emessi restano validi e collegati.\n",
        "description": "Marks the customer as deleted by stamping `deleted_at` with the\nserver clock. The row stays in the database; subsequent\nGET/PATCH/DELETE all 404.\n\nReturns the deleted resource (200) rather than 204 No Content so\nthe response shape is consistent across the API (always a typed\nresource) and the mobile UI can render an undo banner with the\ntimestamp.\n\nRestoration is an admin-only operation; not exposed via this API.\n",
        "responses": {
          "200": {
            "description": "Customer soft-deleted. `deleted_at` is populated.",
            "x-description-it": "Cliente eliminato. Il campo «deleted_at» viene valorizzato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                },
                "example": {
                  "object": "customer",
                  "id": "8f173fd2-6fa4-46e5-a8e3-23dfff31a0a1",
                  "customer_type": "private",
                  "full_name": "Mario Rossi",
                  "email": "mario.rossi@example.com",
                  "phone": "+393331234567",
                  "city": "Milano",
                  "created_at": "2026-08-03T14:32:11Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/customers/{id}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Customer id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Upload a document",
        "x-scope": "customers.write",
        "x-summary-it": "Carica un documento",
        "x-description-it": "Allega un file alla scheda del cliente.\nLa richiesta è `multipart/form-data`.\n",
        "description": "Attaches a file to the customer — the one multipart write on this\nAPI (`Content-Type: multipart/form-data`). Storage layout matches\nthe web upload, so documents from either surface are identical.\nMax 25MB.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "description": {
                    "type": "string",
                    "description": "Display label; defaults to the file name."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored document (url = its download route).",
            "x-description-it": "Il documento salvato («url» è la rotta di download).",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDocument"
                },
                "example": {
                  "object": "customer_document",
                  "id": "3b91ee0c-19b4-4f0a-9c2e-7d51a0c88e14",
                  "name": "libretto.pdf",
                  "content_type": "application/pdf",
                  "file_size_bytes": 184320,
                  "url": "/v1/customers/8f173fd2-6fa4-46e5-a8e3-23dfff31a0a1/documents/3b91ee0c-19b4-4f0a-9c2e-7d51a0c88e14/download",
                  "created_at": "2026-08-03T14:41:02Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/customers/{id}/documents/{documentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Delete customer document",
        "x-scope": "customers.write",
        "x-summary-it": "Elimina un documento",
        "x-description-it": "Rimuove il file allegato alla scheda del cliente.\n",
        "operationId": "customerDocumentDelete",
        "description": "CustomerDocumentDelete handles\nDELETE /v1/customers/{id}/documents/{documentID}.\n",
        "responses": {
          "200": {
            "description": "success (the removed document)",
            "x-description-it": "Documento eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerDocument"
                },
                "example": {
                  "object": "customer_document",
                  "id": "3b91ee0c-19b4-4f0a-9c2e-7d51a0c88e14",
                  "name": "libretto.pdf",
                  "content_type": "application/pdf",
                  "file_size_bytes": 184320,
                  "url": "/v1/customers/8f173fd2-6fa4-46e5-a8e3-23dfff31a0a1/documents/3b91ee0c-19b4-4f0a-9c2e-7d51a0c88e14/download",
                  "created_at": "2026-08-03T14:41:02Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/customers/{id}/documents/{documentID}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Customer id (UUID).",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Document id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Download a document",
        "x-scope": "customers.read",
        "x-summary-it": "Scarica un documento",
        "x-description-it": "Restituisce un link temporaneo per scaricare il file allegato.\n",
        "description": "Streams the document bytes with the stored `Content-Type` and an\n`attachment` disposition — the target of every\n`customer_document.url`. Bearer-authenticated like every other\nroute; clients save the bytes and hand them to the OS viewer.\n",
        "responses": {
          "200": {
            "description": "The file bytes.",
            "x-description-it": "Il contenuto del file.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/dealership-report/{widget}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "widget",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "Dealership report widget",
        "x-summary-it": "Recupera un widget concessionaria",
        "x-scope": "reports.read",
        "x-description-it": "Come `GET /v1/report/{widget}`, ma per le statistiche della\nconcessionaria. Stessa busta di risposta.\n",
        "operationId": "dealershipReportWidget",
        "description": "the Situazione Concessionaria dashboard, mirroring the officina\nreport surface (one route per widget, progressive loading, the\nsame date filter as the list endpoints).\n\nWidget keys:\n\nkpis                    the web KPI row bundled: vehicles-sold,\ntotal-revenue, total-margin, average-days,\naverage-profit-per-vehicle,\nimmobilized-capital, loss-sales-percentage,\naverage-markup-percentage — each with the\nprevious-period delta\nsales-chart             revenue + margin series per period bucket\ntop-profitable-sales    ranked best sales by profit\nbottom-profitable-sales ranked worst sales by profit\ntop-giacenza-sales      fastest vehicles (lowest days in stock)\nbottom-giacenza-sales   slowest vehicles (highest days in stock)\n\nGate: dealership.read + reports.read, like the web dashboard (the\nmodule flag gates the menu client-side).\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I dati del widget concessionaria.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "report_widget",
                  "widget": "stock_aging",
                  "preset": "year",
                  "from": "2026-01-01",
                  "to": "2026-12-31",
                  "data": [
                    {
                      "label": "0-30 giorni",
                      "value": 4
                    },
                    {
                      "label": "31-90 giorni",
                      "value": 7
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/delivery-notes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "List delivery notes",
        "x-summary-it": "Elenca i documenti di trasporto",
        "x-scope": "suppliers.read",
        "x-description-it": "I DDT ricevuti dai fornitori, in sola lettura.\n",
        "operationId": "deliveryNoteList",
        "description": "The delivery notes (documenti di trasporto, DDT) received from suppliers.\nThe date filter defaults to the last 12 months; the applied\nrange is echoed in `date_filter`. Scoped to the authenticated user's\ncompany and gated on the articles.read permission (web parity — DDT lives\nunder the inventory module).\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on the supplier name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Tab filter: `draft` or `issued`. Omit for every status.",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "issued"
              ]
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Restrict to one supplier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key (default `date`).",
            "schema": {
              "type": "string",
              "enum": [
                "date",
                "number",
                "supplier",
                "total"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction (default `desc`).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `delivery_notes.created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `delivery_notes.created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a previous response's `next_starting_after`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "+ date_filter    success",
            "x-description-it": "I documenti di trasporto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DeliveryNote"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "delivery_note",
                      "id": "9d1e6b48-5c20-4f37-a814-3b7e2d9f5a61",
                      "seq_number": 77,
                      "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                      "supplier_name": "Ricambi Lombardia S.r.l.",
                      "description": "Consegna filtri e olio",
                      "status": "issued",
                      "delivery_date": "2026-07-02",
                      "total_before_tax": 128.0,
                      "total_tax": 28.16,
                      "total_after_tax": 156.16,
                      "created_at": "2026-07-02T11:14:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/delivery-notes",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/delivery-notes/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Delivery note status counts",
        "x-summary-it": "Conta i DDT per stato",
        "x-scope": "suppliers.read",
        "x-description-it": "Il numero di documenti di trasporto in ciascuno stato, con gli stessi\nfiltri dell'elenco.\n",
        "operationId": "deliveryNoteStatusCounts",
        "description": "Bozza / Emessi tab counters. Counts respect the date range, the\nfree-text search and the selected supplier (web parity). The applied range\nis echoed in date_filter.\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Filtra per fornitore.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il numero di DDT per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveryNoteStatusCounts"
                },
                "example": {
                  "object": "delivery_note_status_counts",
                  "draft": 1,
                  "issued": 64,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/electronic-invoices": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "ElectronicInvoicing"
        ],
        "summary": "List electronic invoices",
        "x-summary-it": "Elenca le fatture elettroniche emesse",
        "x-scope": "invoices.read",
        "x-description-it": "Le fatture elettroniche trasmesse allo SdI, con il loro stato\n(`sent`, `delivered`, `rejected`).\n\nL'intero gruppo è in sola lettura: la trasmissione allo SdI non è\nesposta su questa API. Rientra nel permesso `invoices`, non ne ha uno\nproprio.\n",
        "operationId": "electronicInvoiceList",
        "description": "(attive) transmissions, newest first.\n\ndocument_type (TD01/TD04/scontrino, \"\"=all), from/to/preset\n(default last 12 months, echoed in date_filter).\n",
        "parameters": [
          {
            "name": "document_type",
            "in": "query",
            "required": false,
            "description": "Filtra per tipo documento (es. `TD01`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero di risultati per pagina.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Cursore opaco della pagina precedente. Va restituito invariato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Le fatture elettroniche emesse.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "electronic_invoice",
                      "id": "6b2f9d54-8c17-4a03-be91-2d7c5a4f8e30",
                      "status": "delivered",
                      "document_type": "TD01",
                      "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                      "credit_note_id": null,
                      "receipt_number": null,
                      "seq_number": 214,
                      "customer_name": "Mario Rossi",
                      "vehicle_name": "Fiat Panda",
                      "total_after_discount": 285.36,
                      "date": "2026-07-16",
                      "sent_at": "2026-07-16T10:35:00Z",
                      "delivered_at": "2026-07-16T11:02:00Z",
                      "rejected_at": null,
                      "created_at": "2026-07-16T10:35:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/electronic-invoices",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/electronic-invoices-incoming": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "ElectronicInvoicing"
        ],
        "summary": "List electronic invoice incomings",
        "x-summary-it": "Elenca le fatture elettroniche ricevute",
        "x-scope": "invoices.read",
        "x-description-it": "Le fatture elettroniche ricevute dai fornitori. `expense_id` è\nvalorizzato quando la fattura è già stata registrata come spesa.\n",
        "operationId": "electronicInvoiceIncomingList",
        "description": "— the passive supplier invoices synced from ACube.\n\nFatturaPA code, \"\"=all), supplier_id, from/to/preset (default\nlast 12 months).\n",
        "parameters": [
          {
            "name": "document_type",
            "in": "query",
            "required": false,
            "description": "Filtra per tipo documento (es. `TD01`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero di risultati per pagina.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Cursore opaco della pagina precedente. Va restituito invariato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Filtra per fornitore.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Le fatture elettroniche ricevute.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "electronic_invoice_incoming",
                      "id": "4c7e1b90-6d23-4a85-9f31-7b2c8e5d4a06",
                      "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                      "business_name": "Ricambi Lombardia S.r.l.",
                      "invoice_number": "2026/1184",
                      "invoice_date": "2026-07-28",
                      "total_amount": 512.4,
                      "status": "delivered",
                      "document_type": "TD01",
                      "expense_id": "ba38f519-4c72-4e06-9d81-5a2c7f4b3e90",
                      "created_at": "2026-07-29T06:00:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/electronic-invoices-incoming",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/electronic-invoices-incoming/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "ElectronicInvoicing"
        ],
        "summary": "Electronic invoice incoming status counts",
        "x-summary-it": "Conta le fatture ricevute per stato",
        "x-scope": "invoices.read",
        "x-description-it": "Il numero di fatture ricevute in ciascuno stato, con gli stessi\nfiltri dell'elenco.\n",
        "operationId": "electronicInvoiceIncomingStatusCounts",
        "description": "/v1/electronic-invoices-incoming/status-counts — the Passive tab counters\n(Ricevute / In quarantena), same filters as the list minus status.\n",
        "parameters": [
          {
            "name": "document_type",
            "in": "query",
            "required": false,
            "description": "Filtra per tipo documento (es. `TD01`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Filtra per fornitore.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il numero di fatture ricevute per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "electronic_invoice_incoming_status_counts",
                  "received": 214,
                  "quarantena": 0,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/electronic-invoices/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "ElectronicInvoicing"
        ],
        "summary": "Electronic invoice status counts",
        "x-summary-it": "Conta le fatture elettroniche per stato",
        "x-scope": "invoices.read",
        "x-description-it": "Il numero di fatture elettroniche in ciascuno stato, con gli stessi\nfiltri dell'elenco.\n",
        "operationId": "electronicInvoiceStatusCounts",
        "description": "/v1/electronic-invoices/status-counts — the Attive tab counters. Uses the\nsame filters as the list minus status (so the counts stay stable while\nswitching tabs, web parity).\n",
        "parameters": [
          {
            "name": "document_type",
            "in": "query",
            "required": false,
            "description": "Filtra per tipo documento (es. `TD01`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il numero di fatture elettroniche per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "electronic_invoice_status_counts",
                  "sent": 3,
                  "delivered": 128,
                  "rejected": 1,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expense-categories": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Expenses"
        ],
        "summary": "List expense categories (unpaginated)",
        "x-summary-it": "Elenca le categorie di spesa",
        "x-scope": "expenses.read",
        "x-description-it": "Le categorie configurate dall'officina, da usare per valorizzare\n`expense_category_id`.\n",
        "responses": {
          "200": {
            "description": "Categories.",
            "x-description-it": "Le categorie di spesa.",
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "expense_category",
                      "id": "1a6f8d47-3c92-4e05-b731-8d2b5a9c4e60",
                      "name": "Ricambi"
                    },
                    {
                      "object": "expense_category",
                      "id": "5d2c9b78-4e13-40fa-8265-3b7a1f6d9c04",
                      "name": "Utenze"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/expense-categories"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Expenses"
        ],
        "summary": "List expenses",
        "x-summary-it": "Elenca le spese",
        "x-scope": "expenses.read",
        "x-description-it": "Le spese registrate. Filtra per stato con `status`, per fornitore con\n`supplier_id` e per periodo con `from` / `to`.\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero di risultati per pagina.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Cursore opaco della pagina precedente. Va restituito invariato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Filtra per fornitore.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Expenses + date_filter.",
            "x-description-it": "Le spese registrate.",
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "expense",
                      "id": "ba38f519-4c72-4e06-9d81-5a2c7f4b3e90",
                      "seq_number": 91,
                      "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                      "supplier_name": "Ricambi Lombardia S.r.l.",
                      "expense_category_id": "1a6f8d47-3c92-4e05-b731-8d2b5a9c4e60",
                      "category_name": "Ricambi",
                      "description": "Ricambi vari luglio",
                      "invoice_number": "2026/1184",
                      "comments": null,
                      "status": "to_pay",
                      "expired": false,
                      "payment_method": "MP05",
                      "payment_due_at": "2026-08-31T00:00:00Z",
                      "total_before_tax": 420.0,
                      "total_tax": 92.4,
                      "total_after_tax": 512.4,
                      "total_discount": 0,
                      "total_payments": 0,
                      "outstanding_payments": 512.4,
                      "is_stornata": false,
                      "created_at": "2026-07-28T09:15:00Z",
                      "updated_at": "2026-07-28T09:15:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/expenses",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Expenses"
        ],
        "summary": "Create an expense (totals-first)",
        "x-summary-it": "Registra una spesa",
        "x-scope": "expenses.write",
        "x-description-it": "Registra una spesa verso un fornitore. Gli importi si passano al\nnetto (`total_before_tax`) e come imposta (`total_tax`): il totale\nlordo è calcolato dal server.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "supplier_id",
                  "total_before_tax",
                  "total_tax"
                ],
                "properties": {
                  "supplier_id": {
                    "type": "string"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "invoice_number": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "expense_category_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "payment_method": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "payment_due_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "total_before_tax": {
                    "type": "number"
                  },
                  "total_tax": {
                    "type": "number"
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                "description": "Ricambi vari luglio",
                "invoice_number": "2026/1184",
                "payment_method": "MP05",
                "payment_due_at": "2026-08-31T00:00:00Z",
                "total_before_tax": 420.0,
                "total_tax": 92.4
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Expense.",
            "x-description-it": "La spesa registrata.",
            "content": {
              "application/json": {
                "example": {
                  "object": "expense",
                  "id": "ba38f519-4c72-4e06-9d81-5a2c7f4b3e90",
                  "seq_number": 91,
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l.",
                  "expense_category_id": "1a6f8d47-3c92-4e05-b731-8d2b5a9c4e60",
                  "category_name": "Ricambi",
                  "description": "Ricambi vari luglio",
                  "invoice_number": "2026/1184",
                  "comments": null,
                  "status": "to_pay",
                  "expired": false,
                  "payment_method": "MP05",
                  "payment_due_at": "2026-08-31T00:00:00Z",
                  "total_before_tax": 420.0,
                  "total_tax": 92.4,
                  "total_after_tax": 512.4,
                  "total_discount": 0,
                  "total_payments": 0,
                  "outstanding_payments": 512.4,
                  "is_stornata": false,
                  "created_at": "2026-07-28T09:15:00Z",
                  "updated_at": "2026-07-28T09:15:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Expenses"
        ],
        "summary": "Tab counters (to_pay / expired / paid)",
        "x-summary-it": "Conta le spese per stato",
        "x-scope": "expenses.read",
        "x-description-it": "Il numero di spese in ciascuno stato, con gli stessi filtri\ndell'elenco.\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Filtra per fornitore.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Counts + date_filter.",
            "x-description-it": "Il numero di spese per stato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "expense_status_counts",
                  "to_pay": 6,
                  "expired": 2,
                  "paid": 143,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Expenses"
        ],
        "summary": "Retrieve an expense (line items + installments)",
        "x-summary-it": "Recupera una spesa",
        "x-scope": "expenses.read",
        "x-description-it": "Restituisce la spesa con le righe, le rate, i pagamenti e il residuo\nda pagare (`outstanding_payments`).\n",
        "responses": {
          "200": {
            "description": "Expense.",
            "x-description-it": "La spesa richiesta.",
            "content": {
              "application/json": {
                "example": {
                  "object": "expense",
                  "id": "ba38f519-4c72-4e06-9d81-5a2c7f4b3e90",
                  "seq_number": 91,
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l.",
                  "expense_category_id": "1a6f8d47-3c92-4e05-b731-8d2b5a9c4e60",
                  "category_name": "Ricambi",
                  "description": "Ricambi vari luglio",
                  "invoice_number": "2026/1184",
                  "comments": null,
                  "status": "to_pay",
                  "expired": false,
                  "payment_method": "MP05",
                  "payment_due_at": "2026-08-31T00:00:00Z",
                  "total_before_tax": 420.0,
                  "total_tax": 92.4,
                  "total_after_tax": 512.4,
                  "total_discount": 0,
                  "total_payments": 0,
                  "outstanding_payments": 512.4,
                  "is_stornata": false,
                  "created_at": "2026-07-28T09:15:00Z",
                  "updated_at": "2026-07-28T09:15:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "Expenses"
        ],
        "summary": "Update header fields and totals",
        "x-summary-it": "Aggiorna una spesa",
        "x-scope": "expenses.write",
        "x-description-it": "Aggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "invoice_number": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "expense_category_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "payment_method": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "payment_due_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "total_before_tax": {
                    "type": "number"
                  },
                  "total_tax": {
                    "type": "number"
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "description": "Ricambi vari luglio (rettificato)",
                "total_before_tax": 400.0,
                "total_tax": 88.0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated expense.",
            "x-description-it": "La spesa aggiornata.",
            "content": {
              "application/json": {
                "example": {
                  "object": "expense",
                  "id": "ba38f519-4c72-4e06-9d81-5a2c7f4b3e90",
                  "seq_number": 91,
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l.",
                  "expense_category_id": "1a6f8d47-3c92-4e05-b731-8d2b5a9c4e60",
                  "category_name": "Ricambi",
                  "description": "Ricambi vari luglio",
                  "invoice_number": "2026/1184",
                  "comments": null,
                  "status": "to_pay",
                  "expired": false,
                  "payment_method": "MP05",
                  "payment_due_at": "2026-08-31T00:00:00Z",
                  "total_before_tax": 420.0,
                  "total_tax": 92.4,
                  "total_after_tax": 512.4,
                  "total_discount": 0,
                  "total_payments": 0,
                  "outstanding_payments": 512.4,
                  "is_stornata": false,
                  "created_at": "2026-07-28T09:15:00Z",
                  "updated_at": "2026-07-28T09:15:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Expenses"
        ],
        "summary": "Delete an expense",
        "x-summary-it": "Elimina una spesa",
        "x-scope": "expenses.write",
        "x-description-it": "Eliminazione logica della spesa.\n",
        "responses": {
          "200": {
            "description": "The removed expense.",
            "x-description-it": "La spesa eliminata.",
            "content": {
              "application/json": {
                "example": {
                  "object": "expense",
                  "id": "ba38f519-4c72-4e06-9d81-5a2c7f4b3e90",
                  "seq_number": 91,
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l.",
                  "expense_category_id": "1a6f8d47-3c92-4e05-b731-8d2b5a9c4e60",
                  "category_name": "Ricambi",
                  "description": "Ricambi vari luglio",
                  "invoice_number": "2026/1184",
                  "comments": null,
                  "status": "to_pay",
                  "expired": false,
                  "payment_method": "MP05",
                  "payment_due_at": "2026-08-31T00:00:00Z",
                  "total_before_tax": 420.0,
                  "total_tax": 92.4,
                  "total_after_tax": 512.4,
                  "total_discount": 0,
                  "total_payments": 0,
                  "outstanding_payments": 512.4,
                  "is_stornata": false,
                  "created_at": "2026-07-28T09:15:00Z",
                  "updated_at": "2026-07-28T09:15:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera la spesa dal numero e dall'anno.",
            "en": "New: retrieve a expense by its number and year."
          }
        ],
        "tags": [
          "Expenses"
        ],
        "summary": "Retrieve a expense by number",
        "x-summary-it": "Recupera la spesa dal numero",
        "x-scope": "expenses.read",
        "x-description-it": "Restituisce la spesa con il numero `number` dell'anno `year` (anno della data della spesa): lo stesso numero mostrato nell'app, per esempio 12/2026.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the expense carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026.\n\nThe response is identical to `GET /v1/expenses/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The expense, exactly as retrieving it by id returns it.",
            "x-description-it": "La spesa, con la stessa risposta del recupero per id.",
            "content": {
              "application/json": {
                "example": {
                  "object": "expense",
                  "id": "ba38f519-4c72-4e06-9d81-5a2c7f4b3e90",
                  "seq_number": 91,
                  "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "supplier_name": "Ricambi Lombardia S.r.l.",
                  "expense_category_id": "1a6f8d47-3c92-4e05-b731-8d2b5a9c4e60",
                  "category_name": "Ricambi",
                  "description": "Ricambi vari luglio",
                  "invoice_number": "2026/1184",
                  "comments": null,
                  "status": "to_pay",
                  "expired": false,
                  "payment_method": "MP05",
                  "payment_due_at": "2026-08-31T00:00:00Z",
                  "total_before_tax": 420.0,
                  "total_tax": 92.4,
                  "total_after_tax": 512.4,
                  "total_discount": 0,
                  "total_payments": 0,
                  "outstanding_payments": 512.4,
                  "is_stornata": false,
                  "created_at": "2026-07-28T09:15:00Z",
                  "updated_at": "2026-07-28T09:15:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/{id}/payments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Expenses"
        ],
        "summary": "List expense payments",
        "x-summary-it": "Elenca i pagamenti della spesa",
        "x-scope": "expenses.read",
        "x-description-it": "I pagamenti registrati sulla spesa. Il residuo è\n`outstanding_payments` sulla spesa stessa.\n",
        "operationId": "expensePaymentList",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I pagamenti della spesa.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "expense_payment",
                      "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                      "amount": 512.4,
                      "payment_method": "MP05",
                      "payment_date": "2026-08-01",
                      "financial_account_id": "d61c8b04-3f97-4a25-8e70-9b2d5f1a6c38",
                      "created_at": "2026-08-01T10:00:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/expenses/ba38f519-4c72-4e06-9d81-5a2c7f4b3e90/payments"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Expenses"
        ],
        "summary": "Create an expense payment",
        "x-summary-it": "Registra un pagamento",
        "x-scope": "expenses.write",
        "x-description-it": "Registra un pagamento sulla spesa. `payment_date` è nel formato\n`YYYY-MM-DD` e vale la data odierna se omesso. L'importo può essere\nparziale.\n\nA differenza di ogni altro endpoint, la risposta è un `201` **senza\ncorpo**: il pagamento non viene restituito. Rileggi la spesa per\nvedere i totali aggiornati.\n",
        "operationId": "expensePaymentCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount"
                ],
                "properties": {
                  "amount": {
                    "type": "number"
                  },
                  "payment_method": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "payment_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Formato `YYYY-MM-DD`, fra 1900-01-01 e 2100-12-31. Se omesso\nvale la data odierna.\n"
                  },
                  "financial_account_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "amount": 512.4,
                "payment_method": "MP05",
                "payment_date": "2026-08-01"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. Unlike every other endpoint on this API this one\nanswers with an EMPTY body — the payment is not echoed back.\nRe-read the expense to see the updated totals.\n",
            "x-description-it": "Pagamento registrato. Risposta senza corpo."
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/expenses/{id}/payments/{paymentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "paymentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Expenses"
        ],
        "summary": "Delete expense payment",
        "x-summary-it": "Elimina un pagamento",
        "x-scope": "expenses.write",
        "x-description-it": "Rimuove il pagamento e riporta il residuo della spesa al valore\nprecedente.\n",
        "operationId": "expensePaymentDelete",
        "responses": {
          "204": {
            "description": "Deleted. No body — unlike the invoice and worksheet payment\ndeletes, which echo the removed payment.\n",
            "x-description-it": "Pagamento eliminato. Risposta senza corpo."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/financial-accounts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "CashBook"
        ],
        "summary": "List the company's financial accounts",
        "x-summary-it": "Elenca i conti",
        "x-scope": "cashbook.read",
        "x-description-it": "I conti configurati (cassa, banca), da usare per valorizzare\n`financial_account_id` sui pagamenti.\n",
        "description": "The conti (Banca, Cassa, …) — the options of the prima nota\nConto filter, mirroring the web cashbook's account dropdown.\nUnpaginated; read-only (accounts are managed in the web\nsettings).\n",
        "responses": {
          "200": {
            "description": "The company's accounts.",
            "x-description-it": "I conti configurati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "object",
                          "id",
                          "type",
                          "name"
                        ],
                        "properties": {
                          "object": {
                            "type": "string",
                            "const": "financial_account"
                          },
                          "id": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string",
                            "example": "bank"
                          },
                          "name": {
                            "type": "string",
                            "example": "Banca"
                          }
                        }
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "financial_account",
                      "id": "d61c8b04-3f97-4a25-8e70-9b2d5f1a6c38",
                      "type": "cassa",
                      "name": "Cassa"
                    },
                    {
                      "object": "financial_account",
                      "id": "7e3c9a25-1b68-4f40-9d72-5a8e2c6b3f19",
                      "type": "banca",
                      "name": "Banca Intesa"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/financial-accounts"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "CashBook"
        ],
        "summary": "Create a financial account",
        "x-summary-it": "Crea un conto",
        "x-scope": "cashbook.write",
        "x-description-it": "Crea un conto finanziario. `type` distingue cassa e banca.\n",
        "operationId": "financialAccountCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "type"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string",
                    "description": "Tipo di conto, es. `cassa` o `banca`."
                  }
                }
              },
              "example": {
                "name": "Banca Intesa",
                "type": "banca"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "Il conto creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "financial_account",
                  "name": "Banca Intesa",
                  "type": "banca"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/financial-accounts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "CashBook"
        ],
        "summary": "Delete financial account",
        "x-summary-it": "Elimina un conto",
        "x-scope": "cashbook.write",
        "x-description-it": "Elimina un conto senza movimenti. Un conto su cui è registrato\nalmeno un movimento (pagamento di fattura, scontrino o spesa,\nmovimento manuale di prima nota) non si elimina: `409`, e i\nmovimenti non vengono spostati; il conto si può rinominare. Anche\nl'ultimo conto Banca o l'ultimo conto Cassa non si elimina: `409`.\n",
        "description": "Deletes an account with no movements. An account with at least\none recorded movement (invoice, scontrino or expense payment, or\na manual prima-nota entry) is not deleted: 409, and movements\nare never moved; the account can be renamed. The last Banca or\nthe last Cassa account is not deleted either: 409.\n",
        "operationId": "financialAccountDelete",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il conto eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "financial_account",
                  "id": "7e3c9a25-1b68-4f40-9d72-5a8e2c6b3f19"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "tags": [
          "CashBook"
        ],
        "summary": "Update financial account",
        "x-summary-it": "Aggiorna un conto",
        "x-scope": "cashbook.write",
        "x-description-it": "Aggiorna il nome del conto.\n",
        "operationId": "financialAccountPatch",
        "description": "(rename only — the type is fixed at creation, web parity).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "name": "Nuovo nome"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il conto aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "financial_account",
                  "id": "7e3c9a25-1b68-4f40-9d72-5a8e2c6b3f19"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovo campo `work_category_names`: le categorie della scheda lavoro collegata, in ordine.",
            "en": "New field `work_category_names`: the linked worksheet's work categories, in order."
          }
        ],
        "tags": [
          "Invoices"
        ],
        "summary": "List invoices",
        "x-summary-it": "Elenca le fatture",
        "x-scope": "invoices.read",
        "x-description-it": "Le fatture dell'officina, dalla più recente. Filtra per stato con\n`status` (`draft` o `issued`), per cliente con `customer_id` e per\nperiodo con `from` / `to`. La paginazione usa un cursore opaco: passa\n`next_starting_after` invariato in `starting_after`.\n",
        "description": "Returns invoices in the authenticated user's company, newest\nfirst. Filter by name (`?q=`), payment state\n(`?payment_filter=pending|settled`), fiscal status\n(`?fiscal_status=draft|issued`), or date range\n(`?from=`/`?to=`/`?preset=`). Paginate with cursor-based\n`?limit=` + `?starting_after=`.\n\nThe date window defaults to the last 12 months; set it with\n`from`/`to`/`preset`. The applied range is echoed in\n`date_filter`.\n\nThe cursor is opaque — clients MUST NOT parse it. Pass back\nthe value verbatim. `has_more` drives the \"load more\" UI;\na full page may still be the last page.\n\nList rows OMIT `line_items` to keep payloads small. Fetch\nthe detail endpoint for the full body.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on invoice display fields.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "payment_filter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "settled"
              ]
            }
          },
          {
            "name": "fiscal_status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "issued"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key (default `date` = emission date, i.e. `issued_at`\nfalling back to `created_at`). Named keys are mapped\nserver-side to whitelisted columns.\n",
            "schema": {
              "type": "string",
              "enum": [
                "date",
                "number",
                "customer",
                "vehicle",
                "total",
                "outstanding"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction (default `desc`).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "$ref": "#/components/parameters/WorkCategoryFilter"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "without_electronic_invoice",
            "in": "query",
            "required": false,
            "description": "Solo le fatture senza controparte elettronica.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of invoices (line_items omitted).",
            "x-description-it": "Una pagina di fatture.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "date_filter",
                        "work_category_filter"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Invoice"
                          }
                        },
                        "date_filter": {
                          "$ref": "#/components/schemas/DateFilter"
                        },
                        "work_category_filter": {
                          "$ref": "#/components/schemas/WorkCategoryFilter"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "invoice",
                      "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                      "work_category_name": "Tagliando",
                      "work_category_names": [
                        "Tagliando",
                        "Freni"
                      ],
                      "status": "issued",
                      "seq_number": 214,
                      "title": "Tagliando 60.000 km",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                      "sezionale_code": "A",
                      "payment_method": "bonifico",
                      "payment_due_at": "2026-08-15T00:00:00Z",
                      "total_before_tax": 233.9,
                      "total_tax": 51.46,
                      "total_after_tax": 285.36,
                      "total_payments": 150.0,
                      "outstanding_payments": 135.36,
                      "sdi_status": "delivered",
                      "issued_at": "2026-07-16T10:30:00Z",
                      "created_at": "2026-07-16T10:12:00Z",
                      "updated_at": "2026-07-16T10:30:00Z"
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJvIjoyMH0",
                  "url": "/v1/invoices",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Create a draft invoice from a worksheet",
        "x-summary-it": "Crea una fattura",
        "x-scope": "invoices.write",
        "x-description-it": "La fattura nasce sempre da una scheda lavoro: passa `worksheet_id` e\nrighe, totali e anagrafiche vengono copiati da lì. Non esiste un\nflusso «fattura vuota + aggiungi righe» su questa API.\n\nLa fattura creata è in stato `draft` e non ha ancora numero: il\nnumero viene assegnato da `POST /v1/invoices/{id}/issue`.\n",
        "description": "Snapshots the referenced worksheet's totals and line items\ninto a new draft invoice. The worksheet MUST belong to the\nauthenticated user's company; cross-tenant requests return\n404 (same as direct invoice access).\n\n`payment_due_at` is optional. When omitted the server\ncomputes today + the company's default payment term (e.g.\n`30_days`), matching the web flow.\n\nA worksheet has at most one live invoice: when it already has\none (or produces a scontrino) the request answers 409 `conflict`.\n\nThe response includes the materialized `line_items` array.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceCreateRequest"
              },
              "example": {
                "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                "payment_method": "bank_transfer",
                "payment_due_at": "2026-06-30T00:00:00Z"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invoice created.",
            "x-description-it": "La fattura creata, in bozza.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "object": "invoice",
                  "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "work_category_name": "Tagliando",
                  "work_category_names": [
                    "Tagliando",
                    "Freni"
                  ],
                  "status": "draft",
                  "seq_number": 214,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "payment_method": "bonifico",
                  "payment_due_at": "2026-08-15T00:00:00Z",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "sdi_status": "delivered",
                  "issued_at": "2026-07-16T10:30:00Z",
                  "created_at": "2026-07-16T10:12:00Z",
                  "updated_at": "2026-07-16T10:30:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/payment-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "Count invoices per payment state",
        "x-summary-it": "Conta le fatture per stato di incasso",
        "x-scope": "invoices.read",
        "x-description-it": "Quante fatture risultano da incassare (`pending`) e quante saldate\n(`settled`), con gli stessi filtri dell'elenco.\n",
        "description": "Returns how many invoices are pending payment vs fully settled,\nfor the tab counters above the mobile list. Accepts the same\nfilters as `GET /v1/invoices` minus `payment_filter` and\npagination (`q`, `from`/`to`/`preset` with the same date-filter\nsemantics), so the numbers always match what the\nlist shows. The applied range is echoed in `date_filter`.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on invoice display fields.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "$ref": "#/components/parameters/WorkCategoryFilter"
          },
          {
            "name": "fiscal_status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato fiscale del documento.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Counts per payment state.",
            "x-description-it": "Il numero di fatture per stato di incasso.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoicePaymentCounts"
                },
                "example": {
                  "object": "invoice_payment_counts",
                  "pending": 7,
                  "settled": 132,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Invoice id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovo campo `work_category_names`: le categorie della scheda lavoro collegata, in ordine.",
            "en": "New field `work_category_names`: the linked worksheet's work categories, in order."
          }
        ],
        "tags": [
          "Invoices"
        ],
        "summary": "Retrieve an invoice",
        "x-summary-it": "Recupera una fattura",
        "x-scope": "invoices.read",
        "x-description-it": "Restituisce la fattura con le righe (`line_items`) e i totali. Il\ncampo `sdi_status` riporta lo stato della trasmissione elettronica in\nsola lettura: l'invio allo SdI non è esposto su questa API.\n",
        "description": "Returns a single invoice by id, scoped to the authenticated\nuser's company. Unknown ids, ids from another company, and\nsoft-deleted rows all return 404 — never 403, never 200 with\n`deleted_at` populated.\n\nIncludes the nested `line_items` array.\n",
        "responses": {
          "200": {
            "description": "Invoice found.",
            "x-description-it": "La fattura richiesta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "object": "invoice",
                  "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "work_category_name": "Tagliando",
                  "work_category_names": [
                    "Tagliando",
                    "Freni"
                  ],
                  "status": "issued",
                  "seq_number": 214,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "payment_method": "bonifico",
                  "payment_due_at": "2026-08-15T00:00:00Z",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "sdi_status": "delivered",
                  "issued_at": "2026-07-16T10:30:00Z",
                  "created_at": "2026-07-16T10:12:00Z",
                  "updated_at": "2026-07-16T10:30:00Z",
                  "line_items": [
                    {
                      "object": "invoice_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "Invoices"
        ],
        "summary": "Update an invoice",
        "x-summary-it": "Aggiorna una fattura",
        "x-scope": "invoices.write",
        "x-description-it": "Aggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati, e `null` svuota il campo. Una fattura già emessa accetta\nsolo modifiche che non incidono sui valori fiscali.\n",
        "description": "Partial update following the design philosophy:\n\n- Omitted JSON keys leave the corresponding entity fields\n  untouched.\n- Explicit JSON `null` clears the field (SQL NULL) where the\n  column is nullable.\n- Concrete values overwrite the entity field.\n\nNon-nullable columns (`payment_method`, `payment_condition`,\n`payment_due_at`, `vat_exigibility`) reject explicit `null`\nwith 422 `parameter_invalid`.\n\nFields NOT patchable through this endpoint: `id`,\n`seq_number`, `status`, `customer_id`, `vehicle_id`,\n`worksheet_id`, all totals, all timestamps except\n`payment_due_at`. Line items are not mutable via PATCH.\nUse POST `/v1/invoices/{id}/issue` to transition a draft.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoicePatchRequest"
              },
              "example": {
                "title": "Tagliando completo",
                "comments": null,
                "payment_method": "card",
                "payment_due_at": "2026-07-15T00:00:00Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice updated.",
            "x-description-it": "La fattura aggiornata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "object": "invoice",
                  "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "work_category_name": "Tagliando",
                  "work_category_names": [
                    "Tagliando",
                    "Freni"
                  ],
                  "status": "issued",
                  "seq_number": 214,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "payment_method": "bonifico",
                  "payment_due_at": "2026-08-15T00:00:00Z",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "sdi_status": "delivered",
                  "issued_at": "2026-07-16T10:30:00Z",
                  "created_at": "2026-07-16T10:12:00Z",
                  "updated_at": "2026-07-16T10:30:00Z",
                  "line_items": [
                    {
                      "object": "invoice_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Invoices"
        ],
        "summary": "Soft-delete an invoice",
        "x-summary-it": "Elimina una fattura",
        "x-scope": "invoices.write",
        "x-description-it": "Eliminazione logica della fattura.\n\nUna fattura la cui controparte elettronica è già stata trasmessa\nallo SdI non è eliminabile: la richiesta risponde `409`. In quel\ncaso il documento va stornato con una nota di credito\n(`POST /v1/credit-notes`), non eliminato.\n",
        "description": "Stamps `deleted_at`; the row stays in the database, and\nsubsequent GET/PATCH/DELETE/issue all 404. Only a draft can be\ndeleted; its payments (and their attachments) are deleted with\nit. Returns the\ndeleted resource (200) for consistency with the rest of the\nAPI and so the client can render an undo banner.\n",
        "responses": {
          "200": {
            "description": "Invoice soft-deleted.",
            "x-description-it": "La fattura eliminata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "object": "invoice",
                  "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "work_category_name": "Tagliando",
                  "work_category_names": [
                    "Tagliando",
                    "Freni"
                  ],
                  "status": "issued",
                  "seq_number": 214,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "payment_method": "bonifico",
                  "payment_due_at": "2026-08-15T00:00:00Z",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "sdi_status": "delivered",
                  "issued_at": "2026-07-16T10:30:00Z",
                  "created_at": "2026-07-16T10:12:00Z",
                  "updated_at": "2026-07-16T10:30:00Z",
                  "deleted_at": "2026-07-20T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year it was issued in).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        },
        {
          "name": "sezionale_id",
          "in": "query",
          "required": false,
          "description": "Series the number belongs to: a sezionale id, or `default` for\nthe series without a sezionale. Omitted: every series is\nsearched, and a number used in more than one returns 409\n`ambiguous_number`.\n",
          "x-description-it": "Serie del numero: l'id del sezionale, oppure `default` per la\nnumerazione senza sezionale. Senza, la ricerca copre tutte le\nserie e un numero usato in più serie restituisce 409\n`ambiguous_number`.\n",
          "schema": {
            "type": "string"
          },
          "example": "default"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera la fattura dal numero e dall'anno.",
            "en": "New: retrieve a invoice by its number and year."
          }
        ],
        "tags": [
          "Invoices"
        ],
        "summary": "Retrieve a invoice by number",
        "x-summary-it": "Recupera la fattura dal numero",
        "x-scope": "invoices.read",
        "x-description-it": "Restituisce la fattura con il numero `number` dell'anno `year` (anno di emissione): lo stesso numero mostrato nell'app, per esempio 12/2026. Le fatture, le note di credito e gli scontrini sono numerati per sezionale: indica `sezionale_id` (l'id del sezionale, oppure `default` per la numerazione senza sezionale) per cercare in una sola serie; senza, la ricerca copre tutte le serie.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the invoice carrying number `number` in year `year` (the year it was issued in) — the number shown in the app, e.g. 12/2026. Numbers repeat across sezionali: pass `sezionale_id` (a sezionale id, or `default` for the series without one) to search one series; without it every series is searched.\n\nThe response is identical to `GET /v1/invoices/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The invoice, exactly as retrieving it by id returns it.",
            "x-description-it": "La fattura, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "object": "invoice",
                  "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "work_category_name": "Tagliando",
                  "work_category_names": [
                    "Tagliando",
                    "Freni"
                  ],
                  "status": "issued",
                  "seq_number": 214,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "payment_method": "bonifico",
                  "payment_due_at": "2026-08-15T00:00:00Z",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "sdi_status": "delivered",
                  "issued_at": "2026-07-16T10:30:00Z",
                  "created_at": "2026-07-16T10:12:00Z",
                  "updated_at": "2026-07-16T10:30:00Z",
                  "line_items": [
                    {
                      "object": "invoice_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{id}/convert-to-scontrino": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Invoice id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Convert a draft fattura into a scontrino",
        "x-summary-it": "Converti in scontrino",
        "x-scope": "invoices.write",
        "x-description-it": "Trasforma la fattura in uno scontrino, mantenendo righe e totali.\n\nAttenzione: la risposta è lo **scontrino** creato, non la fattura —\nl'oggetto restituito ha `object: receipt`.\n",
        "description": "Swaps a non-emessa, worksheet-backed draft invoice for a draft\nscontrino on the same scheda lavoro — the same conversion the web\nperforms (POST /hx/private/invoices/{id}/convert-to-scontrino).\n\nPayments move onto the new receipt; the source invoice is\nsoft-deleted; `worksheets.fiscal_document_type` flips to scontrino.\nLine items are not copied (both documents project the worksheet).\n\nNo request body. Server re-validates every convertibility guard:\ndraft only, worksheet-backed, no blocking e-invoice (past draft),\nno linked nota di credito, no ritenuta d'acconto.\n\nReturns 422 when any guard fails. The invoice is gone after a\nsuccessful conversion — navigate to the returned receipt.\n",
        "responses": {
          "201": {
            "description": "The freshly created draft scontrino (full detail shape).",
            "x-description-it": "Lo scontrino generato dalla fattura.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Receipt"
                },
                "example": {
                  "object": "receipt",
                  "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                  "seq_number": 58,
                  "status": "draft",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "created_at": "2026-07-17T08:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{id}/issue": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Invoice id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Issue a draft invoice",
        "x-summary-it": "Emetti una fattura",
        "x-scope": "invoices.write",
        "x-description-it": "Porta la fattura da `draft` a `issued`, assegnandole il numero\nprogressivo del sezionale e valorizzando `issued_at`. È il punto in\ncui la fattura diventa un documento fiscale: da qui in poi non è più\neliminabile.\n\nL'emissione non trasmette allo SdI — la trasmissione non è esposta su\nquesta API.\n\nSe indicata, `issued_at` deve cadere tra oggi e 12 giorni fa\n(fattura immediata, giorni di calendario Europe/Rome), altrimenti\n`422` su `/issued_at`.\n",
        "description": "Transitions an invoice from draft to issued, stamping\n`issued_at` and assigning the next `seq_number` for the\ncompany atomically.\n\nThe body is optional. When `issued_at` is omitted the server\nuses its own clock. A client-supplied `issued_at` must fall\nwithin the fattura-immediata window — today back to 12 days\nago (Europe/Rome calendar days) — otherwise 422 on\n`/issued_at`. Back-dates that would violate the monotonic\nper-company sequence return 409.\nIssuing an already-issued invoice also returns 409 (issuance\nis not idempotent — seq_number would be wasted).\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceIssueRequest"
              },
              "example": {
                "issued_at": "2026-05-29T12:00:00Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice issued. `status` is now \"issued\".",
            "x-description-it": "La fattura emessa, con numero e data.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                },
                "example": {
                  "object": "invoice",
                  "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "work_category_name": "Tagliando",
                  "work_category_names": [
                    "Tagliando",
                    "Freni"
                  ],
                  "status": "issued",
                  "seq_number": 214,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "A",
                  "payment_method": "bonifico",
                  "payment_due_at": "2026-08-15T00:00:00Z",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "sdi_status": "delivered",
                  "issued_at": "2026-07-16T10:30:00Z",
                  "created_at": "2026-07-16T10:12:00Z",
                  "updated_at": "2026-07-16T10:30:00Z",
                  "line_items": [
                    {
                      "object": "invoice_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{id}/unissue": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Invoice id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Unissue the last issued invoice",
        "x-summary-it": "Annulla l'emissione di una fattura",
        "x-scope": "invoices.write",
        "x-description-it": "Riporta in `draft` una fattura emessa: azzera `seq_number` e\n`issued_at` e il numero torna libero, quindi la prossima fattura\nemessa nello stesso sezionale e anno lo riprende.\n\nPossibile solo per l'ultima fattura emessa del suo sezionale\nnel suo anno (anche un anno passato), senza nota di credito e senza fattura elettronica inviata\n(una bozza mai inviata viene eliminata insieme, i dati SDI\nrestano sul cliente); altrimenti `409`. I pagamenti registrati restano\ncollegati. Nessun corpo.\n",
        "description": "Turns an issued invoice back into a draft: `seq_number` and\n`issued_at` are cleared and the number goes back to the series,\nso the next invoice issued in the same sezionale and year gets it\nagain.\n\nAllowed only for the last issued invoice of its sezionale in its\nyear (past years included), with no credit note and no electronic invoice other than a\ndraft never sent to SdI, which is deleted with the issue (the\ncustomer's SDI data stays on the customer); otherwise 409. Recorded payments stay attached.\n`If-Match` is optional: when sent, an older version is 409.\nNo body.\n",
        "responses": {
          "200": {
            "description": "Invoice back to draft. `status` is now \"draft\".",
            "x-description-it": "La fattura tornata in bozza, senza numero né data.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invoice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{id}/payments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "List an invoice's payments",
        "x-summary-it": "Elenca i pagamenti della fattura",
        "x-scope": "invoices.read",
        "x-description-it": "Gli incassi registrati sulla fattura. Il residuo è\n`outstanding_payments` sulla fattura stessa.\n",
        "description": "Payments registered against the invoice (acconti/saldi), the\nsame surface as the worksheet-scoped endpoints without the\nworksheet indirection. Unpaginated.\n",
        "responses": {
          "200": {
            "description": "Payments, oldest first.",
            "x-description-it": "I pagamenti della fattura.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "payment",
                      "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                      "amount": 150.0,
                      "payment_method": "MP05",
                      "payment_date": "2026-07-16",
                      "created_at": "2026-07-16T10:30:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/invoices/4412e7b9-0c35-4a18-92d7-5b6ea1f38c04/payments"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Invoices"
        ],
        "summary": "Register a payment on an invoice",
        "x-summary-it": "Registra un pagamento",
        "x-scope": "invoices.write",
        "x-description-it": "Registra un incasso sulla fattura. Qui `payment_method` è un codice\nministeriale (`MP01` contanti, `MP05` bonifico, `MP08` carta), a\ndifferenza del campo omonimo sulla fattura che è un'etichetta libera.\nL'importo può essere parziale.\n",
        "description": "Mirrors the web invoice dialog: the payment attaches to the\ninvoice and the invoice paid-state is refreshed.\n`payment_method` falls back to the customer's preferred method,\nthen the company default, when missing or invalid. The optional\n`financial_account_id` (conto di saldo) must belong to the\ncompany.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentCreateRequest"
              },
              "example": {
                "amount": 150.0,
                "payment_method": "MP01",
                "payment_date": "2026-07-16"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment registered.",
            "x-description-it": "Il pagamento registrato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "object": "payment",
                  "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                  "amount": 150.0,
                  "payment_method": "MP05",
                  "payment_date": "2026-07-16",
                  "created_at": "2026-07-16T10:30:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/invoices/{id}/payments/{paymentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "paymentID",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "delete": {
        "tags": [
          "Invoices"
        ],
        "summary": "Remove a payment from an invoice",
        "x-summary-it": "Elimina un pagamento",
        "x-scope": "invoices.write",
        "x-description-it": "Rimuove l'incasso e riporta il residuo della fattura al valore\nprecedente.\n",
        "description": "Hard-deletes the payment (payments have no soft-delete) and\nrefreshes the invoice paid-state. The payment must belong to\nthe invoice — otherwise 404.\n",
        "responses": {
          "200": {
            "description": "The removed payment.",
            "x-description-it": "Il pagamento eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "object": "payment",
                  "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                  "amount": 150.0,
                  "payment_method": "MP05",
                  "payment_date": "2026-07-16",
                  "created_at": "2026-07-16T10:30:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/manufacturers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Vehicles"
        ],
        "summary": "List vehicle manufacturers",
        "x-summary-it": "Elenca le marche attive",
        "x-scope": "vehicles.read",
        "x-description-it": "Le marche selezionate dall'officina, da usare per popolare\n`manufacturer_id` in fase di creazione o modifica di un veicolo.\nL'elenco è breve e non paginato: `has_more` è sempre `false`.\n",
        "description": "Returns every vehicle manufacturer available to the\nauthenticated user's company — the global catalog plus any\ncompany-specific additions. Referenced by\n`Vehicle.manufacturer_id` on vehicle creation.\n\nIntentionally unpaginated: the set is small and changes\nrarely, so clients fetch once and filter locally in the\npicker UI. `has_more` is always `false`.\n",
        "responses": {
          "200": {
            "description": "Full manufacturer catalog.",
            "x-description-it": "Le marche attive dell'officina.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Manufacturer"
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "manufacturer",
                      "id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                      "name": "Fiat"
                    },
                    {
                      "object": "manufacturer",
                      "id": "9a4c2e70-6b18-4d3f-a5c9-1e8b7d0f2a46",
                      "name": "Volkswagen"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/manufacturers"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Replace manufacturer",
        "x-summary-it": "Imposta le marche attive",
        "x-scope": "settings.write",
        "x-description-it": "Sostituisce l'intero insieme delle marche attive dell'officina: le\nmarche non elencate vengono disattivate. `new_names` crea al volo le\nmarche non presenti a catalogo. Almeno una marca deve restare attiva.\nRichiede il permesso `settings.write`.\n",
        "operationId": "manufacturerSet",
        "description": "company's enabled brand set; new_names creates custom brands (web\n\"new-brand\" parity) and enables them in the same call.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "description": "The complete set of manufacturer IDs that stay active.\nAnything absent is deactivated. Must not be empty.\n",
                    "items": {
                      "type": "string"
                    }
                  },
                  "new_names": {
                    "type": "array",
                    "description": "Manufacturer names not in the catalog, created on the fly\nand added to the active set. 2-60 characters each.\n",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "ids": [
                  "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                  "9a4c2e70-6b18-4d3f-a5c9-1e8b7d0f2a46"
                ],
                "new_names": [
                  "Ligier"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Le marche ora attive.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "manufacturers",
                  "ids": [
                    "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                    "9a4c2e70-6b18-4d3f-a5c9-1e8b7d0f2a46"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/manufacturers/catalog": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Manufacturer catalog",
        "x-summary-it": "Catalogo completo delle marche",
        "x-scope": "settings.read",
        "x-description-it": "Tutte le marche disponibili, con `selected` a indicare quelle attive\nper l'officina e `vehicle_count` a indicare quanti veicoli le usano.\nÈ la vista dietro Impostazioni -\u003e Marche veicolo, quindi richiede il\npermesso `settings.read`.\n",
        "operationId": "manufacturerCatalog",
        "description": "brand catalog with the company's enabled marks and usage counts.\n",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il catalogo completo delle marche.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "manufacturer_catalog_entry",
                      "id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                      "name": "Fiat",
                      "selected": true,
                      "vehicle_count": 128
                    },
                    {
                      "object": "manufacturer_catalog_entry",
                      "id": "9a4c2e70-6b18-4d3f-a5c9-1e8b7d0f2a46",
                      "name": "Volkswagen",
                      "selected": true,
                      "vehicle_count": 41
                    },
                    {
                      "object": "manufacturer_catalog_entry",
                      "id": "2f6d8b31-7c05-4a92-9e4d-3b1a6c8f5d20",
                      "name": "Zastava",
                      "selected": false,
                      "vehicle_count": 0
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/manufacturers/catalog"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/orders": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "List orders",
        "x-summary-it": "Elenca gli ordini a fornitore",
        "x-scope": "suppliers.read",
        "x-description-it": "Gli ordini emessi verso i fornitori, in sola lettura: creazione e\nmodifica non sono esposte su questa API.\n",
        "operationId": "orderList",
        "description": "The date filter defaults to the last 12 months; the applied\nrange is echoed back in `date_filter`. Scoped to the authenticated\nuser's company and gated on the orders.read permission.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on the supplier name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Tab filter: `pending`, `delivery` or `received`. Omit for every status.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivery",
                "received"
              ]
            }
          },
          {
            "name": "supplier_id",
            "in": "query",
            "required": false,
            "description": "Restrict to one supplier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key (default `date`).",
            "schema": {
              "type": "string",
              "enum": [
                "date",
                "number",
                "supplier",
                "invoice",
                "total"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction (default `desc`).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `orders.created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `orders.created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a previous response's `next_starting_after`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "+ date_filter          success",
            "x-description-it": "Gli ordini a fornitore.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Order"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "order",
                      "id": "8c14e7a2-5b30-4d96-a17f-2e9c6b4d8f05",
                      "seq_number": 214,
                      "supplier_id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                      "supplier_name": "Ricambi Lombardia S.r.l.",
                      "supplier_invoice_reference": "2026/1184",
                      "status": "received",
                      "total_before_tax": 420.0,
                      "total_tax": 92.4,
                      "total_after_tax": 512.4,
                      "payment_method": "MP05",
                      "invoice_date": "2026-07-28",
                      "confirmed_at": "2026-07-20T09:00:00Z",
                      "received_at": "2026-07-27T11:30:00Z",
                      "created_at": "2026-07-18T15:00:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/orders",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/orders/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Order status counts",
        "x-summary-it": "Conta gli ordini per stato",
        "x-scope": "suppliers.read",
        "x-description-it": "Il numero di ordini in ciascuno stato, con gli stessi filtri\ndell'elenco.\n",
        "operationId": "orderStatusCounts",
        "description": "tab counters for the \"Arrivi Merce\" list. Counts respect the date\nrange and the free-text search (supplier narrowing is not applied to the\ncounts, web parity). The applied range is echoed in date_filter.\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il numero di ordini per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderStatusCounts"
                },
                "example": {
                  "object": "order_status_counts",
                  "pending": 2,
                  "delivery": 5,
                  "received": 118,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/price-lists": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "List price lists",
        "x-summary-it": "Elenca i listini",
        "x-scope": "settings.read",
        "x-description-it": "I listini configurati, applicabili ai clienti per ottenere prezzi\ndedicati.\n",
        "operationId": "priceListList",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I listini configurati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "price_list",
                      "id": "9c5e1a83-6b24-4d70-8f39-2a7c4e1b8d56",
                      "name": "Listino flotte"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/price-lists"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Settings"
        ],
        "summary": "Create a price list",
        "x-summary-it": "Crea un listino",
        "x-scope": "settings.write",
        "x-description-it": "Crea un listino prezzi.\n",
        "operationId": "priceListCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "name": "Nuovo nome"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "Il listino creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "price_list",
                  "id": "9c5e1a83-6b24-4d70-8f39-2a7c4e1b8d56",
                  "name": "Listino flotte"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/price-lists/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Settings"
        ],
        "summary": "Delete price list",
        "x-summary-it": "Elimina un listino",
        "x-scope": "settings.write",
        "x-description-it": "Elimina il listino. I clienti che lo usavano tornano ai prezzi di\nlistino base.\n",
        "operationId": "priceListDelete",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il listino eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "price_list",
                  "id": "9c5e1a83-6b24-4d70-8f39-2a7c4e1b8d56",
                  "name": "Listino flotte"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "tags": [
          "Settings"
        ],
        "summary": "Update price list",
        "x-summary-it": "Aggiorna un listino",
        "x-scope": "settings.write",
        "x-description-it": "Aggiorna il nome del listino.\n",
        "operationId": "priceListPatch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "name": "Nuovo nome"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il listino aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "price_list",
                  "id": "9c5e1a83-6b24-4d70-8f39-2a7c4e1b8d56",
                  "name": "Listino flotte"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-28",
            "it": "Nuovo stato `draft` (bozza): ogni preventivo nuovo nasce in bozza e passa a `waiting` quando lo si segna come inviato o quando parte un messaggio al cliente. Il filtro `status` accetta `draft`. Le revisioni precedenti vedono le bozze come `waiting`, anche nel filtro.",
            "en": "New `draft` status: every new quote starts as a draft and becomes `waiting` when marked as sent or when a message goes to the customer. The `status` filter accepts `draft`. Older revisions see drafts as `waiting`, in the filter too."
          },
          {
            "since": "2026-10-27",
            "it": "Nuovo campo `expires_at`: la data scadenza del preventivo (`YYYY-MM-DD`, `null` = nessuna scadenza).",
            "en": "New field `expires_at`: the quote's expiry date (`YYYY-MM-DD`, `null` = no expiry)."
          },
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "List quotes",
        "x-summary-it": "Elenca i preventivi",
        "x-scope": "quotes.read",
        "x-description-it": "I preventivi dell'officina, dal più recente. Filtra per stato con\n`status`, per cliente con `customer_id`, per veicolo con `vehicle_id`\ne per periodo con `from` / `to`. La paginazione usa un cursore opaco:\npassa `next_starting_after` invariato in `starting_after` e fermati\nquando `has_more` è `false`.\n",
        "description": "Returns quotes in the authenticated user's company, newest\nfirst. Filter by status with `?status=`, search by customer\nor title with `?q=`, and bound the result by `created_at`\nwith `?from=` / `?to=` / `?preset=`. Paginate with cursor-based\n`?limit=` + `?starting_after=`.\n\nThe date window defaults to the last 12 months; set it with\n`from`/`to`/`preset`. The applied range is echoed in\n`date_filter`.\n\nList rows omit `line_items` to keep the payload small —\nfetch the detail endpoint when you need the full body.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match against customer / title.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter to a single lifecycle state. `draft` from revision\n2026-10-28; before it, `waiting` also lists drafts and `draft` is\nrefused with 422.\n",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "waiting",
                "accepted",
                "rejected"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key (default `date` = creation date). Named keys are\nmapped server-side to whitelisted columns.\n",
            "schema": {
              "type": "string",
              "enum": [
                "date",
                "number",
                "customer",
                "vehicle",
                "total"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction (default `desc`).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "$ref": "#/components/parameters/WorkCategoryFilter"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response. Omit on the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of quotes (without line items).",
            "x-description-it": "Una pagina di preventivi.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "date_filter",
                        "work_category_filter"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Quote"
                          }
                        },
                        "date_filter": {
                          "$ref": "#/components/schemas/DateFilter"
                        },
                        "work_category_filter": {
                          "$ref": "#/components/schemas/WorkCategoryFilter"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "quote",
                      "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                      "seq_number": 87,
                      "status": "waiting",
                      "title": "Preventivo tagliando",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "work_category_id": null,
                      "work_category_name": null,
                      "work_category_ids": [],
                      "work_categories": [],
                      "km": 61240,
                      "expires_at": "2026-08-15",
                      "worksheet_id": null,
                      "worksheet_seq_number": null,
                      "total_before_tax": 70.0,
                      "total_tax": 15.4,
                      "total_after_tax": 85.4,
                      "total_discount": 0,
                      "hide_discounts": false,
                      "created_at": "2026-07-10T09:14:00Z",
                      "updated_at": "2026-07-10T09:20:00Z"
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJvIjoyMH0",
                  "url": "/v1/quotes",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "x-changes": [
          {
            "since": "2026-10-28",
            "it": "Il preventivo nasce in stato `draft` (bozza). Con una revisione precedente nasce `waiting`, come prima.",
            "en": "The quote starts as `draft`. With an older revision it starts as `waiting`, as before."
          },
          {
            "since": "2026-10-27",
            "it": "Accetta `expires_at` (data scadenza, `YYYY-MM-DD`), che non può precedere la data del preventivo: altrimenti 422 su `/expires_at`. La risposta include `expires_at`.",
            "en": "Accepts `expires_at` (expiry date, `YYYY-MM-DD`), which cannot precede the quote's date: otherwise 422 on `/expires_at`. The response includes `expires_at`."
          },
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids` per impostare più categorie (massimo 10); la risposta include `work_category_ids` e `work_categories`. `work_category_id` resta valido con il significato di sempre.",
            "en": "Accepts `work_category_ids` to set several work categories (at most 10); the response includes `work_category_ids` and `work_categories`. `work_category_id` still works with its usual meaning."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Create a quote",
        "x-summary-it": "Crea un preventivo",
        "x-scope": "quotes.write",
        "x-description-it": "Crea un preventivo in stato `draft` (bozza; `waiting` con revisioni precedenti alla 2026-10-28). Il numero progressivo\n(`seq_number`) è assegnato dal server. Le righe non si passano qui:\ncrea il preventivo, poi aggiungile con\n`POST /v1/quotes/{id}/line-items/batch`.\n",
        "description": "Creates a new quote owned by the authenticated user's\ncompany. Server-managed fields (`id`, `company_id`,\n`created_by`, `seq_number`, `status`, all timestamps,\ntotals) are filled in automatically. Status starts as\n`draft` (`waiting` for revisions before 2026-10-28). Line items\nare not accepted at creation time.\n\nForeign-key references in the body (`customer_id`, `vehicle_id`,\n`work_category_id`) must resolve to resources owned by the\nauthenticated user's company. Unreachable refs return 422\n`parameter_invalid` pointing at the offending field.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteCreateRequest"
              },
              "example": {
                "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                "title": "Preventivo tagliando",
                "km": 61240,
                "expires_at": "2026-08-15"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Quote created.",
            "x-description-it": "Il preventivo creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "waiting",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": null,
                  "work_category_name": null,
                  "work_category_ids": [],
                  "work_categories": [],
                  "km": 61240,
                  "expires_at": "2026-08-15",
                  "worksheet_id": null,
                  "worksheet_seq_number": null,
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "total_discount": 0,
                  "hide_discounts": false,
                  "created_at": "2026-07-10T09:14:00Z",
                  "updated_at": "2026-07-10T09:20:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-28",
            "it": "Nuovo contatore `draft`. Per le revisioni precedenti manca, e le bozze sono contate in `waiting`.",
            "en": "New `draft` counter. Older revisions do not get it, and drafts are counted in `waiting`."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Count quotes per status",
        "x-summary-it": "Conta i preventivi per stato",
        "x-scope": "quotes.read",
        "x-description-it": "Il numero di preventivi in ciascuno stato, con gli stessi filtri\ndell'elenco. Serve a popolare i contatori senza scaricare i\npreventivi.\n",
        "description": "Returns how many quotes sit in each status, for the tab counters\nabove the mobile list. Accepts the same filters as\n`GET /v1/quotes` minus `status` and pagination (`q`,\n`from`/`to`/`preset` with the same date-filter semantics), so\nthe numbers always match what the list shows.\nThe applied range is echoed in `date_filter`.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match against customer or vehicle name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "$ref": "#/components/parameters/WorkCategoryFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "Counts per status.",
            "x-description-it": "Il numero di preventivi per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuoteStatusCounts"
                },
                "example": {
                  "object": "quote_status_counts",
                  "waiting": 6,
                  "accepted": 23,
                  "rejected": 4,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Quote id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-28",
            "it": "`status` può valere `draft` (bozza). Le revisioni precedenti ricevono `waiting`.",
            "en": "`status` can be `draft`. Older revisions receive `waiting`."
          },
          {
            "since": "2026-10-27",
            "it": "Nuovo campo `expires_at`: la data scadenza del preventivo (`YYYY-MM-DD`, `null` = nessuna scadenza).",
            "en": "New field `expires_at`: the quote's expiry date (`YYYY-MM-DD`, `null` = no expiry)."
          },
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Retrieve a quote",
        "x-summary-it": "Recupera un preventivo",
        "x-scope": "quotes.read",
        "x-description-it": "Restituisce il preventivo con le righe (`line_items`) e i totali\ncalcolati. Se il preventivo è già stato convertito, `worksheet_id`\npunta alla scheda lavoro generata.\n",
        "description": "Returns a single quote (with embedded line items) by id,\nscoped to the authenticated user's company. Unknown ids,\nids from another company, and soft-deleted rows all return\n404 — never 403.\n",
        "responses": {
          "200": {
            "description": "Quote found. Includes `line_items`.",
            "x-description-it": "Il preventivo richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "waiting",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": null,
                  "work_category_name": null,
                  "work_category_ids": [],
                  "work_categories": [],
                  "km": 61240,
                  "expires_at": "2026-08-15",
                  "worksheet_id": null,
                  "worksheet_seq_number": null,
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "total_discount": 0,
                  "hide_discounts": false,
                  "created_at": "2026-07-10T09:14:00Z",
                  "updated_at": "2026-07-10T09:20:00Z",
                  "comments": "Valido 30 giorni.",
                  "line_items": [
                    {
                      "object": "quote_line_item",
                      "id": "2e6b8d40-7f19-4c53-a802-9d4e1b7c3f56",
                      "article_id": null,
                      "description": "Manodopera preventivata",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 35.0,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 70.0,
                      "total_tax": 15.4,
                      "total_after_tax": 85.4,
                      "position": 1,
                      "created_at": "2026-07-10T09:20:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-27",
            "it": "Accetta `expires_at` (data scadenza, `YYYY-MM-DD`; `null` la rimuove), che non può precedere la data del preventivo: altrimenti 422 su `/expires_at`.",
            "en": "Accepts `expires_at` (expiry date, `YYYY-MM-DD`; `null` clears it), which cannot precede the quote's date: otherwise 422 on `/expires_at`."
          },
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          },
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids`: sostituisce l'intero elenco delle categorie (`[]` o `null` li rimuove tutti). `work_category_id` continua a sostituire solo il primo dell'elenco. La risposta include `work_category_ids` e `work_categories`.",
            "en": "Accepts `work_category_ids`: replaces the whole work-category list (`[]` or `null` removes them all). `work_category_id` still replaces only the first in the list. The response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Update quote metadata",
        "x-summary-it": "Aggiorna un preventivo",
        "x-scope": "quotes.write",
        "x-description-it": "Aggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati, e `null` svuota il campo. Per cambiare stato usa\n`POST /v1/quotes/{id}/transitions`, non questo endpoint.\n",
        "description": "Three-state PATCH semantics (omitted = unchanged, `null` =\nclear, value = set). Status transitions go through\n`POST /v1/quotes/{id}/transitions`, not PATCH.\n\nFields not listed in `QuotePatchRequest` are intentionally\nNOT patchable: `id`, `company_id`, `created_by`,\n`seq_number`, `status`, `customer_id`, `vehicle_id`, all\ntotals, all timestamps, `worksheet_id`, `line_items`.\n\nForeign-key references in the body (`work_category_id`) must\nresolve to resources owned by the authenticated user's company.\nUnreachable refs return 422 `parameter_invalid` pointing at the\noffending field.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuotePatchRequest"
              },
              "example": {
                "title": "Preventivo aggiornato",
                "comments": null,
                "expires_at": "2026-08-31"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote updated. Returns the full post-update entity.",
            "x-description-it": "Il preventivo aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "waiting",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": null,
                  "work_category_name": null,
                  "work_category_ids": [],
                  "work_categories": [],
                  "km": 61240,
                  "expires_at": "2026-08-15",
                  "worksheet_id": null,
                  "worksheet_seq_number": null,
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "total_discount": 0,
                  "hide_discounts": false,
                  "created_at": "2026-07-10T09:14:00Z",
                  "updated_at": "2026-07-10T09:20:00Z",
                  "comments": "Valido 30 giorni.",
                  "line_items": [
                    {
                      "object": "quote_line_item",
                      "id": "2e6b8d40-7f19-4c53-a802-9d4e1b7c3f56",
                      "article_id": null,
                      "description": "Manodopera preventivata",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 35.0,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 70.0,
                      "total_tax": 15.4,
                      "total_after_tax": 85.4,
                      "position": 1,
                      "created_at": "2026-07-10T09:20:00Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Quotes"
        ],
        "summary": "Soft-delete a quote",
        "x-summary-it": "Elimina un preventivo",
        "x-scope": "quotes.write",
        "x-description-it": "Eliminazione logica: il preventivo sparisce dagli elenchi. La\nrisposta contiene il preventivo con `deleted_at` valorizzato.\n",
        "description": "Marks the quote as deleted by stamping `deleted_at` with the\nserver clock. Returns the deleted resource (200) for shape\nconsistency.\n",
        "responses": {
          "200": {
            "description": "Quote soft-deleted. `deleted_at` is populated.",
            "x-description-it": "Il preventivo eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "waiting",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": null,
                  "work_category_name": null,
                  "work_category_ids": [],
                  "work_categories": [],
                  "km": 61240,
                  "expires_at": "2026-08-15",
                  "worksheet_id": null,
                  "worksheet_seq_number": null,
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "total_discount": 0,
                  "hide_discounts": false,
                  "created_at": "2026-07-10T09:14:00Z",
                  "updated_at": "2026-07-10T09:20:00Z",
                  "deleted_at": "2026-07-20T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera il preventivo dal numero e dall'anno.",
            "en": "New: retrieve a quote by its number and year."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Retrieve a quote by number",
        "x-summary-it": "Recupera il preventivo dal numero",
        "x-scope": "quotes.read",
        "x-description-it": "Restituisce il preventivo con il numero `number` dell'anno `year` (anno della data del preventivo): lo stesso numero mostrato nell'app, per esempio 12/2026.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the quote carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026.\n\nThe response is identical to `GET /v1/quotes/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The quote, exactly as retrieving it by id returns it.",
            "x-description-it": "Il preventivo, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "waiting",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": null,
                  "work_category_name": null,
                  "work_category_ids": [],
                  "work_categories": [],
                  "km": 61240,
                  "expires_at": "2026-08-15",
                  "worksheet_id": null,
                  "worksheet_seq_number": null,
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "total_discount": 0,
                  "hide_discounts": false,
                  "created_at": "2026-07-10T09:14:00Z",
                  "updated_at": "2026-07-10T09:20:00Z",
                  "comments": "Valido 30 giorni.",
                  "line_items": [
                    {
                      "object": "quote_line_item",
                      "id": "2e6b8d40-7f19-4c53-a802-9d4e1b7c3f56",
                      "article_id": null,
                      "description": "Manodopera preventivata",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 35.0,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 70.0,
                      "total_tax": 15.4,
                      "total_after_tax": 85.4,
                      "position": 1,
                      "created_at": "2026-07-10T09:20:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{id}/convert": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Quote id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "x-changes": [
          {
            "since": "2026-10-28",
            "it": "Corpo facoltativo `{\"mark_accepted\": false}`: crea la scheda senza cambiare lo stato del preventivo. Senza corpo, o con `true`, il preventivo diventa `accepted` come prima.",
            "en": "Optional body `{\"mark_accepted\": false}`: creates the worksheet without changing the quote's status. With no body, or `true`, the quote becomes `accepted` as before."
          },
          {
            "since": "2026-10-03",
            "it": "Il documento creato eredita l'elenco completo delle categorie, in ordine; la risposta include `work_category_ids` e `work_categories`.",
            "en": "The created document inherits the full ordered work-category list; the response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Convert into a scheda lavoro",
        "x-summary-it": "Converti in scheda lavoro",
        "x-scope": "quotes.write",
        "x-description-it": "Genera una scheda lavoro dal preventivo, copiandone righe e totali,\ne collega i due documenti (`worksheet_id` sul preventivo).\n\nAttenzione: la risposta è la **scheda lavoro** creata, non il\npreventivo — l'oggetto restituito ha `object: worksheet`. La\nconversione è una sola volta: un preventivo già convertito risponde\n`409`.\n",
        "description": "Creates a worksheet carrying the quote's customer, vehicle, km,\ntitle, notes, line items and totals — the same conversion the web\nperforms — then links the quote to it and, unless the body says\n`{\"mark_accepted\": false}`, marks the quote `accepted` from whatever\nstatus it had. The quote is kept. A quote already linked to a\nworksheet returns 422; when the company requires km on\nvehicle-linked worksheets and the quote has none, 422\n`parameter_missing` `/km`.\n\nThe body is optional and read from revision 2026-10-28: without it\nthe quote becomes `accepted`, as before. Older revisions' bodies are\nignored.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteConvertRequest"
              },
              "example": {
                "mark_accepted": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The freshly created scheda lavoro (full detail shape).",
            "x-description-it": "La scheda lavoro generata dal preventivo.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "draft",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "frozen": false,
                  "created_at": "2026-07-11T08:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{id}/waste-disposal": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Quote id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Quotes"
        ],
        "summary": "Preview the waste-disposal charge",
        "x-summary-it": "Anteprima smaltimento rifiuti",
        "x-scope": "quotes.read",
        "x-changes": [
          {
            "since": "2026-10-06",
            "it": "Nuovo endpoint.",
            "en": "New endpoint."
          }
        ],
        "x-description-it": "Dice se lo smaltimento rifiuti si può applicare al preventivo e quanto\nvale (importo netto, prima dell'IVA), secondo le impostazioni\n`waste_disposal_*` dell'officina. `reason` spiega perché non è\napplicabile: `disabled`, `already_applied`, `nothing_to_charge`.\n",
        "description": "Reports whether the \"smaltimento rifiuti\" charge can be applied and\nits net amount, computed from the company's `waste_disposal_*`\nsettings: `rate`% of the net of the goods lines (`base: articles`)\nor of every line (`base: total`), rounded to cents.\n\n`reason` (null when applicable): `disabled` — off or no rate;\n`already_applied` — a line already carries the configured or the\ndefault description; `nothing_to_charge` — the amount rounds to 0.\n",
        "operationId": "quoteWasteDisposalPreview",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "L'anteprima dell'addebito.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WasteDisposalPreview"
                },
                "example": {
                  "object": "waste_disposal_preview",
                  "applicable": true,
                  "reason": null,
                  "rate": 1.2,
                  "base": "articles",
                  "description": "Smaltimento rifiuti",
                  "amount": 2.5
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Quotes"
        ],
        "summary": "Apply the waste-disposal charge",
        "x-summary-it": "Applica smaltimento rifiuti",
        "x-scope": "quotes.write",
        "x-changes": [
          {
            "since": "2026-10-06",
            "it": "Nuovo endpoint.",
            "en": "New endpoint."
          }
        ],
        "x-description-it": "Aggiunge la riga di smaltimento rifiuti (quantità 1, unità `lav`,\naliquota IVA predefinita dell'officina, in fondo al documento),\nricalcola i totali e restituisce il documento aggiornato. La riga\nsi aggiunge una sola volta: se non è applicabile la risposta è\n`409 waste_disposal_not_applicable` con il motivo in `error.reason`.\n",
        "description": "Appends the charge line — quantity 1 at the previewed amount, unit\n`lav`, the company's default VAT rate (its first enabled one), last\nposition — recomputes the totals and returns the updated document\n(as the line-items batch does).\n\nThe rules are re-checked server-side and the line is written in one\ntransaction holding the document's row lock, so concurrent calls run\none after the other and add the line once. When the charge can't be\napplied the response is `409 waste_disposal_not_applicable` with\n`error.reason` (`disabled`, `already_applied`, `nothing_to_charge`, and\non worksheets `fully_paid`).\n",
        "operationId": "quoteWasteDisposalApply",
        "responses": {
          "200": {
            "description": "Line added. Returns the updated document (with `line_items`).",
            "x-description-it": "Il documento con la riga aggiunta e i totali ricalcolati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`waste_disposal_not_applicable` (`error.reason` says why).\n",
            "x-description-it": "Non applicabile (motivo in `error.reason`) oppure scheda bloccata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "https://api.officina.it/errors/waste_disposal_not_applicable",
                    "status": 409,
                    "code": "waste_disposal_not_applicable",
                    "message": "Lo smaltimento rifiuti è già presente su questo documento.",
                    "reason": "already_applied",
                    "trace_id": "abc-123"
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{id}/line-items/batch": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Quote id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Mutate quote line items in a batch",
        "x-summary-it": "Modifica le righe in blocco",
        "x-scope": "quotes.write",
        "x-description-it": "Aggiunge, aggiorna, rimuove e riordina le righe in una sola\nrichiesta, applicata come singola transazione: se una operazione\nfallisce non viene applicata nessuna. I totali del preventivo vengono\nricalcolati e restituiti.\n",
        "description": "Adds, updates, removes, and reorders the quote's line items in a\nsingle atomic diff, then returns the full quote with recomputed\ntotals and the updated `line_items` array. The top-level quote\nPATCH is metadata-only; all line-item mutation flows through this\nendpoint. Identical wire shape to the worksheet batch endpoint.\n\n**Operations** (all arrays optional; empty body is a no-op):\n`add`, `update` (full replace by id), `remove` (by id),\n`reorder`. An id may appear in at most one of\n`remove`/`update`/`reorder`. As with worksheets, `update` is a\nremove+add internally so an updated line receives a NEW id;\n`tax_rate` is resolved server-side from `vat_code`.\n\nQuotes are estimates — there is no invoice-freeze rule and\narticle references on lines do not move inventory stock (they\nare validated for ownership only).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LineItemsBatchRequest"
              },
              "example": {
                "add": [
                  {
                    "description": "Estimated labour",
                    "quantity": 2,
                    "unit_type": "pz",
                    "unit_price": 35,
                    "unit_discount_rate": 0,
                    "vat_code": "22"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch applied. Returns the updated quote (with `line_items`).",
            "x-description-it": "Il preventivo con le righe aggiornate e i totali ricalcolati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "waiting",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": null,
                  "work_category_name": null,
                  "work_category_ids": [],
                  "work_categories": [],
                  "km": 61240,
                  "expires_at": "2026-08-15",
                  "worksheet_id": null,
                  "worksheet_seq_number": null,
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "total_discount": 0,
                  "hide_discounts": false,
                  "created_at": "2026-07-10T09:14:00Z",
                  "updated_at": "2026-07-10T09:20:00Z",
                  "comments": "Valido 30 giorni.",
                  "line_items": [
                    {
                      "object": "quote_line_item",
                      "id": "2e6b8d40-7f19-4c53-a802-9d4e1b7c3f56",
                      "article_id": null,
                      "description": "Manodopera preventivata",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 35.0,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 70.0,
                      "total_tax": 15.4,
                      "total_after_tax": 85.4,
                      "position": 1,
                      "created_at": "2026-07-10T09:20:00Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/quotes/{id}/transitions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Quote id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "x-changes": [
          {
            "since": "2026-10-28",
            "it": "Da `draft` si può andare a `waiting` (segna come inviato), `accepted` o `rejected`. Non si torna a `draft`.",
            "en": "From `draft` a quote can move to `waiting` (marks it as sent), `accepted` or `rejected`. Nothing moves it back to `draft`."
          }
        ],
        "tags": [
          "Quotes"
        ],
        "summary": "Apply a status transition",
        "x-summary-it": "Cambia stato al preventivo",
        "x-scope": "quotes.write",
        "x-description-it": "Porta il preventivo in `waiting` (segnato come inviato), `accepted` o `rejected`; una bozza può passare a ciascuno dei tre. È il modo\ncorretto di registrare l'esito: non impostare `status` via PATCH.\nAccettare un preventivo non crea da solo la scheda lavoro — per\nquello serve `POST /v1/quotes/{id}/convert`.\n",
        "description": "Drives the quote lifecycle: `draft` → `waiting` (marks the quote\nas sent) → `accepted` / `rejected`; a draft can also go straight to\n`accepted` / `rejected`. Nothing moves a quote back to `draft`. Any other `action` value returns 422.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteTransitionRequest"
              },
              "example": {
                "action": "accepted"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transition applied. Returns the post-transition entity.",
            "x-description-it": "Il preventivo dopo la transizione.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Quote"
                },
                "example": {
                  "object": "quote",
                  "id": "b93f7c25-4a18-4e60-9d37-8c1b5f2a6e04",
                  "seq_number": 87,
                  "status": "accepted",
                  "title": "Preventivo tagliando",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": null,
                  "work_category_name": null,
                  "work_category_ids": [],
                  "work_categories": [],
                  "km": 61240,
                  "expires_at": "2026-08-15",
                  "worksheet_id": null,
                  "worksheet_seq_number": null,
                  "total_before_tax": 70.0,
                  "total_tax": 15.4,
                  "total_after_tax": 85.4,
                  "total_discount": 0,
                  "hide_discounts": false,
                  "created_at": "2026-07-10T09:14:00Z",
                  "updated_at": "2026-07-10T09:20:00Z",
                  "comments": "Valido 30 giorni.",
                  "line_items": [
                    {
                      "object": "quote_line_item",
                      "id": "2e6b8d40-7f19-4c53-a802-9d4e1b7c3f56",
                      "article_id": null,
                      "description": "Manodopera preventivata",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 35.0,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 70.0,
                      "total_tax": 15.4,
                      "total_after_tax": 85.4,
                      "position": 1,
                      "created_at": "2026-07-10T09:20:00Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/receipts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-13",
            "it": "Paginazione: `limit` (predefinito 20, massimo 100) e `starting_after`; le voci sono ordinate dalla più recente (per data e id) e `next_starting_after` porta alla pagina successiva. Prima di questa revisione l'elenco torna intero.",
            "en": "Pagination: `limit` (default 20, max 100) and `starting_after`; entries come newest first (by date, then id) and `next_starting_after` leads to the next page. Before this revision the list comes back whole."
          }
        ],
        "tags": [
          "Receipts"
        ],
        "summary": "List receipts (date range, ?q= customer filter)",
        "x-summary-it": "Elenca gli scontrini",
        "x-scope": "receipts.read",
        "x-description-it": "Gli scontrini dell'officina, dal più recente. Filtra per cliente con\n`q` e per periodo con `from` / `to`.\n\nA differenza degli altri elenchi questo non è paginato: restituisce\ntutti gli scontrini del periodo e `has_more` è sempre `false`.\n",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size from revision 2026-10-13 (ignored before).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "The `next_starting_after` of the previous page (revision 2026-10-13).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sdi_status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato di trasmissione allo SdI.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Receipts + date_filter.",
            "x-description-it": "Una pagina di scontrini.",
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "receipt",
                      "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                      "type": "issued",
                      "status": "sale",
                      "seq_number": 58,
                      "title": "Tagliando 60.000 km",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                      "sezionale_code": "B",
                      "payment_method": "MP01",
                      "total_before_tax": 233.9,
                      "total_tax": 51.46,
                      "total_after_tax": 285.36,
                      "total_payments": 285.36,
                      "outstanding_payments": 0,
                      "electronic_receipts_enabled": false,
                      "e_receipt_status": null,
                      "issued_at": "2026-07-17T08:20:00Z",
                      "voided_at": null,
                      "created_at": "2026-07-17T08:00:00Z",
                      "updated_at": "2026-07-17T08:20:00Z"
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJvIjoyMH0",
                  "url": "/v1/receipts",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Receipts"
        ],
        "summary": "Create a receipt from a worksheet",
        "x-summary-it": "Crea uno scontrino",
        "x-scope": "receipts.write",
        "x-description-it": "Lo scontrino nasce sempre da una scheda lavoro: passa `worksheet_id`\ne righe, totali e anagrafiche vengono copiati da lì.\n\nUna scheda lavoro può generare un solo scontrino: se ne esiste già\nuno la richiesta risponde `409`.\n",
        "description": "Body {worksheet_id, payment_method}. 409 when the worksheet already has a receipt.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "worksheet_id",
                  "payment_method"
                ],
                "properties": {
                  "worksheet_id": {
                    "type": "string",
                    "description": "La scheda lavoro da cui generare lo scontrino."
                  },
                  "payment_method": {
                    "type": "string",
                    "description": "Codice ministeriale, es. `MP01` contanti."
                  }
                }
              },
              "example": {
                "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                "payment_method": "MP01"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Receipt (line items included).",
            "x-description-it": "Lo scontrino creato, in bozza.",
            "content": {
              "application/json": {
                "example": {
                  "object": "receipt",
                  "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                  "type": "draft",
                  "status": "sale",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "B",
                  "payment_method": "MP01",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 285.36,
                  "outstanding_payments": 0,
                  "electronic_receipts_enabled": false,
                  "e_receipt_status": null,
                  "voided_at": null,
                  "created_at": "2026-07-17T08:00:00Z",
                  "updated_at": "2026-07-17T08:20:00Z",
                  "seq_number": null,
                  "issued_at": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/receipts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Receipts"
        ],
        "summary": "Retrieve a receipt (line items included)",
        "x-summary-it": "Recupera uno scontrino",
        "x-scope": "receipts.read",
        "x-description-it": "Restituisce lo scontrino con le righe (`line_items`) e i totali. Il\ncampo `type` è derivato e vale `draft`, `issued` o `voided`.\n",
        "responses": {
          "200": {
            "description": "Receipt.",
            "x-description-it": "Lo scontrino richiesto.",
            "content": {
              "application/json": {
                "example": {
                  "object": "receipt",
                  "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                  "type": "issued",
                  "status": "sale",
                  "seq_number": 58,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "B",
                  "payment_method": "MP01",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 285.36,
                  "outstanding_payments": 0,
                  "electronic_receipts_enabled": false,
                  "e_receipt_status": null,
                  "issued_at": "2026-07-17T08:20:00Z",
                  "voided_at": null,
                  "created_at": "2026-07-17T08:00:00Z",
                  "updated_at": "2026-07-17T08:20:00Z",
                  "line_items": [
                    {
                      "object": "receipt_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_price": 74.0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-23",
            "it": "`version` nel dettaglio degli scontrini. Da questa revisione `PATCH /v1/receipts/{id}` richiede l'header `If-Match` con la `version` letta: senza risponde `428 precondition_required`, con una versione superata `409 conflict` e non salva nulla (`If-Match: *` scrive comunque). Le revisioni precedenti lo accettano facoltativo.",
            "en": "`version` on the receipt detail. From this revision `PATCH /v1/receipts/{id}` requires the `If-Match` header with the `version` you read: without it 428 `precondition_required`, with a superseded version 409 `conflict` and nothing written (`If-Match: *` writes anyway). Older revisions accept it as optional."
          }
        ],
        "tags": [
          "Receipts"
        ],
        "summary": "Update a draft receipt (title, comments, email, payment_method, sezionale_id)",
        "x-summary-it": "Aggiorna uno scontrino",
        "x-scope": "receipts.write",
        "x-description-it": "Modifica i campi descrittivi di uno scontrino ancora in bozza.\nAggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "internal_comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "payment_method": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "sezionale_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "title": "Scontrino tagliando",
                "payment_method": "MP08"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated receipt.",
            "x-description-it": "Lo scontrino aggiornato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "receipt",
                  "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                  "type": "draft",
                  "status": "sale",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "B",
                  "payment_method": "MP01",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 285.36,
                  "outstanding_payments": 0,
                  "electronic_receipts_enabled": false,
                  "e_receipt_status": null,
                  "voided_at": null,
                  "created_at": "2026-07-17T08:00:00Z",
                  "updated_at": "2026-07-17T08:20:00Z",
                  "seq_number": null,
                  "issued_at": null,
                  "line_items": [
                    {
                      "object": "receipt_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_price": 74.0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Receipts"
        ],
        "summary": "Delete a receipt (issued ones must be voided from the web first)",
        "x-summary-it": "Elimina uno scontrino",
        "x-scope": "receipts.write",
        "x-description-it": "Eliminazione logica, possibile finché lo scontrino è in bozza. Uno\nscontrino già emesso va prima annullato dal gestionale web:\nl'annullamento non è esposto su questa API.\n",
        "responses": {
          "200": {
            "description": "The removed receipt.",
            "x-description-it": "Lo scontrino eliminato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "receipt",
                  "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                  "type": "draft",
                  "status": "sale",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "B",
                  "payment_method": "MP01",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 285.36,
                  "outstanding_payments": 0,
                  "electronic_receipts_enabled": false,
                  "e_receipt_status": null,
                  "voided_at": null,
                  "created_at": "2026-07-17T08:00:00Z",
                  "updated_at": "2026-07-17T08:20:00Z",
                  "seq_number": null,
                  "issued_at": null,
                  "deleted_at": "2026-07-20T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/receipts/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year it was issued in).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        },
        {
          "name": "sezionale_id",
          "in": "query",
          "required": false,
          "description": "Series the number belongs to: a sezionale id, or `default` for\nthe series without a sezionale. Omitted: every series is\nsearched, and a number used in more than one returns 409\n`ambiguous_number`.\n",
          "x-description-it": "Serie del numero: l'id del sezionale, oppure `default` per la\nnumerazione senza sezionale. Senza, la ricerca copre tutte le\nserie e un numero usato in più serie restituisce 409\n`ambiguous_number`.\n",
          "schema": {
            "type": "string"
          },
          "example": "default"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera lo scontrino dal numero e dall'anno.",
            "en": "New: retrieve a receipt by its number and year."
          }
        ],
        "tags": [
          "Receipts"
        ],
        "summary": "Retrieve a receipt by number",
        "x-summary-it": "Recupera lo scontrino dal numero",
        "x-scope": "receipts.read",
        "x-description-it": "Restituisce lo scontrino con il numero `number` dell'anno `year` (anno di emissione): lo stesso numero mostrato nell'app, per esempio 12/2026. Le fatture, le note di credito e gli scontrini sono numerati per sezionale: indica `sezionale_id` (l'id del sezionale, oppure `default` per la numerazione senza sezionale) per cercare in una sola serie; senza, la ricerca copre tutte le serie.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the receipt carrying number `number` in year `year` (the year it was issued in) — the number shown in the app, e.g. 12/2026. Numbers repeat across sezionali: pass `sezionale_id` (a sezionale id, or `default` for the series without one) to search one series; without it every series is searched.\n\nThe response is identical to `GET /v1/receipts/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The receipt, exactly as retrieving it by id returns it.",
            "x-description-it": "Lo scontrino, con la stessa risposta del recupero per id.",
            "content": {
              "application/json": {
                "example": {
                  "object": "receipt",
                  "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                  "type": "issued",
                  "status": "sale",
                  "seq_number": 58,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "B",
                  "payment_method": "MP01",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 285.36,
                  "outstanding_payments": 0,
                  "electronic_receipts_enabled": false,
                  "e_receipt_status": null,
                  "issued_at": "2026-07-17T08:20:00Z",
                  "voided_at": null,
                  "created_at": "2026-07-17T08:00:00Z",
                  "updated_at": "2026-07-17T08:20:00Z",
                  "line_items": [
                    {
                      "object": "receipt_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_price": 74.0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/receipts/{id}/issue": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "post": {
        "tags": [
          "Receipts"
        ],
        "summary": "Issue (paper numbering; 409 with electronic receipts enabled)",
        "x-summary-it": "Emetti uno scontrino",
        "x-scope": "receipts.write",
        "x-description-it": "Assegna il numero progressivo e valorizza `issued_at`.\n\nVale solo per la numerazione cartacea: se l'officina ha i\ncorrispettivi elettronici attivi (`electronic_receipts_enabled`) la\nrichiesta risponde `409`, perché in quel caso l'emissione passa dal\nregistratore telematico e non da questa API.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "payment_method"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Se omesso vale il residuo da incassare."
                  },
                  "payment_method": {
                    "type": "string"
                  },
                  "payment_date": {
                    "type": "string",
                    "description": "Formato `YYYY-MM-DD`, fra 1900-01-01 e 2100-12-31."
                  },
                  "financial_account_id": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "amount": 150.0,
                "payment_method": "MP01",
                "payment_date": "2026-07-16"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Issued receipt.",
            "x-description-it": "Lo scontrino emesso, con numero e data.",
            "content": {
              "application/json": {
                "example": {
                  "object": "receipt",
                  "id": "3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19",
                  "type": "issued",
                  "status": "sale",
                  "seq_number": 58,
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "sezionale_id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "sezionale_code": "B",
                  "payment_method": "MP01",
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_payments": 285.36,
                  "outstanding_payments": 0,
                  "electronic_receipts_enabled": false,
                  "e_receipt_status": null,
                  "issued_at": "2026-07-17T08:20:00Z",
                  "voided_at": null,
                  "created_at": "2026-07-17T08:00:00Z",
                  "updated_at": "2026-07-17T08:20:00Z",
                  "line_items": [
                    {
                      "object": "receipt_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "vat_code": "22",
                      "tax_rate": 22,
                      "total_price": 74.0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/receipts/{id}/payments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Receipts"
        ],
        "summary": "List a receipt's payments",
        "x-summary-it": "Elenca i pagamenti dello scontrino",
        "x-scope": "receipts.read",
        "x-description-it": "Gli incassi registrati sullo scontrino. Il residuo è\n`outstanding_payments` sullo scontrino stesso.\n",
        "responses": {
          "200": {
            "description": "Payments, oldest first (unpaginated).",
            "x-description-it": "I pagamenti dello scontrino.",
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "payment",
                      "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                      "amount": 285.36,
                      "payment_method": "MP01",
                      "payment_date": "2026-07-17",
                      "created_at": "2026-07-17T08:20:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/receipts/3d7f1b95-6c28-4a04-9e51-8b2f7a3c6d19/payments"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Receipts"
        ],
        "summary": "Register a payment (PaymentCreateRequest; method falls back to the receipt's)",
        "x-summary-it": "Registra un pagamento",
        "x-scope": "receipts.write",
        "x-description-it": "Registra un incasso sullo scontrino. `payment_method` usa i codici\nministeriali (`MP01` contanti, `MP05` bonifico, `MP08` carta); se\nomesso vale quello dello scontrino. L'importo può essere parziale.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "payment_method"
                ],
                "properties": {
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Se omesso vale il residuo da incassare."
                  },
                  "payment_method": {
                    "type": "string"
                  },
                  "payment_date": {
                    "type": "string",
                    "description": "Formato `YYYY-MM-DD`, fra 1900-01-01 e 2100-12-31."
                  },
                  "financial_account_id": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "amount": 150.0,
                "payment_method": "MP01",
                "payment_date": "2026-07-16"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment.",
            "x-description-it": "Il pagamento registrato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "payment",
                  "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                  "amount": 285.36,
                  "payment_method": "MP01",
                  "payment_date": "2026-07-17",
                  "created_at": "2026-07-17T08:20:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/receipts/{id}/payments/{paymentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "paymentID",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "delete": {
        "tags": [
          "Receipts"
        ],
        "summary": "Remove a payment from a receipt",
        "x-summary-it": "Elimina un pagamento",
        "x-scope": "receipts.write",
        "x-description-it": "Rimuove l'incasso e riporta il residuo dello scontrino al valore\nprecedente.\n",
        "responses": {
          "200": {
            "description": "The removed payment.",
            "x-description-it": "Il pagamento eliminato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "payment",
                  "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                  "amount": 285.36,
                  "payment_method": "MP01",
                  "payment_date": "2026-07-17",
                  "created_at": "2026-07-17T08:20:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/reports/{widget}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "widget",
          "in": "path",
          "required": true,
          "description": "Card key. Officina: `officina-kpis`, `lavorato-chart`, `categories`,\n`quote-funnel`, `customers-chart`, `worksheet-status`,\n`top-services`, `calendar-heatmap`, `duration`,\n`worksheet-margin-chart`, `worksheet-margins`, `time-kpis`,\n`time-weeks`, `time-people`, `time-cats`, `time-check`. Economico:\n`economico-kpis`, `fatturato-chart`, `to-invoice`,\n`fatturato-sources`, `incassato-chart`, `payment-methods`,\n`sales-chart`, `receivables`, `outstanding-worksheets`,\n`expense-categories`, `composition`, `top-customers-fatturato`.\nUnknown keys → 404.\n",
          "schema": {
            "type": "string",
            "enum": [
              "officina-kpis",
              "lavorato-chart",
              "categories",
              "quote-funnel",
              "customers-chart",
              "worksheet-status",
              "top-services",
              "calendar-heatmap",
              "duration",
              "worksheet-margin-chart",
              "worksheet-margins",
              "time-kpis",
              "time-weeks",
              "time-people",
              "time-cats",
              "time-check",
              "economico-kpis",
              "fatturato-chart",
              "to-invoice",
              "fatturato-sources",
              "incassato-chart",
              "payment-methods",
              "sales-chart",
              "receivables",
              "outstanding-worksheets",
              "expense-categories",
              "composition",
              "top-customers-fatturato"
            ]
          }
        }
      ],
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "One card of the report (Officina, Economico)",
        "x-summary-it": "Recupera una card del report",
        "x-scope": "reports.read",
        "x-changes": [
          {
            "since": "2026-10-19",
            "it": "Nuovo endpoint: le card del report Officina ed Economico, con gli stessi dati della pagina Report del gestionale. Sostituisce `GET /v1/report/{widget}`.",
            "en": "New endpoint: the Officina and Economico report cards, with the same data as the web Report page. Replaces `GET /v1/report/{widget}`."
          }
        ],
        "x-description-it": "Restituisce i dati di una card del report, gli stessi che mostra la\npagina Report del gestionale (schede Officina ed Economico). Ogni card\nè una richiesta a sé.\n\nL'intervallo si sceglie con `preset` oppure `from` / `to` (senza\nparametri: ultimi 12 mesi); `grain` sceglie settimane o mesi nei\ngrafici (predefinito: settimane fino a 62 giorni, mesi oltre).\n\nUna card che per l'officina o il periodo non ha niente da mostrare\nrisponde con `visible: false` e `data: null`: va nascosta. Gli importi\nsono in euro, IVA esclusa salvo dove il campo dice altrimenti; un campo\n`null` indica un valore non significativo (nessun periodo precedente,\nsotto la soglia minima, permesso mancante).\n\nPermessi: le card con importi richiedono anche `prices.read`;\n`quote-funnel` richiede `quotes.read`, `top-customers-fatturato`\nanche `customers.read`, le card dei tempi `settings_company.read`.\nIn `receivables` i nomi dei debitori compaiono solo con\n`customers.read` (`debtors_shown`).\n",
        "description": "Returns one card of Report v2 — the data the web Report page renders\non its Officina and Economico tabs, computed by the same code. One\nrequest per card.\n\nPeriod: `preset` or `from`/`to` (no params = last 12 months; on the\nmobile app the sticky `report` filter shared with the web). `grain`\npicks the chart buckets (default weeks up to 62 days, months above).\n\nA card with nothing to show for the company or the period answers\n`visible: false` and `data: null` — hide it. Amounts are euro, IVA\nexcluded unless the field says otherwise; a `null` figure is not\nmeaningful (no previous period, below the minimum count, permission\nmissing). Percentages are 0–100.\n\nPermissions: cards with amounts also need `prices.read`;\n`quote-funnel` needs `quotes.read`, `top-customers-fatturato` also\n`customers.read`, the time cards `settings_company.read`; otherwise\n403. In `receivables` debtor names need `customers.read`\n(`debtors_shown`).\n\nThe cards kept from the first report (`worksheet-status`,\n`top-services`, `worksheet-margin-chart`, `worksheet-margins`,\n`sales-chart`, `outstanding-worksheets`) keep their\n`/v1/report/{widget}` data shapes.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Period start (default: 12 months ago).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Period end, inclusive (default: today).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "grain",
            "in": "query",
            "required": false,
            "description": "Chart buckets. Default: `week` up to 62 days, `month` above.",
            "schema": {
              "type": "string",
              "enum": [
                "week",
                "month"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "`top-services`, `outstanding-worksheets`, `worksheet-margins` — rows returned. Default 5, max 50.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "metric",
            "in": "query",
            "required": false,
            "description": "`worksheet-margins` only — ranking metric.",
            "schema": {
              "type": "string",
              "enum": [
                "eur",
                "pct"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "`worksheet-margins` only.",
            "schema": {
              "type": "string",
              "enum": [
                "best",
                "worst"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The card (visible or hidden).",
            "x-description-it": "La card richiesta, visibile o nascosta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportV2Envelope"
                },
                "example": {
                  "object": "report_widget",
                  "widget": "economico-kpis",
                  "tab": "economico",
                  "preset": "rel365",
                  "from": "2025-10-19",
                  "to": "2026-10-19",
                  "grain": "month",
                  "visible": true,
                  "data": {
                    "ever_issued": true,
                    "fatturato": {
                      "total": 48210.5,
                      "previous_total": 41890,
                      "delta_percent": 15.1,
                      "documents": 212
                    },
                    "incassato": {
                      "total": 51340.2,
                      "payments": 240,
                      "last_year_total": 47100,
                      "last_year_payments": 221,
                      "delta_percent": 9
                    },
                    "margine": {
                      "state": "shown",
                      "value": 19210.5,
                      "percent_of_fatturato": 39.8
                    },
                    "receivables": {
                      "total": 6420,
                      "invoices": 14,
                      "overdue": 2110
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/reports/fiscal": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "Fiscal profile and VAT periods",
        "x-summary-it": "Recupera il profilo fiscale e i periodi IVA",
        "x-scope": "reports.read",
        "x-changes": [
          {
            "since": "2026-10-19",
            "it": "Nuovo endpoint: profilo fiscale e periodi di liquidazione IVA del report Fiscale.",
            "en": "New endpoint: fiscal profile and VAT liquidation periods of the Fiscale report."
          }
        ],
        "x-description-it": "Il profilo fiscale che decide la scheda Fiscale del report: `state`\nvale `profile` (profilo incompleto), `forfettario` (niente IVA) o\n`full`. Solo con `full` arrivano i periodi di liquidazione (dal più\nrecente) e `default_period`, l'ultimo periodo chiuso, quello del\nprossimo F24. Richiede anche `prices.read`.\n",
        "description": "The fiscal profile that drives the Fiscale tab: `state` is `profile`\n(incomplete: show the form), `forfettario` (no VAT) or `full`. Only\nwith `full` come the liquidation periods (newest first) and\n`default_period` — the last completed period, the one the next F24\nis about. `regime_locked`: an e-invoice company's regime belongs to\nits e-invoice settings and cannot change here. Needs `prices.read`\ntoo.\n",
        "responses": {
          "200": {
            "description": "The profile.",
            "x-description-it": "Il profilo fiscale.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportFiscal"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/reports/fiscal/profile": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "put": {
        "tags": [
          "Reports"
        ],
        "summary": "Save the fiscal profile",
        "x-summary-it": "Salva il profilo fiscale",
        "x-scope": "reports.write",
        "x-changes": [
          {
            "since": "2026-10-19",
            "it": "Nuovo endpoint: salva regime fiscale, periodicità IVA e metodo dell'acconto.",
            "en": "New endpoint: saves the fiscal regime, VAT settlement period and advance method."
          }
        ],
        "x-description-it": "Salva il profilo con le stesse regole del gestionale e risponde come\n`GET /v1/reports/fiscal`. Un profilo incompleto non si salva: manca\n`fiscal_regime`, o `vat_settlement_period` per un regime diverso dal\nforfettario (`422 parameter_missing`). Se l'officina ha la\nfatturazione elettronica configurata, il regime resta quello delle\nsue impostazioni. Richiede anche `settings_company.read`.\n",
        "description": "Saves the profile with the web form's rules and answers like\n`GET /v1/reports/fiscal`. An incomplete profile is not saved: missing\n`fiscal_regime`, or `vat_settlement_period` for a regime other than\nforfettario → `422 parameter_missing`. Unknown values are dropped. An\ne-invoice company keeps the regime of its e-invoice settings whatever\nis sent. Needs `settings_company.read` too.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportFiscalProfileInput"
              },
              "example": {
                "fiscal_regime": "RF01",
                "vat_settlement_period": "quarterly",
                "vat_advance_method": "storico"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved; the profile and its periods.",
            "x-description-it": "Salvato: il profilo e i suoi periodi.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportFiscal"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/reports/fiscal/export.zip": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "CSV export for the commercialista",
        "x-summary-it": "Scarica l'export per il commercialista",
        "x-scope": "reports.read",
        "x-changes": [
          {
            "since": "2026-10-19",
            "it": "Nuovo endpoint: zip con i CSV del periodo IVA (documenti emessi, fatture ricevute, pagamenti).",
            "en": "New endpoint: zip of the VAT period's CSVs (issued documents, received invoices, payments)."
          }
        ],
        "x-description-it": "Lo stesso zip che scarica il gestionale: i CSV del periodo (separati\nda `;`, UTF-8 con BOM) con documenti emessi, fatture ricevute e\npagamenti. `files` sceglie quali (predefinito: tutti).\n",
        "description": "The zip the web page downloads: the period's CSVs (`;`-separated,\nUTF-8 with BOM) — documenti emessi, fatture ricevute, pagamenti.\n`files` picks them (default: all three).\n",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "A period key from `GET /v1/reports/fiscal` (`2026-Q3`, `2026-09`). Default: the last completed period.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "files",
            "in": "query",
            "required": false,
            "description": "Repeatable: `sales`, `purchases`, `payments`.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "sales",
                  "purchases",
                  "payments"
                ]
              }
            },
            "style": "form",
            "explode": true
          }
        ],
        "responses": {
          "200": {
            "description": "The zip.",
            "x-description-it": "Il file zip.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/reports/fiscal/{widget}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "widget",
          "in": "path",
          "required": true,
          "description": "Fiscale card: `kpis`, `iva`, `deadlines`, `history`, `rates`, `advance`, `export`. Unknown keys → 404.",
          "schema": {
            "type": "string",
            "enum": [
              "kpis",
              "iva",
              "deadlines",
              "history",
              "rates",
              "advance",
              "export"
            ]
          }
        }
      ],
      "get": {
        "tags": [
          "Reports"
        ],
        "summary": "One card of the Fiscale report",
        "x-summary-it": "Recupera una card del report Fiscale",
        "x-scope": "reports.read",
        "x-changes": [
          {
            "since": "2026-10-19",
            "it": "Nuovo endpoint: liquidazione IVA, aliquote, storico, acconto, scadenze ed export del periodo.",
            "en": "New endpoint: VAT liquidation, rates, history, advance, deadlines and export of the period."
          }
        ],
        "x-description-it": "Una card della scheda Fiscale per un periodo di liquidazione\n(`period`, da `GET /v1/reports/fiscal`). `kpis` e `iva` portano la\nstessa liquidazione (`VATSummary`). Nascosta (`visible: false`) finché\nil profilo non è completo e per il forfettario. Richiede anche\n`prices.read`.\n",
        "description": "One Fiscale card for a liquidation period (`period`, from\n`GET /v1/reports/fiscal`; default the last completed one). `kpis` and\n`iva` carry the same liquidation (`VATSummary`); `deadlines` does not\ndepend on the period. Hidden (`visible: false`) until the profile is\ncomplete, and for the forfettario. Needs `prices.read` too.\n",
        "parameters": [
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "A period key (`2026-Q3`, `2026-09`). Default: the last completed period.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The card.",
            "x-description-it": "La card richiesta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportFiscalWidgetEnvelope"
                },
                "example": {
                  "object": "report_fiscal_widget",
                  "widget": "kpis",
                  "period": {
                    "key": "2026-Q3",
                    "label": "3° trimestre 2026",
                    "from": "2026-07-01",
                    "to": "2026-09-30",
                    "current": false,
                    "due_date": "2026-11-16",
                    "due_code": "6033"
                  },
                  "visible": true,
                  "data": {
                    "debit": 4820.4,
                    "credit": 1210.1,
                    "balance": 3610.3,
                    "interest": 36.1,
                    "to_pay": 3646.4,
                    "status": "due",
                    "quarterly": true,
                    "sales_documents": 61,
                    "purchase_documents": 18,
                    "split_payment": 0,
                    "due_date": "2026-11-16",
                    "due_code": "6033",
                    "days_until_due": 28
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/report/{widget}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "widget",
          "in": "path",
          "required": true,
          "description": "Widget key. One route per data widget so clients fetch widgets\nindependently (progressive loading) and new widgets are purely\nadditive. Current keys: `kpis`, `sales-chart`,\n`expenses-chart`, `quote-conversion`, `worksheet-status`,\n`outstanding-worksheets`, `upcoming-appointments`,\n`top-customers`, `top-services`, `worksheet-margin-chart`,\n`worksheet-margins`. Unknown keys → 404.\n",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "deprecated": true,
        "tags": [
          "Reports"
        ],
        "summary": "One data widget of the Situazione report (deprecated)",
        "x-summary-it": "Recupera un widget di report",
        "x-scope": "reports.read",
        "x-description-it": "**Deprecato dalla revisione 2026-10-19**: usa `GET /v1/reports/{widget}`\n(Report v2). Dichiarando la revisione 2026-10-19 o successive risponde\n`410 endpoint_retired`.\n\nRestituisce i dati di un singolo widget delle statistiche officina.\nIl nome del widget va nel percorso; l'intervallo si sceglie con\n`preset` oppure con `from` / `to`.\n\nLa risposta è una busta uniforme: `widget` ripete il widget\nrichiesto, `preset`, `from` e `to` riportano l'intervallo\neffettivamente applicato e `data` contiene il risultato, la cui forma\ndipende dal widget.\n",
        "description": "**Deprecated since revision 2026-10-19** — use\n`GET /v1/reports/{widget}` (Report v2: the same cards and data as the\nweb Report page). Responses carry `Deprecation` and a\n`Link: \u003c/v1/reports/{widget}\u003e; rel=\"successor-version\"` header; a\nclient declaring `X-API-Version: 2026-10-19` or later gets\n`410 endpoint_retired`. The endpoint is removed when the minimum\nsupported revision passes 2026-10-19.\n\nReturns the data behind one widget of the mobile Situazione\nscreen, wrapped in a `report_widget` envelope that echoes the\napplied period. Backed by the same report services as the web\nreport page; requires the `reports.read` permission (403\notherwise).\n\nThe period uses the same date filter as the list endpoints: no\ndate params means the last 12 months, and an explicit\n`preset`/`from`/`to` applies to that request only.\n`preset: all` spans from the company's creation to today.\n\nChart widgets bucket by **day** for periods up to 31 days and by\n**month** otherwise — `x_axis` entries are always `YYYY-MM-DD`,\nso clients re-bucket freely (the mobile app aggregates daily\naxes into weeks).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Period start (default: 12 months ago).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Period end, inclusive (default: today).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "List widgets only — rows returned. Default 5, max 50 (the\nrepository cap; raise both to go beyond).\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "metric",
            "in": "query",
            "required": false,
            "description": "`worksheet-margins` only — ranking metric.",
            "schema": {
              "type": "string",
              "enum": [
                "eur",
                "pct"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "`worksheet-margins` only.",
            "schema": {
              "type": "string",
              "enum": [
                "best",
                "worst"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Widget data.",
            "x-description-it": "I dati del widget richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportWidgetEnvelope"
                },
                "example": {
                  "object": "report_widget",
                  "widget": "revenue_by_month",
                  "preset": "year",
                  "from": "2026-01-01",
                  "to": "2026-12-31",
                  "data": [
                    {
                      "label": "2026-06",
                      "value": 12480.5
                    },
                    {
                      "label": "2026-07",
                      "value": 15230.0
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/sezionali": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Invoices"
        ],
        "summary": "List invoice sezionali",
        "x-summary-it": "Elenca i sezionali",
        "x-scope": "settings.read",
        "x-description-it": "I registri di numerazione configurati dall'officina, da usare per\nvalorizzare `sezionale_id`. È una voce di Impostazioni, quindi\nrichiede il permesso `settings.read`.\n",
        "description": "The company's invoice numbering registers, behind the fattura\nedit form's Sezionale select. Unpaginated — the set is a\nhandful of rows. `has_more` is always `false`.\n",
        "responses": {
          "200": {
            "description": "Sezionali.",
            "x-description-it": "I sezionali configurati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Sezionale"
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "sezionale",
                      "id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                      "code": "A",
                      "label": "Sezionale principale"
                    },
                    {
                      "object": "sezionale",
                      "id": "5a9c2e71-4b83-40df-9126-7e3d1f6a8c25",
                      "code": "B",
                      "label": "Corrispettivi"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/sezionali"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Settings"
        ],
        "summary": "Create a sezionale",
        "x-summary-it": "Crea un sezionale",
        "x-scope": "settings.write",
        "x-description-it": "Crea un registro di numerazione. `code` è il suffisso che compare sul\nnumero del documento.\n",
        "operationId": "sezionaleCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "label"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "code": "A",
                "label": "Sezionale principale"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "Il sezionale creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "sezionale",
                  "id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "code": "A",
                  "label": "Sezionale principale"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/sezionali/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Settings"
        ],
        "summary": "Delete sezionale",
        "x-summary-it": "Elimina un sezionale",
        "x-scope": "settings.write",
        "x-description-it": "Elimina il sezionale. Non è eliminabile se è già stato usato per\nnumerare un documento.\n",
        "operationId": "sezionaleDelete",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il sezionale eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "sezionale",
                  "id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "tags": [
          "Settings"
        ],
        "summary": "Update sezionale",
        "x-summary-it": "Aggiorna un sezionale",
        "x-scope": "settings.write",
        "x-description-it": "Aggiorna codice ed etichetta del sezionale.\n",
        "operationId": "sezionalePatch",
        "requestBody": {
          "required": true,
          "description": "Il corpo è lo stesso della creazione: va inviato\nl'oggetto completo, non solo i campi da cambiare.\n",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code",
                  "label"
                ],
                "properties": {
                  "code": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "code": "A",
                "label": "Sezionale principale"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il sezionale aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "sezionale",
                  "id": "c81f4a26-7d09-4e35-b6a1-2f8c5d3e9047",
                  "code": "A",
                  "label": "Sezionale principale"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/suppliers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "List suppliers (?q= name substring, cursor pagination)",
        "x-summary-it": "Elenca i fornitori",
        "x-scope": "suppliers.read",
        "x-description-it": "I fornitori dell'officina. Filtra per testo con `q`.\n",
        "parameters": [
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Direzione di ordinamento: `asc` o `desc`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero di risultati per pagina.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Colonna di ordinamento.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Cursore opaco della pagina precedente. Va restituito invariato.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suppliers.",
            "x-description-it": "I fornitori dell'officina.",
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "supplier",
                      "id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                      "name": "Ricambi Lombardia S.r.l.",
                      "email": "ordini@ricambi.example.it",
                      "phone": "+390212345678",
                      "phone_national": "02 1234 5678",
                      "address": "Via Mecenate 12",
                      "city": "Milano",
                      "zip_code": "20138",
                      "tax_id": "01234567890",
                      "comments": null,
                      "bank_account_iban": "IT60X0542811101000000123456",
                      "website": "https://ricambi.example.it",
                      "website_display": "ricambi.example.it",
                      "supplier_type": "ricambi",
                      "created_at": "2025-02-11T10:00:00Z",
                      "updated_at": "2026-06-03T14:20:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/suppliers"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Create a supplier",
        "x-summary-it": "Crea un fornitore",
        "x-scope": "suppliers.write",
        "x-description-it": "Crea un fornitore. Il telefono va in formato E.164 (`+390212345678`)\ne il sito con lo schema completo (`https://...`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "In formato E.164, es. `+390212345678`."
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "zip_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "tax_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "bank_account_iban": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stored uppercase with no spaces. On writes it must pass the IBAN checksum (mod 97), else 422 on `/bank_account_iban`."
                  },
                  "website": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "URL completo, es. `https://ricambi.example.it`."
                  }
                }
              },
              "example": {
                "name": "Ricambi Lombardia S.r.l.",
                "email": "ordini@ricambi.example.it",
                "phone": "+390212345678",
                "city": "Milano",
                "zip_code": "20100",
                "tax_id": "01234567890",
                "website": "https://ricambi.example.it"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Supplier.",
            "x-description-it": "Il fornitore creato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "supplier",
                  "id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "name": "Ricambi Lombardia S.r.l.",
                  "email": "ordini@ricambi.example.it",
                  "phone": "+390212345678",
                  "phone_national": "02 1234 5678",
                  "address": "Via Mecenate 12",
                  "city": "Milano",
                  "zip_code": "20138",
                  "tax_id": "01234567890",
                  "comments": null,
                  "bank_account_iban": "IT60X0542811101000000123456",
                  "website": "https://ricambi.example.it",
                  "website_display": "ricambi.example.it",
                  "supplier_type": "ricambi",
                  "created_at": "2025-02-11T10:00:00Z",
                  "updated_at": "2026-06-03T14:20:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/suppliers/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Retrieve a supplier",
        "x-summary-it": "Recupera un fornitore",
        "x-scope": "suppliers.read",
        "x-description-it": "Restituisce il fornitore con i riepiloghi collegati: ordini,\narticoli forniti e spese.\n",
        "responses": {
          "200": {
            "description": "Supplier.",
            "x-description-it": "Il fornitore richiesto.",
            "content": {
              "application/json": {
                "example": {
                  "object": "supplier",
                  "id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "name": "Ricambi Lombardia S.r.l.",
                  "email": "ordini@ricambi.example.it",
                  "phone": "+390212345678",
                  "phone_national": "02 1234 5678",
                  "address": "Via Mecenate 12",
                  "city": "Milano",
                  "zip_code": "20138",
                  "tax_id": "01234567890",
                  "comments": null,
                  "bank_account_iban": "IT60X0542811101000000123456",
                  "website": "https://ricambi.example.it",
                  "website_display": "ricambi.example.it",
                  "supplier_type": "ricambi",
                  "created_at": "2025-02-11T10:00:00Z",
                  "updated_at": "2026-06-03T14:20:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "Suppliers"
        ],
        "summary": "Update a supplier",
        "x-summary-it": "Aggiorna un fornitore",
        "x-scope": "suppliers.write",
        "x-description-it": "Aggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "In formato E.164, es. `+390212345678`."
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "zip_code": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "tax_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "bank_account_iban": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Stored uppercase with no spaces. On writes it must pass the IBAN checksum (mod 97), else 422 on `/bank_account_iban`."
                  },
                  "website": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "URL completo, es. `https://ricambi.example.it`."
                  }
                }
              },
              "example": {
                "name": "Ricambi Lombardia S.r.l.",
                "email": "ordini@ricambi.example.it",
                "phone": "+390212345678",
                "city": "Milano",
                "zip_code": "20100",
                "tax_id": "01234567890",
                "website": "https://ricambi.example.it"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated supplier.",
            "x-description-it": "Il fornitore aggiornato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "supplier",
                  "id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "name": "Ricambi Lombardia S.r.l.",
                  "email": "ordini@ricambi.example.it",
                  "phone": "+390212345678",
                  "phone_national": "02 1234 5678",
                  "address": "Via Mecenate 12",
                  "city": "Milano",
                  "zip_code": "20138",
                  "tax_id": "01234567890",
                  "comments": null,
                  "bank_account_iban": "IT60X0542811101000000123456",
                  "website": "https://ricambi.example.it",
                  "website_display": "ricambi.example.it",
                  "supplier_type": "ricambi",
                  "created_at": "2025-02-11T10:00:00Z",
                  "updated_at": "2026-06-03T14:20:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Suppliers"
        ],
        "summary": "Delete a supplier",
        "x-summary-it": "Elimina un fornitore",
        "x-scope": "suppliers.write",
        "x-description-it": "Eliminazione logica del fornitore. Ordini e spese già registrati\nrestano intatti.\n",
        "responses": {
          "200": {
            "description": "The removed supplier.",
            "x-description-it": "Il fornitore eliminato.",
            "content": {
              "application/json": {
                "example": {
                  "object": "supplier",
                  "id": "6f2b8c14-9d70-4a35-b8e2-1c5f7a3d9e60",
                  "name": "Ricambi Lombardia S.r.l.",
                  "email": "ordini@ricambi.example.it",
                  "phone": "+390212345678",
                  "phone_national": "02 1234 5678",
                  "address": "Via Mecenate 12",
                  "city": "Milano",
                  "zip_code": "20138",
                  "tax_id": "01234567890",
                  "comments": null,
                  "bank_account_iban": "IT60X0542811101000000123456",
                  "website": "https://ricambi.example.it",
                  "website_display": "ricambi.example.it",
                  "supplier_type": "ricambi",
                  "created_at": "2025-02-11T10:00:00Z",
                  "updated_at": "2026-06-03T14:20:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/time-types": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-25",
            "it": "Nuovo endpoint: i tipi di tempo del Marcatempo dell'officina.",
            "en": "New endpoint: the workshop's Marcatempo time types."
          }
        ],
        "tags": [
          "TimeTracking"
        ],
        "summary": "List time types",
        "x-summary-it": "Elenca i tipi di tempo",
        "x-scope": "worksheets.read",
        "operationId": "timeTypeList",
        "x-description-it": "I tipi di tempo del Marcatempo, nell'ordine di Impostazioni → Tempi.\nUn'officina che non ha mai usato il Marcatempo non ha ancora tipi:\nl'elenco è vuoto (una lettura via API non crea nulla; il primo tipo,\n`Lavorazione`, nasce quando l'officina apre il Marcatempo).\n\nUn tipo di lavoro (`is_wait: false`) è il timer di una persona e\nconta nelle ore lavorate. Un tipo di attesa (`is_wait: true`) è il\ntempo della scheda senza una persona, per esempio l'auto ferma in\nattesa dei ricambi: si registra ma non conta mai come lavorato.\n\nServe il modulo Marcatempo attivo in Impostazioni → Moduli: se è\nspento la risposta è `403 forbidden`. Elenco completo in una sola\npagina (`has_more` è sempre `false`).\n",
        "description": "The workshop's Marcatempo time types, in the order set in\nImpostazioni → Tempi. A workshop that never used the Marcatempo has\nno types yet and lists none: an API read writes nothing (the first\ntype, `Lavorazione`, is created when the workshop opens the\nMarcatempo).\n\nA Lavoro type (`is_wait: false`) is a person's own timer and counts\nas worked time. An Attesa type (`is_wait: true`) is the worksheet's\ntime with no person (the car waiting for parts): it is recorded but\nnever counted as worked.\n\nNeeds the Marcatempo module on (Impostazioni → Moduli), else\n`403 forbidden`. The whole list in one page (`has_more` is always\n`false`).\n",
        "responses": {
          "200": {
            "description": "The time types, in display order.",
            "x-description-it": "I tipi di tempo, in ordine.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/TimeType"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "has_more": false,
                  "url": "/v1/time-types",
                  "data": [
                    {
                      "object": "time_type",
                      "id": "3b8e2f71-6c4a-4d19-9e05-7a1c2d3e4f60",
                      "name": "Lavorazione",
                      "is_wait": false,
                      "position": 0
                    },
                    {
                      "object": "time_type",
                      "id": "5d0a9c32-1e7b-4f86-b3a4-8c2e6f1d9b07",
                      "name": "Diagnosi",
                      "is_wait": false,
                      "position": 1
                    },
                    {
                      "object": "time_type",
                      "id": "7f4c1b98-2d6e-4a35-9c81-0e5b3a7d2f14",
                      "name": "Attesa ricambi",
                      "is_wait": true,
                      "position": 2
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/TimeTrackingForbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/time-entries": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-25",
            "it": "Nuovo endpoint: i tempi registrati col Marcatempo in un periodo, con filtri per scheda e collaboratore.",
            "en": "New endpoint: the times recorded with the Marcatempo over a period, filtered by worksheet and person."
          }
        ],
        "tags": [
          "TimeTracking"
        ],
        "summary": "List time entries",
        "x-summary-it": "Elenca i tempi registrati",
        "x-scope": "worksheets.read",
        "operationId": "timeEntryList",
        "x-description-it": "I tempi registrati col Marcatempo su tutte le schede lavoro, dal più\nrecente per ora di inizio: timer accesi e fermati e tempi inseriti a\nmano, di tutti i collaboratori. Serve per portare le ore lavorate in\nun foglio presenze, in un gestionale paghe o in un'analisi della resa.\n\n`from` e `to` sono giorni (`AAAA-MM-GG`, ora di Roma), entrambi\ninclusi, e filtrano sull'ora di inizio; `to` prima di `from` risponde\n`422` con `param: /to`. `worksheet_id` limita a una scheda, `user_id`\na un collaboratore. I tempi delle schede eliminate non compaiono.\n\nUn timer acceso ha `running: true`, `ended_at: null` e la durata\ncontata fino al momento della risposta. I tempi di attesa\n(`user: null`, tipo con `is_wait: true`) non sono ore lavorate.\nServe il modulo Marcatempo attivo (`403` altrimenti).\n",
        "description": "The times recorded with the Marcatempo across every worksheet,\nnewest start first: running and stopped timers and times typed by\nhand, of every person. Use it to feed a timesheet, a payroll tool or\na resa analysis.\n\n`from` and `to` are days (`YYYY-MM-DD`, Rome time), both inclusive,\nfiltering on the start; `to` before `from` answers `422` with\n`param: /to`. `worksheet_id` narrows to one worksheet, `user_id` to\none person. Times of deleted worksheets are left out.\n\nA running timer has `running: true`, `ended_at: null` and its\nduration counted up to the response time. Attesa times (`user:\nnull`, a type with `is_wait: true`) are not worked time. Needs the\nMarcatempo module on (`403` otherwise). Pages are keyset cursors on\n`(started_at, id)`.\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "First day, inclusive (`YYYY-MM-DD`, Rome), on the start time.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Last day, inclusive (`YYYY-MM-DD`, Rome), on the start time. Before `from` → 422.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "worksheet_id",
            "in": "query",
            "required": false,
            "description": "Only the times of this worksheet.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "user_id",
            "in": "query",
            "required": false,
            "description": "Only the times of this person.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "The `next_starting_after` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of times, newest start first.",
            "x-description-it": "Una pagina di tempi, dal più recente.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/TimeEntry"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "time_entry",
                      "id": "4e9a0c17-3b5d-4a82-9f6e-2c1b7d8e5a93",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "time_type_id": "7f4c1b98-2d6e-4a35-9c81-0e5b3a7d2f14",
                      "user": null,
                      "started_at": "2026-10-23T08:30:00Z",
                      "ended_at": "2026-10-23T09:30:00Z",
                      "duration_seconds": 3600,
                      "running": false,
                      "note": "Pastiglie in arrivo dal fornitore",
                      "source": "manual",
                      "forgotten": false,
                      "suggested_stop_at": null,
                      "can_edit": true,
                      "can_stop": false,
                      "can_discard": false
                    },
                    {
                      "object": "time_entry",
                      "id": "1f6b3d82-7a9c-4e05-b1d4-6e8a2c9f0b37",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "time_type_id": "3b8e2f71-6c4a-4d19-9e05-7a1c2d3e4f60",
                      "user": {
                        "object": "time_person",
                        "id": "2f3e4d5c-6b7a-4980-8f1e-2d3c4b5a6978",
                        "first_name": "Marco",
                        "last_name": "Esposito"
                      },
                      "started_at": "2026-10-23T07:00:00Z",
                      "ended_at": "2026-10-23T08:30:00Z",
                      "duration_seconds": 5400,
                      "running": false,
                      "note": "Smontaggio freni anteriori",
                      "source": "timer",
                      "forgotten": false,
                      "suggested_stop_at": null,
                      "can_edit": true,
                      "can_stop": false,
                      "can_discard": false
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJ0IjoiMjAyNi0xMC0yM1QwNzowMDowMFoiLCJpIjoiMWY2YjNkODItN2E5Yy00ZTA1LWIxZDQtNmU4YTJjOWYwYjM3In0",
                  "url": "/v1/time-entries"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/TimeTrackingForbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/trash": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "Nuovo endpoint: i documenti e i record eliminati dell'officina, con chi li ha eliminati e quando.",
            "en": "New endpoint: the workshop's deleted documents and records, with who deleted them and when."
          }
        ],
        "tags": [
          "Cestino"
        ],
        "summary": "List deleted records (the Cestino)",
        "x-summary-it": "Elenca i documenti e i record eliminati",
        "x-scope": "trash.read",
        "operationId": "trashList",
        "x-description-it": "Il Cestino di Impostazioni: ogni documento o record eliminato\ndell'officina (fatture, scontrini, note di credito, schede lavoro,\npreventivi, clienti, veicoli, articoli, appuntamenti, accettazioni,\nspese, ordini, fornitori, depositi gomme, DDT, vendite e noleggi),\ndal più recente, normalizzato alle stesse colonne: tipo, numero o\nnome, cliente, importo, data di eliminazione e chi l'ha eliminato\n(dal registro attività; `null` per le eliminazioni più vecchie).\nNiente viene mai eliminato definitivamente e nessun numero viene\nriutilizzato: un documento fiscale numerato eliminato riporta\n`number_unused: true`.\n\n`type` sceglie una scheda (`all`, `invoice`, `receipt`, `worksheet`,\n`quote`, `customer`, `vehicle`, `article`, `other`), `q` cerca per\nnumero, nome, targa o codice, `period` sceglie la finestra (`c30`,\n`c60`, `c180`, `c365`, `all`). Ogni pagina riporta `counts` per\nscheda, il `period` applicato e `periods` con il flag `allowed`.\n\nLo storico dipende dal piano (`window_days`): 30 giorni con Base,\n180 con Pro, tutto con Business e Illimitato. Senza `period` si\napplica la finestra più ampia del piano; un `period` oltre il piano\nrisponde `402 plan_upgrade_required` con `param: /period`. Serve un\nabbonamento attivo (`402 subscription_required` altrimenti). Sola\nlettura: il ripristino non è disponibile via API e i dettagli di un\nrecord eliminato rispondono `404` su ogni endpoint.\n",
        "description": "The Impostazioni → Cestino page as data: every soft-deleted\ndocument or record of the workshop (invoices, receipts, credit\nnotes, worksheets, quotes, customers, vehicles, articles, calendar\nentries, check-ins, expenses, orders, suppliers, tyre sets, delivery\nnotes, vehicle sales and rentals), newest deletion first, normalised\nto one row shape: type, number or label, customer, amount, deletion\ntime and the user who deleted it (from the events log; `null` for\nolder deletions). Nothing is ever purged and no number is reused:\na numbered fiscal document that was deleted reports\n`number_unused: true`.\n\n`type` picks a tab (`all`, `invoice`, `receipt`, `worksheet`,\n`quote`, `customer`, `vehicle`, `article`, `other`), `q` searches\nnumber, name, plate or code, `period` picks the window (`c30`,\n`c60`, `c180`, `c365`, `all`). Every page carries the per-tab\n`counts`, the applied `period` and `periods` with their `allowed`\nflag, so a client can render the control without knowing the plan.\n\nThe history window depends on the plan (`window_days`): 30 days on\nBase, 180 on Pro, everything on Business and Illimitato. Without\n`period` the plan's widest window applies; a `period` beyond the plan\nanswers `402 plan_upgrade_required` with `param: /period`. An active\nsubscription is required (`402 subscription_required` otherwise).\nRead-only: restore is not on the API, and a deleted record's detail\nanswers `404` on every endpoint. Pages are keyset cursors on\n`(deleted_at, id)`.\n",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Tab to list. Unknown values are rejected with 422.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "invoice",
                "receipt",
                "worksheet",
                "quote",
                "customer",
                "vehicle",
                "article",
                "other"
              ],
              "default": "all"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring of the number, name, plate, title or code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": false,
            "description": "History window. Must be allowed by the plan (`periods[].allowed`),\nelse `402 plan_upgrade_required`. Omitted: the plan's widest window.\n",
            "schema": {
              "type": "string",
              "enum": [
                "c30",
                "c60",
                "c180",
                "c365",
                "all"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "The `next_starting_after` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of deleted records, newest deletion first, plus counts and the period control.",
            "x-description-it": "Una pagina di record eliminati, dal più recente, con i conteggi e i periodi.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "period",
                        "periods",
                        "window_days",
                        "counts"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/DeletedRecord"
                          }
                        },
                        "period": {
                          "$ref": "#/components/schemas/TrashPeriod"
                        },
                        "periods": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/TrashPeriodOption"
                          }
                        },
                        "window_days": {
                          "type": "integer",
                          "description": "The plan's history window in days, -1 = unlimited."
                        },
                        "counts": {
                          "$ref": "#/components/schemas/TrashCounts"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "deleted_record",
                      "type": "invoice",
                      "id": "2f6c1a84-93d7-4e1b-b0a2-7c58d9e3f614",
                      "seq_number": 816,
                      "label": "Tagliando 60.000 km",
                      "meta": "",
                      "customer_name": "Mario Rossi",
                      "amount": 285.36,
                      "issued": true,
                      "number_unused": true,
                      "numbered_at": "2026-09-30T10:12:00Z",
                      "deleted_at": "2026-10-03T16:40:12Z",
                      "deleted_by": "Luca Bianchi"
                    },
                    {
                      "object": "deleted_record",
                      "type": "customer",
                      "id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "seq_number": null,
                      "label": "Rossi Autotrasporti Srl",
                      "meta": "Azienda",
                      "customer_name": "",
                      "amount": null,
                      "issued": null,
                      "number_unused": false,
                      "numbered_at": "2026-02-11T09:00:00Z",
                      "deleted_at": "2026-10-01T08:05:44Z",
                      "deleted_by": null
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/trash",
                  "period": {
                    "object": "trash_period",
                    "key": "c30",
                    "label": "Ultimi 30 giorni",
                    "days": 30,
                    "from": "2026-09-22",
                    "to": "2026-10-22"
                  },
                  "periods": [
                    {
                      "object": "trash_period_option",
                      "key": "c30",
                      "label": "Ultimi 30 giorni",
                      "days": 30,
                      "allowed": true
                    },
                    {
                      "object": "trash_period_option",
                      "key": "c60",
                      "label": "Ultimi 60 giorni",
                      "days": 60,
                      "allowed": false
                    },
                    {
                      "object": "trash_period_option",
                      "key": "c180",
                      "label": "Ultimi 180 giorni",
                      "days": 180,
                      "allowed": false
                    },
                    {
                      "object": "trash_period_option",
                      "key": "c365",
                      "label": "Ultimo anno",
                      "days": 365,
                      "allowed": false
                    },
                    {
                      "object": "trash_period_option",
                      "key": "all",
                      "label": "Tutto lo storico",
                      "days": -1,
                      "allowed": false
                    }
                  ],
                  "window_days": 30,
                  "counts": {
                    "object": "trash_counts",
                    "all": 2,
                    "invoice": 1,
                    "receipt": 0,
                    "worksheet": 0,
                    "quote": 0,
                    "customer": 1,
                    "vehicle": 0,
                    "article": 0,
                    "other": 0
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/team": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "List teams",
        "x-summary-it": "Elenca i collaboratori",
        "x-scope": "settings.read",
        "x-description-it": "I collaboratori dell'officina, con ruolo e stato di attivazione.\n",
        "operationId": "teamList",
        "description": "(read-only; invites and permissions stay web-side).\n",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I collaboratori dell'officina.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "team_member",
                      "id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                      "first_name": "Luca",
                      "last_name": "Bianchi",
                      "email": "luca.bianchi@example.it",
                      "phone": "+393337654321",
                      "role": "meccanico",
                      "deactivated": false,
                      "is_self": false
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/team"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/team/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Retrieve a team",
        "x-summary-it": "Recupera un collaboratore",
        "x-scope": "settings.read",
        "x-description-it": "Restituisce il collaboratore con i gruppi di permessi assegnati.\n",
        "operationId": "teamGet",
        "description": "(read-only): identity, stato, ruolo and the functional-area permissions.\nDeactivated members are included so their card still opens.\n",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il collaboratore richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "team_member",
                  "id": "4e7b1d29-5a63-40c8-9f15-2b8d6e3a1c74",
                  "first_name": "Luca",
                  "last_name": "Bianchi",
                  "email": "luca.bianchi@example.it",
                  "phone": "+393337654321",
                  "role": "meccanico",
                  "deactivated": false,
                  "is_self": false,
                  "permission_groups": [
                    {
                      "name": "Officina",
                      "granted": true
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/tyresets": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "TyreSets"
        ],
        "summary": "List tyre sets",
        "x-summary-it": "Elenca i treni in deposito",
        "x-scope": "articles.read",
        "x-description-it": "I treni di gomme custoditi per i clienti. Filtra per stato con\n`status` e per testo con `q`.\n\nIl deposito gomme rientra nel permesso `articles`, non ne ha uno\nproprio: una chiave con `articles.read` vede sia il magazzino sia il\ndeposito.\n",
        "operationId": "tyreSetList",
        "description": "?status, ?customer_id, cursor pagination).\n",
        "parameters": [
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "description": "Filtra per cliente.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero di risultati per pagina.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "location",
            "in": "query",
            "required": false,
            "description": "Filtra per posizione di deposito.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rim_type",
            "in": "query",
            "required": false,
            "description": "Filtra per tipo di cerchio.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Cursore opaco della pagina precedente. Va restituito invariato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I treni in deposito.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "tyre_set",
                      "id": "47e2c9b1-8d50-4f36-a127-6c3b9e5d2a84",
                      "seq_number": 63,
                      "status": "deposited",
                      "description": "Michelin Primacy 195/65 R15",
                      "quantity": 4,
                      "rim_type": "lega",
                      "season_type": "estive",
                      "condition": "buono",
                      "residual_mm": 6,
                      "location": "Scaffale B3",
                      "comments": null,
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "deposited_at": "2026-04-10T09:00:00Z",
                      "mounted_at": null,
                      "collected_at": null,
                      "disposed_at": null,
                      "inventory_check_at": null,
                      "created_at": "2026-04-10T09:00:00Z",
                      "updated_at": "2026-04-10T09:00:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/tyresets"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "TyreSets"
        ],
        "summary": "Create a tyre set",
        "x-summary-it": "Registra un treno in deposito",
        "x-scope": "articles.write",
        "x-description-it": "Prende in custodia un treno di gomme per un cliente. Servono\n`customer_id`, `vehicle_id`, `description` e `rim_type`.\n",
        "operationId": "tyreSetCreate",
        "description": "customer's vehicle (status starts deposited, web parity).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "vehicle_id",
                  "description",
                  "rim_type"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string"
                  },
                  "vehicle_id": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "quantity": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "rim_type": {
                    "type": "string"
                  },
                  "season_type": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "condition": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "residual_mm": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "location": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                "description": "Michelin Primacy 195/65 R15",
                "quantity": 4,
                "rim_type": "lega",
                "season_type": "estive",
                "residual_mm": 6,
                "location": "Scaffale B3"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "Il treno registrato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "tyre_set",
                  "id": "47e2c9b1-8d50-4f36-a127-6c3b9e5d2a84",
                  "seq_number": 63,
                  "status": "deposited",
                  "description": "Michelin Primacy 195/65 R15",
                  "quantity": 4,
                  "rim_type": "lega",
                  "season_type": "estive",
                  "condition": "buono",
                  "residual_mm": 6,
                  "location": "Scaffale B3",
                  "comments": null,
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "deposited_at": "2026-04-10T09:00:00Z",
                  "mounted_at": null,
                  "collected_at": null,
                  "disposed_at": null,
                  "inventory_check_at": null,
                  "created_at": "2026-04-10T09:00:00Z",
                  "updated_at": "2026-04-10T09:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/tyresets/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "TyreSets"
        ],
        "summary": "Replace tyre status counts",
        "x-summary-it": "Conta i treni per stato",
        "x-scope": "articles.read",
        "x-description-it": "Il numero di treni in ciascuno stato, con gli stessi filtri\ndell'elenco.\n",
        "operationId": "tyreSetStatusCounts",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il numero di treni per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "tyre_set_status_counts",
                  "deposited": 18,
                  "mounted": 4,
                  "collected": 61,
                  "disposed": 3,
                  "locations": [
                    "Scaffale B3",
                    "Magazzino esterno"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/tyresets/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "TyreSets"
        ],
        "summary": "Delete tyre set",
        "x-summary-it": "Elimina un treno dal deposito",
        "x-scope": "articles.write",
        "x-description-it": "Eliminazione logica del treno in deposito.\n",
        "operationId": "tyreSetDelete",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il treno eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "tyre_set",
                  "id": "47e2c9b1-8d50-4f36-a127-6c3b9e5d2a84",
                  "seq_number": 63,
                  "status": "deposited",
                  "description": "Michelin Primacy 195/65 R15",
                  "quantity": 4,
                  "rim_type": "lega",
                  "season_type": "estive",
                  "condition": "buono",
                  "residual_mm": 6,
                  "location": "Scaffale B3",
                  "comments": null,
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "deposited_at": "2026-04-10T09:00:00Z",
                  "mounted_at": null,
                  "collected_at": null,
                  "disposed_at": null,
                  "inventory_check_at": null,
                  "created_at": "2026-04-10T09:00:00Z",
                  "updated_at": "2026-04-10T09:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "tags": [
          "TyreSets"
        ],
        "summary": "Retrieve a tyre set",
        "x-summary-it": "Recupera un treno in deposito",
        "x-scope": "articles.read",
        "x-description-it": "Restituisce il treno con le date dei passaggi di stato:\n`deposited_at`, `mounted_at`, `collected_at`, `disposed_at`.\n",
        "operationId": "tyreSetGet",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il treno richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "tyre_set",
                  "id": "47e2c9b1-8d50-4f36-a127-6c3b9e5d2a84",
                  "seq_number": 63,
                  "status": "deposited",
                  "description": "Michelin Primacy 195/65 R15",
                  "quantity": 4,
                  "rim_type": "lega",
                  "season_type": "estive",
                  "condition": "buono",
                  "residual_mm": 6,
                  "location": "Scaffale B3",
                  "comments": null,
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "deposited_at": "2026-04-10T09:00:00Z",
                  "mounted_at": null,
                  "collected_at": null,
                  "disposed_at": null,
                  "inventory_check_at": null,
                  "created_at": "2026-04-10T09:00:00Z",
                  "updated_at": "2026-04-10T09:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "TyreSets"
        ],
        "summary": "Update tyre set",
        "x-summary-it": "Aggiorna un treno in deposito",
        "x-scope": "articles.write",
        "x-description-it": "Il corpo è lo stesso della creazione: va inviato l'oggetto completo,\nnon solo i campi da cambiare.\n",
        "operationId": "tyreSetPatch",
        "description": "the descriptive fields (status moves via transitions).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Il corpo è lo stesso della creazione: va inviato\nl'oggetto completo, non solo i campi da cambiare.\n",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "vehicle_id",
                  "description",
                  "rim_type"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string"
                  },
                  "vehicle_id": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "quantity": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "rim_type": {
                    "type": "string"
                  },
                  "season_type": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "condition": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "residual_mm": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "location": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "comments": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                "description": "Michelin Primacy 195/65 R15",
                "quantity": 4,
                "rim_type": "lega",
                "season_type": "estive",
                "residual_mm": 6,
                "location": "Scaffale B3"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il treno aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "tyre_set",
                  "id": "47e2c9b1-8d50-4f36-a127-6c3b9e5d2a84",
                  "seq_number": 63,
                  "status": "deposited",
                  "description": "Michelin Primacy 195/65 R15",
                  "quantity": 4,
                  "rim_type": "lega",
                  "season_type": "estive",
                  "condition": "buono",
                  "residual_mm": 6,
                  "location": "Scaffale B3",
                  "comments": null,
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "deposited_at": "2026-04-10T09:00:00Z",
                  "mounted_at": null,
                  "collected_at": null,
                  "disposed_at": null,
                  "inventory_check_at": null,
                  "created_at": "2026-04-10T09:00:00Z",
                  "updated_at": "2026-04-10T09:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/EditConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/tyresets/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera il deposito gomme dal numero e dall'anno.",
            "en": "New: retrieve a tyre set by its number and year."
          }
        ],
        "tags": [
          "TyreSets"
        ],
        "summary": "Retrieve a tyre set by number",
        "x-summary-it": "Recupera il deposito gomme dal numero",
        "x-scope": "articles.read",
        "x-description-it": "Restituisce il deposito gomme con il numero `number` dell'anno `year` (anno del deposito): lo stesso numero mostrato nell'app, per esempio 12/2026.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the tyre set carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026.\n\nThe response is identical to `GET /v1/tyresets/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The tyre set, exactly as retrieving it by id returns it.",
            "x-description-it": "Il deposito gomme, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "tyre_set",
                  "id": "47e2c9b1-8d50-4f36-a127-6c3b9e5d2a84",
                  "seq_number": 63,
                  "status": "deposited",
                  "description": "Michelin Primacy 195/65 R15",
                  "quantity": 4,
                  "rim_type": "lega",
                  "season_type": "estive",
                  "condition": "buono",
                  "residual_mm": 6,
                  "location": "Scaffale B3",
                  "comments": null,
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "deposited_at": "2026-04-10T09:00:00Z",
                  "mounted_at": null,
                  "collected_at": null,
                  "disposed_at": null,
                  "inventory_check_at": null,
                  "created_at": "2026-04-10T09:00:00Z",
                  "updated_at": "2026-04-10T09:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/tyresets/{id}/transitions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "TyreSets"
        ],
        "summary": "Change the status of tyre set",
        "x-summary-it": "Cambia stato al treno",
        "x-scope": "articles.write",
        "x-description-it": "Registra un passaggio di stato (montato, ritirato, smaltito) e\nvalorizza la data corrispondente. È l'unico modo di cambiare\n`status`: non impostarlo via PATCH, perché non aggiornerebbe le date.\n",
        "operationId": "tyreSetTransition",
        "description": "{action: deposited|mounted|disposed|collected}.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "action"
                ],
                "properties": {
                  "action": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "action": "ritirato"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il treno dopo il passaggio di stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "tyre_set",
                  "id": "47e2c9b1-8d50-4f36-a127-6c3b9e5d2a84",
                  "seq_number": 63,
                  "status": "mounted",
                  "description": "Michelin Primacy 195/65 R15",
                  "quantity": 4,
                  "rim_type": "lega",
                  "season_type": "estive",
                  "condition": "buono",
                  "residual_mm": 6,
                  "location": "Scaffale B3",
                  "comments": null,
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "deposited_at": "2026-04-10T09:00:00Z",
                  "mounted_at": "2026-08-04T09:00:00Z",
                  "collected_at": null,
                  "disposed_at": null,
                  "inventory_check_at": null,
                  "created_at": "2026-04-10T09:00:00Z",
                  "updated_at": "2026-04-10T09:00:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vat-rates": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Articles"
        ],
        "summary": "List company VAT rates",
        "x-summary-it": "Elenca le aliquote IVA",
        "x-scope": "settings.read",
        "x-description-it": "Le aliquote attive dell'azienda, da usare per valorizzare `vat_code`\nsugli articoli e sulle righe dei documenti. È una voce di\nImpostazioni, quindi richiede il permesso `settings.read`.\n",
        "description": "The VAT rates enabled for the authenticated user's company, in\nthe company's configured order — the first row is the default\nfor new line items. Codes that are disabled but still referenced\nby existing documents are accepted on write but not offered\nhere.\n\nIntentionally unpaginated: the set is a handful of codes.\n`has_more` is always `false`.\n",
        "responses": {
          "200": {
            "description": "Company-enabled VAT rates.",
            "x-description-it": "Le aliquote IVA attive.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VatRate"
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "vat_rate",
                      "code": "22",
                      "rate": 22,
                      "description": "Aliquota ordinaria"
                    },
                    {
                      "object": "vat_rate",
                      "code": "10",
                      "rate": 10,
                      "description": "Aliquota ridotta"
                    },
                    {
                      "object": "vat_rate",
                      "code": "N2.2",
                      "rate": 0,
                      "description": "Non soggetta"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/vat-rates"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "tags": [
          "Settings"
        ],
        "summary": "Replace vat rate",
        "x-summary-it": "Imposta le aliquote IVA attive",
        "x-scope": "settings.write",
        "x-description-it": "Sostituisce l'intero insieme delle aliquote attive: quelle non\nelencate vengono disattivate. I codici ammessi sono quelli di\n`GET /v1/vat-rates/catalog`.\n",
        "operationId": "vatRateSet",
        "description": "set with the given ordered codes.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "codes"
                ],
                "properties": {
                  "codes": {
                    "type": "array",
                    "description": "L'insieme completo delle aliquote attive. Le aliquote non\nelencate vengono disattivate.\n",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "codes": [
                  "22",
                  "10",
                  "4"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Le aliquote ora attive.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vat_rates",
                  "codes": [
                    "22",
                    "10"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vat-rates/catalog": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Vat rate catalog",
        "x-summary-it": "Catalogo delle aliquote IVA",
        "x-scope": "settings.read",
        "x-description-it": "Tutte le aliquote disponibili, con `enabled` a indicare quelle attive\nper l'officina.\n",
        "operationId": "vatRateCatalog",
        "description": "global catalog with the company's enabled marks.\n",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il catalogo delle aliquote.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "vat_rate_catalog_entry",
                      "code": "22",
                      "rate": 22,
                      "description": "Aliquota ordinaria",
                      "enabled": true
                    },
                    {
                      "object": "vat_rate_catalog_entry",
                      "code": "10",
                      "rate": 10,
                      "description": "Aliquota ridotta",
                      "enabled": true
                    },
                    {
                      "object": "vat_rate_catalog_entry",
                      "code": "N2.2",
                      "rate": 0,
                      "description": "Non soggetta",
                      "enabled": false
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/vat-rates/catalog"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-field-settings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "summary": "Retrieve a vehicle field settings",
        "x-summary-it": "Recupera i campi veicolo",
        "x-scope": "settings.read",
        "x-description-it": "Quali campi facoltativi della scheda veicolo sono visibili e quali\ncampi personalizzati sono definiti, incluso quali campi data\ngenerano un evento in agenda (`reminder_days_before`).\n",
        "operationId": "vehicleFieldSettingsGet",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "La configurazione dei campi veicolo.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vehicle_field_settings",
                  "field_visibility": {
                    "km": true,
                    "numero_telaio": true,
                    "transmission": false,
                    "engine_category": true,
                    "engine_power_kw": false,
                    "engine_displacement_ccm": false,
                    "engine_code": false,
                    "vehicle_weight_kg": false,
                    "registration_date": true,
                    "last_inspection_date": true,
                    "next_inspection_date": true
                  },
                  "custom_fields": [
                    {
                      "id": "cf_8a2d6f",
                      "name": "Colore",
                      "type": "string",
                      "visible": true,
                      "order": 1,
                      "reminder_days_before": null
                    },
                    {
                      "id": "cf_3f9b1c",
                      "name": "Scadenza ATP",
                      "type": "datetime",
                      "visible": true,
                      "order": 2,
                      "reminder_days_before": 0
                    },
                    {
                      "id": "cf_5e27a9",
                      "name": "Alimentazione",
                      "type": "list",
                      "options": [
                        "Benzina",
                        "Diesel",
                        "GPL/Metano",
                        "Ibrida",
                        "Elettrica"
                      ],
                      "visible": true,
                      "order": 3,
                      "reminder_days_before": null
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "tags": [
          "Settings"
        ],
        "summary": "Replace vehicle field settings",
        "x-summary-it": "Imposta i campi veicolo",
        "x-scope": "settings.write",
        "x-description-it": "Crea, modifica, riordina ed elimina i campi personalizzati e\naggiorna la visibilità dei campi facoltativi, in un'unica chiamata.\n\n`custom_fields` è l'elenco completo: un campo con `id` modifica\nquello esistente (i valori salvati sui veicoli restano), un campo\nsenza `id` ne crea uno nuovo, un campo non elencato viene eliminato.\nL'ordine dell'array è l'ordine di visualizzazione. Se\n`custom_fields` è assente la configurazione dei campi non cambia.\n\nCiò che il corpo omette viene conservato, non azzerato: una chiave\nassente in `field_visibility` mantiene il valore attuale, e su un\ncampo esistente `visible` o `reminder_days_before` assenti mantengono\nl'impostazione salvata. Così un client che ricostruisce l'elenco con\nsoli `id`, `name` e `type` non nasconde campi né spegne eventi.\n\nUn campo nascosto non genera eventi in agenda, qualunque sia il\nsuo `reminder_days_before` (che resta salvato e torna attivo quando\nil campo viene mostrato di nuovo). Lo stesso vale per la revisione:\ncon `field_visibility.next_inspection_date` a `false` non vengono\ncreati eventi di revisione.\n\nGli eventi in agenda gia' creati non vengono toccati: attivare o\ndisattivare `reminder_days_before`, o nascondere un campo, cambia\nsolo cosa succede al prossimo salvataggio di un veicolo.\n\nUn campo `list` (dalla revisione 2026-10-22) è un menu a tendina:\n`options` elenca le voci nell'ordine scelto (da 1 a 20, ognuna di\n1–30 caratteri) e il valore salvato sul veicolo è una di esse. Su\nun campo già `list`, `options` assente mantiene le voci salvate\n(come `visible`); un campo che diventa `list` deve indicarle; su\nun campo di altro tipo `options` è rifiutato.\n",
        "operationId": "vehicleFieldSettingsPut",
        "description": "Creates, updates, reorders and deletes custom field definitions and\nupdates the optional-field visibility toggles in one call.\n\n`custom_fields` is the complete list: an entry with `id` updates\nthat field (values stored on vehicles are kept), an entry without\n`id` creates one (the id is returned), an existing field left out\nis deleted. Array position is the display order. When\n`custom_fields` is absent the definitions are left unchanged.\n\nOmitted attributes are kept, never reset: an absent\n`field_visibility` key keeps its toggle, and an absent `visible` or\n`reminder_days_before` on an existing field keeps its stored value.\nNew fields default to `visible: true` and no reminder.\n\nA hidden field creates no agenda events whatever its\n`reminder_days_before` says; the value stays stored and applies\nagain once the field is shown. Likewise, with\n`field_visibility.next_inspection_date` set to `false` no revisione\nevents are created. Events already in the agenda are never touched\nhere.\n\nA `list` field (from revision 2026-10-22) is a dropdown: `options`\nlists its choices in order (1 to 20, each 1–30 characters) and the\nvalue stored on the vehicle is one of them. On a field that is\nalready a `list` an absent `options` keeps the stored choices (the\nsame rule as `visible`); a field that becomes a `list` must carry\nthem; `options` on any other type is refused.\n\nReturns 422 `parameter_invalid` (with `param` pointing at the entry)\nfor an unknown or repeated `id`, a repeated name\n(case-insensitive), a name outside 2–30 characters, an unknown\n`type` (`/custom_fields/{i}/type`: \"type deve essere string,\ninteger, datetime o list.\"), invalid `options`\n(`/custom_fields/{i}/options`, or `/custom_fields/{i}/options/{j}`\nfor one choice; a `list` field sent without `options` and with none\nstored answers on `/custom_fields/{i}`) or more than 10 fields.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "field_visibility": {
                    "type": "object",
                    "description": "Quali campi facoltativi restano visibili nella scheda\nveicolo. Le chiavi assenti mantengono il valore attuale.\n",
                    "properties": {
                      "km": {
                        "type": "boolean"
                      },
                      "numero_telaio": {
                        "type": "boolean"
                      },
                      "transmission": {
                        "type": "boolean"
                      },
                      "engine_category": {
                        "type": "boolean"
                      },
                      "engine_power_kw": {
                        "type": "boolean"
                      },
                      "engine_displacement_ccm": {
                        "type": "boolean"
                      },
                      "engine_code": {
                        "type": "boolean",
                        "x-since": "2026-10-05"
                      },
                      "vehicle_weight_kg": {
                        "type": "boolean"
                      },
                      "registration_date": {
                        "type": "boolean"
                      },
                      "last_inspection_date": {
                        "type": "boolean"
                      },
                      "next_inspection_date": {
                        "type": "boolean"
                      }
                    }
                  },
                  "custom_fields": {
                    "type": "array",
                    "maxItems": 10,
                    "description": "L'insieme completo dei campi personalizzati, nell'ordine\ndi visualizzazione. Quelli non elencati vengono rimossi;\nse il campo è assente la configurazione non cambia.\n",
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "type"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Id di un campo esistente (da GET) per modificarlo.\nOmettere per crearne uno nuovo: l'id viene generato\ne restituito. Un id sconosciuto o ripetuto è un 422.\n"
                        },
                        "name": {
                          "type": "string",
                          "minLength": 2,
                          "maxLength": 30,
                          "description": "Unico fra i campi (senza distinzione tra maiuscole e minuscole)."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "string",
                            "integer",
                            "datetime",
                            "list"
                          ],
                          "description": "`list` (dalla revisione 2026-10-22) è un menu a\ntendina: il valore è una delle `options`.\n"
                        },
                        "options": {
                          "type": "array",
                          "x-since": "2026-10-23",
                          "minItems": 1,
                          "maxItems": 20,
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 30
                          },
                          "description": "Le voci del menu a tendina, nell'ordine scelto. Solo\nper i campi `list`: da 1 a 20 voci di 1–30 caratteri\n(lettere, numeri, spazi e `- / . , ' + ( ) \u0026`), senza\ndoppioni (maiuscole e minuscole non contano); ogni\nvoce viene ripulita (spazi ai bordi tolti, spazi\ninterni ridotti a uno). Assente: invariato per un\ncampo che è già `list`; un campo che diventa `list`\ndeve indicarle. Su un campo di altro tipo è\nrifiutato. Errori: 422 `parameter_invalid` con\n`param` `/custom_fields/{i}/options` (l'elenco) o\n`/custom_fields/{i}/options/{j}` (una voce); un\ncampo `list` senza `options` e senza opzioni salvate\nrisponde su `/custom_fields/{i}`.\n"
                        },
                        "visible": {
                          "type": "boolean",
                          "description": "Mostra il campo nei moduli e nei dettagli veicolo\n(`custom_field_values`). Assente: invariato per un\ncampo esistente, `true` per uno nuovo.\n"
                        },
                        "reminder_days_before": {
                          "type": "integer",
                          "nullable": true,
                          "description": "Genera un evento in agenda per la data di questo\ncampo. `null` nessun evento, `0` il giorno stesso\ndella scadenza. Solo per i campi `datetime`: sugli\naltri tipi viene ignorato. Assente: invariato per un\ncampo esistente, nessun evento per uno nuovo.\n\nOggi l'anticipo non e' configurabile: un valore\ndiverso da zero viene normalizzato a `0`.\n"
                        },
                        "order": {
                          "type": "integer",
                          "description": "Ignorato: l'ordine è la posizione nell'array. Accettato\ncosì che il corpo di una GET possa essere rispedito.\n"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "field_visibility": {
                  "km": true,
                  "numero_telaio": true,
                  "transmission": false,
                  "engine_category": true,
                  "engine_power_kw": false,
                  "engine_displacement_ccm": false,
                  "engine_code": false,
                  "vehicle_weight_kg": false,
                  "registration_date": true,
                  "last_inspection_date": true,
                  "next_inspection_date": true
                },
                "custom_fields": [
                  {
                    "id": "cf_8a2d6f",
                    "name": "Colore",
                    "type": "string"
                  },
                  {
                    "name": "Scadenza ATP",
                    "type": "datetime",
                    "reminder_days_before": 0
                  },
                  {
                    "name": "Alimentazione",
                    "type": "list",
                    "options": [
                      "Benzina",
                      "Diesel",
                      "GPL/Metano",
                      "Ibrida",
                      "Elettrica"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "La configurazione aggiornata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vehicle_field_settings",
                  "field_visibility": {
                    "km": true,
                    "numero_telaio": true,
                    "transmission": false,
                    "engine_category": true,
                    "engine_power_kw": false,
                    "engine_displacement_ccm": false,
                    "engine_code": false,
                    "vehicle_weight_kg": false,
                    "registration_date": true,
                    "last_inspection_date": true,
                    "next_inspection_date": true
                  },
                  "custom_fields": [
                    {
                      "id": "cf_8a2d6f",
                      "name": "Colore",
                      "type": "string",
                      "visible": true,
                      "order": 1,
                      "reminder_days_before": null
                    },
                    {
                      "id": "cf_3f9b1c",
                      "name": "Scadenza ATP",
                      "type": "datetime",
                      "visible": true,
                      "order": 2,
                      "reminder_days_before": 0
                    },
                    {
                      "id": "cf_5e27a9",
                      "name": "Alimentazione",
                      "type": "list",
                      "options": [
                        "Benzina",
                        "Diesel",
                        "GPL/Metano",
                        "Ibrida",
                        "Elettrica"
                      ],
                      "visible": true,
                      "order": 3,
                      "reminder_days_before": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-sales": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Dealership"
        ],
        "summary": "List vehicle sales",
        "x-summary-it": "Elenca le vendite veicoli",
        "x-scope": "reports.read",
        "x-description-it": "Le vendite di veicoli, con margine (`total_profit`) e riferimento\nalla fattura emessa.\n",
        "operationId": "vehicleSaleList",
        "description": "q       substring match on customer / manufacturer / model / plate\nstatus  \"draft\" | \"signed\" | \"delivered\" (Bozza / Contratto firmato /\nConsegnata) — omitted returns every status (\"Tutte\")\nfrom, to, preset   date range (default last 12 months)\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero di risultati per pagina.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Cursore opaco della pagina precedente. Va restituito invariato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra per stato.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Le vendite veicoli.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "vehicle_sale",
                      "id": "5b9d3e70-2c41-48a6-9e15-7f2a6c8b4d13",
                      "seq_number": 24,
                      "status": "delivered",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "sale_date": "2026-06-19",
                      "total_after_tax": 6500.0,
                      "total_profit": 1820.0,
                      "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                      "comments": null
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/vehicle-sales",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-sales/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Dealership"
        ],
        "summary": "Vehicle sale status counts",
        "x-summary-it": "Conta le vendite per stato",
        "x-scope": "reports.read",
        "x-description-it": "Il numero di vendite in ciascuno stato, con gli stessi filtri\ndell'elenco.\n",
        "operationId": "vehicleSaleStatusCounts",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inizio dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Fine dell'intervallo, formato `YYYY-MM-DD`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "Il numero di vendite per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vehicle_sale_status_counts",
                  "draft": 1,
                  "signed": 2,
                  "delivered": 21,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-sales/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Dealership"
        ],
        "summary": "Retrieve a vehicle sale",
        "x-summary-it": "Recupera una vendita veicolo",
        "x-scope": "reports.read",
        "x-description-it": "Restituisce la singola vendita con tutte le sezioni del dettaglio:\nrighe di ricavo e di costo, il costo di acquisto del veicolo, il\nmargine, i giorni di giacenza, gli allegati, il veicolo e la fattura\ncollegata.\n\nIl margine è `total_profit`: è il valore memorizzato che leggono\nanche i report e la Situazione concessionaria. **Non** è\n`total_revenue` meno i due costi — quella colonna scorpora l'IVA\ndalle righe di ricavo accessorie. Va mostrato, mai ricalcolato,\naltrimenti il dato non coincide con le altre schermate.\n\n`vehicle` e `invoice` valgono null se la relativa lettura fallisce:\nuna vendita il cui acquisto o la cui fattura non sono leggibili\nresta comunque consultabile.\n\nIl 404 copre sia un id inesistente sia uno di un'altra officina.\n",
        "operationId": "vehicleSaleGet",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "La vendita richiesta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vehicle_sale",
                  "id": "5b9d3e70-2c41-48a6-9e15-7f2a6c8b4d13",
                  "seq_number": 24,
                  "status": "delivered",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "sale_date": "2026-06-19",
                  "total_after_tax": 6500.0,
                  "total_profit": 1820.0,
                  "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "comments": null,
                  "vehicle_stock_id": "3e9a7c12-5b48-40d6-8f27-1c4b9e2a6d35",
                  "sold_by_name": "Luca Bianchi",
                  "created_at": "2026-06-19T09:12:44Z",
                  "total_before_tax": 6450.0,
                  "total_tax": 50.0,
                  "total_discount": 0.0,
                  "total_revenue": 6500.0,
                  "total_sale_costs": 0.0,
                  "vehicle_total_cost": 4680.0,
                  "margin_percent": 28.0,
                  "days_in_stock": 92,
                  "revenue_line_items": [
                    {
                      "object": "vehicle_sale_revenue_line_item",
                      "id": "9f1b2c34-6d78-4e90-a1b2-c3d4e5f60718",
                      "category": "vehicle",
                      "description": "Vendita Fiat Panda",
                      "tax_rate": 0,
                      "vat_code": "N5",
                      "amount": 6500.0
                    }
                  ],
                  "cost_line_items": [],
                  "documents": [
                    {
                      "object": "vehicle_sale_document",
                      "id": "71c8d9e0-2f31-4a52-b6c7-8d9e0f1a2b3c",
                      "name": "contratto.pdf",
                      "content_type": "application/pdf",
                      "description": null,
                      "url": "/v1/vehicle-sales/5b9d3e70-2c41-48a6-9e15-7f2a6c8b4d13/documents/71c8d9e0-2f31-4a52-b6c7-8d9e0f1a2b3c/download",
                      "file_size_bytes": 184320,
                      "created_at": "2026-06-19T09:20:03Z"
                    }
                  ],
                  "vehicle": {
                    "object": "dealership_vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "name": "Fiat Panda",
                    "manufacturer_name": "Fiat",
                    "model": "Panda",
                    "license": "AB123CD",
                    "vin": "ZFA31200003456789",
                    "km": 82000,
                    "registered_at": "2018-03-12"
                  },
                  "invoice": {
                    "object": "dealership_invoice",
                    "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                    "seq_number": 118,
                    "issued_at": "2026-06-20"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-sales/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera la vendita dal numero e dall'anno.",
            "en": "New: retrieve a vehicle sale by its number and year."
          }
        ],
        "tags": [
          "Dealership"
        ],
        "summary": "Retrieve a vehicle sale by number",
        "x-summary-it": "Recupera la vendita dal numero",
        "x-scope": "reports.read",
        "x-description-it": "Restituisce la vendita con il numero `number` dell'anno `year` (anno della vendita): lo stesso numero mostrato nell'app, per esempio 12/2026.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the vehicle sale carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026.\n\nThe response is identical to `GET /v1/vehicle-sales/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The vehicle sale, exactly as retrieving it by id returns it.",
            "x-description-it": "La vendita, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vehicle_sale",
                  "id": "5b9d3e70-2c41-48a6-9e15-7f2a6c8b4d13",
                  "seq_number": 24,
                  "status": "delivered",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "sale_date": "2026-06-19",
                  "total_after_tax": 6500.0,
                  "total_profit": 1820.0,
                  "invoice_id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                  "comments": null,
                  "vehicle_stock_id": "3e9a7c12-5b48-40d6-8f27-1c4b9e2a6d35",
                  "sold_by_name": "Luca Bianchi",
                  "created_at": "2026-06-19T09:12:44Z",
                  "total_before_tax": 6450.0,
                  "total_tax": 50.0,
                  "total_discount": 0.0,
                  "total_revenue": 6500.0,
                  "total_sale_costs": 0.0,
                  "vehicle_total_cost": 4680.0,
                  "margin_percent": 28.0,
                  "days_in_stock": 92,
                  "revenue_line_items": [
                    {
                      "object": "vehicle_sale_revenue_line_item",
                      "id": "9f1b2c34-6d78-4e90-a1b2-c3d4e5f60718",
                      "category": "vehicle",
                      "description": "Vendita Fiat Panda",
                      "tax_rate": 0,
                      "vat_code": "N5",
                      "amount": 6500.0
                    }
                  ],
                  "cost_line_items": [],
                  "documents": [
                    {
                      "object": "vehicle_sale_document",
                      "id": "71c8d9e0-2f31-4a52-b6c7-8d9e0f1a2b3c",
                      "name": "contratto.pdf",
                      "content_type": "application/pdf",
                      "description": null,
                      "url": "/v1/vehicle-sales/5b9d3e70-2c41-48a6-9e15-7f2a6c8b4d13/documents/71c8d9e0-2f31-4a52-b6c7-8d9e0f1a2b3c/download",
                      "file_size_bytes": 184320,
                      "created_at": "2026-06-19T09:20:03Z"
                    }
                  ],
                  "vehicle": {
                    "object": "dealership_vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "name": "Fiat Panda",
                    "manufacturer_name": "Fiat",
                    "model": "Panda",
                    "license": "AB123CD",
                    "vin": "ZFA31200003456789",
                    "km": 82000,
                    "registered_at": "2018-03-12"
                  },
                  "invoice": {
                    "object": "dealership_invoice",
                    "id": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04",
                    "seq_number": 118,
                    "issued_at": "2026-06-20"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-sales/{id}/documents/{documentID}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Dealership"
        ],
        "summary": "Download a vehicle sale attachment",
        "x-summary-it": "Scarica un allegato della vendita",
        "x-scope": "reports.read",
        "x-description-it": "Restituisce i byte di un allegato elencato in `documents` nel\ndettaglio della vendita. Il percorso di archiviazione è privato: il\nfile viene letto lato server e restituito con il proprio content\ntype.\n\nL'allegato deve appartenere sia alla vendita indicata nel path sia\nall'officina chiamante: controllare solo l'officina permetterebbe di\nusare l'id di una vendita per scaricare l'allegato di un'altra.\n\nCaricamento ed eliminazione restano sul web.\n",
        "operationId": "vehicleSaleDocumentDownload",
        "responses": {
          "200": {
            "description": "the file bytes",
            "x-description-it": "Il file richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-stocks": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Dealership"
        ],
        "summary": "List vehicle stocks",
        "x-summary-it": "Elenca i veicoli in stock",
        "x-scope": "reports.read",
        "x-description-it": "I veicoli acquistati per la rivendita, con costo di acquisto, costo\ntotale sostenuto e giorni di giacenza.\n\nLa concessionaria rientra nel permesso `reports`, non ne ha uno\nproprio.\n",
        "operationId": "vehicleStockList",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Numero di risultati per pagina.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Ricerca testuale libera.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Cursore opaco della pagina precedente. Va restituito invariato.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "I veicoli in stock.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "vehicle_stock",
                      "id": "3e9a7c12-5b48-40d6-8f27-1c4b9e2a6d35",
                      "seq_number": 18,
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "purchase_date": "2026-05-04",
                      "purchase_cost": 4200.0,
                      "total_cost": 4680.0,
                      "sale_price": 6500.0,
                      "days_in_stock": 92,
                      "sold": false
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/vehicle-stocks"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Dealership"
        ],
        "summary": "Create a vehicle stock",
        "x-summary-it": "Registra un acquisto veicolo",
        "x-scope": "reports.write",
        "x-description-it": "Registra un nuovo acquisto veicolo. Crea anche il VEICOLO\ncorrispondente: un mezzo destinato al piazzale è per definizione\nnuovo per l'officina, quindi non esiste un veicolo a cui puntare —\nper questo i dati identificativi (`manufacturer_id`, `model`,\n`license`, `vin`, `registered_at`) stanno in questo corpo e non in un\n`vehicle_id`.\n\n`purchase_cost` e `total_cost` **non** si inviano: si ricavano da\n`line_items` — `purchase_cost` somma le righe con categoria\n\"purchase\", `total_cost` le somma tutte, come fa il form web.\nInviare totali propri farebbe divergere i valori memorizzati dalle\nrighe che li spiegano, perciò i campi non riconosciuti vengono\nrifiutati.\n\nAnche `line_items[].tax_rate` è assente: le righe di acquisto sono\nmemorizzate con aliquota 0 e `total_cost` è la somma lorda, quindi\nun'aliquota non cambierebbe nulla.\n\nRichiede il modulo Concessionaria (altrimenti 403). `purchase_date`\nnon può essere nel futuro; la data odierna è accettata. La targa\nviene normalizzata in maiuscolo.\n\n**Attenzione allo scope:** la concessionaria non ha un permesso\nproprio e ricade in `reports`, quindi questa scrittura richiede\n`reports.write` — che qui non scrive un report, registra un acquisto.\n\nLa modifica di un acquisto e l'intero ciclo di vita della vendita\nrestano sul web.\n\nRestituisce 201 con lo stesso payload di\n`GET /v1/vehicle-stocks/{id}`.\n\nL'acquisto creato **non** comparirà in `GET /v1/vehicle-stocks` se in\nseguito gli verrà associata una vendita: quell'elenco contiene solo i\nveicoli ancora disponibili.\n",
        "operationId": "vehicleStockCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "manufacturer_id",
                  "model",
                  "license",
                  "purchase_date",
                  "line_items"
                ],
                "properties": {
                  "manufacturer_id": {
                    "type": "string",
                    "description": "Marca del veicolo."
                  },
                  "model": {
                    "type": "string",
                    "description": "Modello del veicolo."
                  },
                  "license": {
                    "type": "string",
                    "description": "Targa; normalizzata in maiuscolo."
                  },
                  "vin": {
                    "type": "string",
                    "nullable": true,
                    "description": "Numero di telaio."
                  },
                  "registered_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "Data di prima immatricolazione."
                  },
                  "purchase_date": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Data di acquisto; non può essere nel futuro."
                  },
                  "supplier_id": {
                    "type": "string",
                    "nullable": true,
                    "description": "Fornitore da cui è stato acquistato."
                  },
                  "sold_by": {
                    "type": "string",
                    "nullable": true,
                    "description": "Collaboratore a cui è assegnata la vendita."
                  },
                  "comments": {
                    "type": "string",
                    "nullable": true,
                    "description": "Note libere."
                  },
                  "vehicle_sale_price": {
                    "type": "number",
                    "nullable": true,
                    "description": "Prezzo di vendita richiesto."
                  },
                  "line_items": {
                    "type": "array",
                    "minItems": 1,
                    "description": "Voci di costo che compongono total_cost.",
                    "items": {
                      "type": "object",
                      "required": [
                        "category",
                        "description",
                        "amount"
                      ],
                      "properties": {
                        "category": {
                          "type": "string",
                          "enum": [
                            "purchase",
                            "registration_fees",
                            "bodywork",
                            "refurbishment",
                            "tires",
                            "inspection",
                            "transport",
                            "cleaning",
                            "commissions",
                            "other"
                          ]
                        },
                        "description": {
                          "type": "string"
                        },
                        "amount": {
                          "type": "number",
                          "description": "Importo; non negativo."
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "manufacturer_id": "9c2e6b41-70a3-4d18-8f52-1b7d3c9a4e60",
                "model": "Giulietta",
                "license": "ZZ999ZZ",
                "vin": "ZAR94000007654321",
                "registered_at": "2019-04-10T00:00:00Z",
                "purchase_date": "2026-08-01T00:00:00Z",
                "supplier_id": "6d5c4b3a-2918-4706-b5c4-d3e2f1a09b87",
                "comments": "Ritirata da permuta",
                "vehicle_sale_price": 9500.0,
                "line_items": [
                  {
                    "category": "purchase",
                    "description": "Acquisto Giulietta",
                    "amount": 6000.0
                  },
                  {
                    "category": "bodywork",
                    "description": "Ritocco portiera",
                    "amount": 450.5
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "created",
            "x-description-it": "L'acquisto appena registrato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-stocks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Dealership"
        ],
        "summary": "Retrieve a vehicle stock",
        "x-summary-it": "Recupera un acquisto veicolo",
        "x-scope": "reports.read",
        "x-description-it": "Il dettaglio dell'acquisto. Aggiunge alla riga di elenco le voci di\ncosto che compongono `total_cost`, il fornitore, il veicolo, gli\nallegati e la vendita con cui il mezzo è uscito, se venduto.\n\nA differenza dell'elenco, qui si risolve anche un acquisto già\nvenduto: è l'unico modo per raggiungerlo, dato che l'elenco li\nesclude.\n\n`sold` è true quando esiste una vendita collegata. L'elenco ricava lo\nstesso flag da `vehicle_stocks.sold_by`, che il flusso di vendita non\nvalorizza sempre: il dettaglio riporta il dato più affidabile.\n\n`sale` non è la riga completa della vendita: la query per acquisto\nnon legge `total_after_tax`, quindi vengono esposti solo i campi\neffettivamente valorizzati. Per gli importi completi seguire\n`sale.id`.\n\nIl 404 copre sia un id inesistente sia uno di un'altra officina.\n",
        "operationId": "vehicleStockGet",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "L'acquisto richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vehicle_stock",
                  "id": "3e9a7c12-5b48-40d6-8f27-1c4b9e2a6d35",
                  "seq_number": 18,
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "purchase_date": "2026-05-04",
                  "purchase_cost": 4200.0,
                  "total_cost": 4680.0,
                  "sale_price": 6500.0,
                  "days_in_stock": 92,
                  "sold": true,
                  "supplier_id": "6d5c4b3a-2918-4706-b5c4-d3e2f1a09b87",
                  "supplier_name": "Autoparco Bergamo",
                  "sold_by_name": "Luca Bianchi",
                  "comments": null,
                  "created_at": "2026-05-04T08:31:17Z",
                  "line_items": [
                    {
                      "object": "vehicle_stock_line_item",
                      "id": "a1b2c3d4-e5f6-4708-9a0b-1c2d3e4f5061",
                      "category": "purchase",
                      "description": "Acquisto Fiat Panda",
                      "tax_rate": 0,
                      "vat_code": null,
                      "amount": 4200.0
                    },
                    {
                      "object": "vehicle_stock_line_item",
                      "id": "b2c3d4e5-f607-4819-a0b1-2c3d4e5f6072",
                      "category": "bodywork",
                      "description": "Ritocco paraurti",
                      "tax_rate": 22,
                      "vat_code": null,
                      "amount": 480.0
                    }
                  ],
                  "documents": [],
                  "vehicle": {
                    "object": "dealership_vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "name": "Fiat Panda",
                    "manufacturer_name": "Fiat",
                    "model": "Panda",
                    "license": "AB123CD",
                    "vin": "ZFA31200003456789",
                    "km": 82000,
                    "registered_at": "2018-03-12"
                  },
                  "sale": {
                    "object": "vehicle_sale_link",
                    "id": "5b9d3e70-2c41-48a6-9e15-7f2a6c8b4d13",
                    "seq_number": 24,
                    "status": "delivered",
                    "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                    "customer_name": "Mario Rossi",
                    "sale_date": "2026-06-19",
                    "sale_price": 6500.0,
                    "total_profit": 1820.0
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-stocks/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera il veicolo in stock dal numero e dall'anno.",
            "en": "New: retrieve a vehicle stock by its number and year."
          }
        ],
        "tags": [
          "Dealership"
        ],
        "summary": "Retrieve a vehicle stock by number",
        "x-summary-it": "Recupera il veicolo in stock dal numero",
        "x-scope": "reports.read",
        "x-description-it": "Restituisce il veicolo in stock con il numero `number` dell'anno `year` (anno dell'ingresso in stock): lo stesso numero mostrato nell'app, per esempio 12/2026.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the vehicle stock carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026.\n\nThe response is identical to `GET /v1/vehicle-stocks/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The vehicle stock, exactly as retrieving it by id returns it.",
            "x-description-it": "Il veicolo in stock, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "vehicle_stock",
                  "id": "3e9a7c12-5b48-40d6-8f27-1c4b9e2a6d35",
                  "seq_number": 18,
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "purchase_date": "2026-05-04",
                  "purchase_cost": 4200.0,
                  "total_cost": 4680.0,
                  "sale_price": 6500.0,
                  "days_in_stock": 92,
                  "sold": true,
                  "supplier_id": "6d5c4b3a-2918-4706-b5c4-d3e2f1a09b87",
                  "supplier_name": "Autoparco Bergamo",
                  "sold_by_name": "Luca Bianchi",
                  "comments": null,
                  "created_at": "2026-05-04T08:31:17Z",
                  "line_items": [
                    {
                      "object": "vehicle_stock_line_item",
                      "id": "a1b2c3d4-e5f6-4708-9a0b-1c2d3e4f5061",
                      "category": "purchase",
                      "description": "Acquisto Fiat Panda",
                      "tax_rate": 0,
                      "vat_code": null,
                      "amount": 4200.0
                    },
                    {
                      "object": "vehicle_stock_line_item",
                      "id": "b2c3d4e5-f607-4819-a0b1-2c3d4e5f6072",
                      "category": "bodywork",
                      "description": "Ritocco paraurti",
                      "tax_rate": 22,
                      "vat_code": null,
                      "amount": 480.0
                    }
                  ],
                  "documents": [],
                  "vehicle": {
                    "object": "dealership_vehicle",
                    "id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                    "name": "Fiat Panda",
                    "manufacturer_name": "Fiat",
                    "model": "Panda",
                    "license": "AB123CD",
                    "vin": "ZFA31200003456789",
                    "km": 82000,
                    "registered_at": "2018-03-12"
                  },
                  "sale": {
                    "object": "vehicle_sale_link",
                    "id": "5b9d3e70-2c41-48a6-9e15-7f2a6c8b4d13",
                    "seq_number": 24,
                    "status": "delivered",
                    "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                    "customer_name": "Mario Rossi",
                    "sale_date": "2026-06-19",
                    "sale_price": 6500.0,
                    "total_profit": 1820.0
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicle-stocks/{id}/documents/{documentID}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Dealership"
        ],
        "summary": "Download a vehicle stock attachment",
        "x-summary-it": "Scarica un allegato dell'acquisto",
        "x-scope": "reports.read",
        "x-description-it": "Restituisce i byte di un allegato elencato in `documents` nel\ndettaglio dell'acquisto, con le stesse regole della variante per la\nvendita: lettura lato server del percorso privato, content type\noriginale, e l'allegato deve appartenere sia all'acquisto indicato\nnel path sia all'officina chiamante.\n\nCaricamento ed eliminazione restano sul web.\n",
        "operationId": "vehicleStockDocumentDownload",
        "responses": {
          "200": {
            "description": "the file bytes",
            "x-description-it": "Il file richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Vehicles"
        ],
        "summary": "List vehicles",
        "x-summary-it": "Elenca i veicoli",
        "x-scope": "vehicles.read",
        "x-description-it": "Restituisce i veicoli dell'officina, dal più recente. Filtra per\ntesto con `q` (modello o targa) e per proprietario con\n`customer_id`. La paginazione usa un cursore opaco: passa\n`next_starting_after` invariato in `starting_after` per la pagina\nsuccessiva e usa `has_more` per sapere se fermarti.\n",
        "description": "Returns vehicles in the authenticated user's company, newest\nfirst. Search by name with `?q=`; filter to a single customer\nwith `?customer_id=`; paginate with cursor-based `?limit=` +\n`?starting_after=`.\n\nThe cursor is opaque — clients MUST NOT parse it. Pass back\nthe value verbatim. Today it encodes an offset; tomorrow it\nmay encode a `(created_at, id)` tuple without a client-visible\nchange.\n\n`has_more` is `true` when more results exist beyond this page.\nDrive the \"load more\" UI off this flag, not off `data.length`\n— the last page may still contain a full `limit` worth of\nresults.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on vehicle model / license.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "description": "Restrict the result set to vehicles owned by a single\ncustomer. Useful for the \"customer detail → vehicles\"\nmobile screen.\n",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key (default `date` = creation date). Named keys are\nmapped server-side to whitelisted columns.\n",
            "schema": {
              "type": "string",
              "enum": [
                "date",
                "customer",
                "vehicle",
                "license"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction (default `desc`).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100. Out-of-range values clamp\nsilently to the defaults — never error.\n",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response's next-page link. Pass\nverbatim. Omit on the first page.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of vehicles.",
            "x-description-it": "Una pagina di veicoli.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Vehicle"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "vehicle",
                      "id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                      "license": "AB123CD",
                      "manufacturer_id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                      "manufacturer_name": "Fiat",
                      "model": "Panda",
                      "variant": "1.2 Lounge",
                      "vin": "ZFA31200003456789",
                      "transmission": "manuale",
                      "engine_power_kw": 51,
                      "engine_displacement_ccm": 1242,
                      "engine_code": "169A4000",
                      "engine_category": "Euro 6",
                      "registered_at": "2019-06-12T00:00:00Z",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "km": 84210,
                      "km_registered_at": "2026-07-14T09:12:00Z",
                      "last_inspection_at": "2025-06-01T00:00:00Z",
                      "next_inspection_at": "2027-06-01T00:00:00Z",
                      "is_rental": false,
                      "created_at": "2024-03-02T10:14:33Z",
                      "updated_at": "2026-07-14T09:12:00Z"
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJvIjoyMH0",
                  "url": "/v1/vehicles"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "Targa duplicata: se un altro veicolo attivo dell'officina ha già la stessa targa (senza distinguere maiuscole, spazi e trattini) la creazione è rifiutata con 409 `license_plate_taken` e l'id di quel veicolo in `candidates`; con `allow_duplicate_license: true` nel corpo salva comunque. Le revisioni precedenti non controllano.",
            "en": "Duplicate plate: when another live vehicle of the workshop already carries the plate (ignoring case, spaces and dashes) the create is refused with 409 `license_plate_taken` and that vehicle's id in `candidates`; `allow_duplicate_license: true` in the body saves anyway. Earlier revisions do not check."
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "summary": "Create a vehicle",
        "x-summary-it": "Crea un veicolo",
        "x-scope": "vehicles.write",
        "x-description-it": "Crea un veicolo. Sono obbligatori `manufacturer_id` e `model`; la\nmarca deve essere fra quelle attive per l'officina (vedi\n`GET /v1/manufacturers`). Collega il veicolo a un cliente con\n`customer_id`: può restare vuoto e venire assegnato in seguito.\n\nI campi personalizzati vanno in `custom_fields`, con chiave l'`id`\ndel campo (da `GET /v1/vehicle-field-settings`) e un valore del\ntipo del campo: testo, numero intero, oppure data `YYYY-MM-DD` per\ni campi data. Una chiave sconosciuta o un valore del tipo sbagliato\nrestituisce 422. Un campo data con `reminder_days_before` crea\nl'evento in agenda.\n",
        "description": "Creates a new vehicle in the authenticated user's company.\n`company_id` is always derived from the bearer token and\n`created_by` from the authenticated user — neither can be\nset from the body.\n\nRequired: `model`, `manufacturer_id`.\n\nRental fields (`is_rental`, `rental_*`) are not accepted on\ncreate; rental setup is a separate flow.\n\nCreating a vehicle also schedules its deadline events in the\nagenda: the revisione (`next_inspection_at`) and every custom\ndate field whose definition sets `reminder_days_before` (see\n`GET /v1/vehicle-field-settings`).\n\n`custom_fields` is keyed by definition `id` and validated\nagainst the company's definitions — see\n`VehicleCustomFieldValues` for the accepted value per type.\n\nReturns the fully materialized vehicle (including the joined\n`manufacturer_name` and `customer_name`).\n\nForeign-key references in the body (`customer_id`, etc.) must\nresolve to resources owned by the authenticated user's company.\nUnreachable refs return 422 `parameter_invalid` pointing at the\noffending field.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VehicleCreateRequest"
              },
              "example": {
                "manufacturer_id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                "model": "Panda",
                "license": "AB123CD",
                "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                "km": 84210.5,
                "custom_fields": {
                  "cf_8a2d6f": "Rosso",
                  "cf_3f9b1c": "2027-03-31"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Vehicle created.",
            "x-description-it": "Il veicolo creato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                },
                "example": {
                  "object": "vehicle",
                  "id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                  "license": "AB123CD",
                  "manufacturer_id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                  "manufacturer_name": "Fiat",
                  "model": "Panda",
                  "variant": "1.2 Lounge",
                  "vin": "ZFA31200003456789",
                  "transmission": "manuale",
                  "engine_power_kw": 51,
                  "engine_displacement_ccm": 1242,
                  "engine_code": "169A4000",
                  "engine_category": "Euro 6",
                  "registered_at": "2019-06-12T00:00:00Z",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "km": 84210,
                  "km_registered_at": "2026-07-14T09:12:00Z",
                  "last_inspection_at": "2025-06-01T00:00:00Z",
                  "next_inspection_at": "2027-06-01T00:00:00Z",
                  "is_rental": false,
                  "created_at": "2024-03-02T10:14:33Z",
                  "updated_at": "2026-07-14T09:12:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/LicensePlateTaken"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Creation year (year of created_at).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The sequence number (seq_number).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 123
        }
      ],
      "get": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Retrieve a vehicle by number",
        "x-summary-it": "Recupera un veicolo per numero e anno",
        "x-scope": "vehicles.read",
        "description": "Returns the same detail response as GET /v1/vehicles/{id}, scoped to\nthe authenticated company. The number is unique within the creation\nyear. Deleted records and records from other companies return 404.\nAvailable on existing supported API revisions; no new revision is required.\n",
        "responses": {
          "200": {
            "description": "Vehicle found, with the same body as retrieval by id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Multiple records match; the error contains candidate ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Vehicle id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Retrieve a vehicle",
        "x-summary-it": "Recupera un veicolo",
        "x-scope": "vehicles.read",
        "x-description-it": "Restituisce un singolo veicolo. Rispetto all'elenco include anche i\ndocumenti allegati, i campi personalizzati risolti\n(`custom_field_values`) e il primo appuntamento in agenda\n(`next_appointment`).\n",
        "description": "Returns a single vehicle by id, scoped to the authenticated\nuser's company. Unknown ids, ids from another company, and\nsoft-deleted rows all return 404 — never 403, never 200 with\ndeleted_at populated.\n",
        "responses": {
          "200": {
            "description": "Vehicle found.",
            "x-description-it": "Il veicolo richiesto.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                },
                "example": {
                  "object": "vehicle",
                  "id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                  "license": "AB123CD",
                  "manufacturer_id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                  "manufacturer_name": "Fiat",
                  "model": "Panda",
                  "variant": "1.2 Lounge",
                  "vin": "ZFA31200003456789",
                  "transmission": "manuale",
                  "engine_power_kw": 51,
                  "engine_displacement_ccm": 1242,
                  "engine_code": "169A4000",
                  "engine_category": "Euro 6",
                  "registered_at": "2019-06-12T00:00:00Z",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "km": 84210,
                  "km_registered_at": "2026-07-14T09:12:00Z",
                  "last_inspection_at": "2025-06-01T00:00:00Z",
                  "next_inspection_at": "2027-06-01T00:00:00Z",
                  "is_rental": false,
                  "created_at": "2024-03-02T10:14:33Z",
                  "updated_at": "2026-07-14T09:12:00Z",
                  "next_appointment": null,
                  "comments": "Cliente chiede sempre olio Selenia.",
                  "custom_fields": {
                    "cf_8a2d6f": "Rosso",
                    "cf_3f9b1c": "2027-03-31"
                  },
                  "custom_field_values": [
                    {
                      "id": "cf_8a2d6f",
                      "name": "Colore",
                      "type": "string",
                      "value": "Rosso"
                    },
                    {
                      "id": "cf_3f9b1c",
                      "name": "Scadenza ATP",
                      "type": "datetime",
                      "value": "2027-03-31"
                    }
                  ],
                  "documents": [
                    {
                      "object": "vehicle_document",
                      "id": "e2a4b6c8-1d3f-4059-b7e8-2c9a5d1f3b64",
                      "name": "libretto.pdf",
                      "content_type": "application/pdf",
                      "file_size_bytes": 148213,
                      "url": "/v1/vehicles/7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47/documents/e2a4b6c8-1d3f-4059-b7e8-2c9a5d1f3b64/download",
                      "created_at": "2026-07-14T09:20:11Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          },
          {
            "since": "2026-10-22",
            "it": "Targa duplicata: cambiare la `license` in una targa che un altro veicolo attivo dell'officina ha già (senza distinguere maiuscole, spazi e trattini) è rifiutato con 409 `license_plate_taken` e l'id di quel veicolo in `candidates`; con `allow_duplicate_license: true` nel corpo salva comunque. Le revisioni precedenti non controllano.",
            "en": "Duplicate plate: changing `license` to a plate another live vehicle of the workshop already carries (ignoring case, spaces and dashes) is refused with 409 `license_plate_taken` and that vehicle's id in `candidates`; `allow_duplicate_license: true` in the body saves anyway. Earlier revisions do not check."
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "summary": "Update a vehicle",
        "x-summary-it": "Aggiorna un veicolo",
        "x-scope": "vehicles.write",
        "x-description-it": "Header `If-Match` (obbligatorio dalla revisione 2026-10-22) con il campo `version` letto nel\nveicolo: se è stato salvato nel frattempo la modifica è rifiutata con\n409 e nulla viene salvato.\n\nAggiorna solo i campi presenti nel corpo della richiesta: i campi\nomessi restano invariati. Per svuotare un campo passalo\nesplicitamente a `null`.\n\n`custom_fields` si sostituisce per intero: invia tutti i campi\npersonalizzati visibili che vuoi conservare, perché quelli omessi\nvengono svuotati (e il loro evento in agenda rimosso). I campi\nnascosti conservano il loro valore finché non li invii\nesplicitamente.\n\nI campi personalizzati vanno in `custom_fields`, con chiave l'`id`\ndel campo (da `GET /v1/vehicle-field-settings`) e un valore del\ntipo del campo: testo, numero intero, oppure data `YYYY-MM-DD` per\ni campi data. Una chiave sconosciuta o un valore del tipo sbagliato\nrestituisce 422. Un campo data con `reminder_days_before` crea\nl'evento in agenda.\n",
        "description": "`If-Match` header (required from revision 2026-10-22) with the `version` you read: if the\nrecord was saved meanwhile the update is refused with 409 and\nnothing is written.\n\nPartial update following the design philosophy:\n\n- **Omitted JSON keys** leave the corresponding entity fields\n  untouched.\n- **Explicit JSON `null`** clears the field (SQL NULL).\n- **Concrete values** overwrite the entity field.\n\n`model` and `manufacturer_id` are NOT NULL in the schema;\nsending either as `null` is a 422 `parameter_invalid` error.\n\n`custom_fields` is patched as a whole-object replacement\n(omit = unchanged, null = clear, object = overwrite). Per-key\nmerging is intentionally not supported. Keys and values are\nvalidated against the company's definitions — see\n`VehicleCustomFieldValues`.\n\nDeadline dates keep their agenda events in step: moving a date\nmoves its event, clearing one removes it, and a request that\ndoes not change a date touches no event. This covers\n`next_inspection_at` and every custom date field whose\ndefinition sets `reminder_days_before`. Because `custom_fields`\nis replaced wholesale, omitting a visible field's key that held\na date clears it — and removes that event. Hidden fields keep\ntheir values (and events) unless their key is sent; `null` for\nthe whole object clears visible fields only.\n\nRental fields are read-only here — use the rental setup flow\nto change them.\n\nReturns the fully materialized post-update vehicle.\n\nForeign-key references in the body (`customer_id`, etc.) must\nresolve to resources owned by the authenticated user's company.\nUnreachable refs return 422 `parameter_invalid` pointing at the\noffending field.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VehiclePatchRequest"
              },
              "example": {
                "license": "EF456GH",
                "km": 90100,
                "customer_id": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vehicle updated. Returns the full post-update entity.",
            "x-description-it": "Il veicolo aggiornato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                },
                "example": {
                  "object": "vehicle",
                  "id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                  "license": "AB123CD",
                  "manufacturer_id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                  "manufacturer_name": "Fiat",
                  "model": "Panda",
                  "variant": "1.2 Lounge",
                  "vin": "ZFA31200003456789",
                  "transmission": "manuale",
                  "engine_power_kw": 51,
                  "engine_displacement_ccm": 1242,
                  "engine_code": "169A4000",
                  "engine_category": "Euro 6",
                  "registered_at": "2019-06-12T00:00:00Z",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "km": 84210,
                  "km_registered_at": "2026-07-14T09:12:00Z",
                  "last_inspection_at": "2025-06-01T00:00:00Z",
                  "next_inspection_at": "2027-06-01T00:00:00Z",
                  "is_rental": false,
                  "created_at": "2024-03-02T10:14:33Z",
                  "updated_at": "2026-07-14T09:12:00Z",
                  "next_appointment": null,
                  "comments": "Cliente chiede sempre olio Selenia.",
                  "documents": [
                    {
                      "object": "vehicle_document",
                      "id": "e2a4b6c8-1d3f-4059-b7e8-2c9a5d1f3b64",
                      "name": "libretto.pdf",
                      "content_type": "application/pdf",
                      "file_size_bytes": 148213,
                      "url": "/v1/vehicles/7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47/documents/e2a4b6c8-1d3f-4059-b7e8-2c9a5d1f3b64/download",
                      "created_at": "2026-07-14T09:20:11Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "409": {
            "$ref": "#/components/responses/VehiclePatchConflict"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Soft-delete a vehicle",
        "x-summary-it": "Elimina un veicolo",
        "x-scope": "vehicles.write",
        "x-description-it": "Eliminazione logica: il veicolo sparisce dagli elenchi ma i documenti\ngià emessi che lo citano restano intatti.\n",
        "description": "Marks the vehicle as deleted by stamping `deleted_at` with the\nserver clock. The row stays in the database; subsequent\nGET/PATCH/DELETE all 404.\n\nReturns the deleted resource (200) rather than 204 No Content\nso the response shape is consistent across the API and the\nmobile UI can render an undo banner with the timestamp.\n",
        "responses": {
          "200": {
            "description": "Vehicle soft-deleted. `deleted_at` is populated.",
            "x-description-it": "Il veicolo eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vehicle"
                },
                "example": {
                  "object": "vehicle",
                  "id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                  "license": "AB123CD",
                  "manufacturer_id": "c3d9e8f1-4a72-4b60-8e15-9d2f7c4a6b83",
                  "manufacturer_name": "Fiat",
                  "model": "Panda",
                  "variant": "1.2 Lounge",
                  "vin": "ZFA31200003456789",
                  "transmission": "manuale",
                  "engine_power_kw": 51,
                  "engine_displacement_ccm": 1242,
                  "engine_code": "169A4000",
                  "engine_category": "Euro 6",
                  "registered_at": "2019-06-12T00:00:00Z",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "km": 84210,
                  "km_registered_at": "2026-07-14T09:12:00Z",
                  "last_inspection_at": "2025-06-01T00:00:00Z",
                  "next_inspection_at": "2027-06-01T00:00:00Z",
                  "is_rental": false,
                  "created_at": "2024-03-02T10:14:33Z",
                  "updated_at": "2026-07-14T09:12:00Z",
                  "deleted_at": "2026-07-20T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/{id}/checkins": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Vehicle id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-13",
            "it": "Paginazione: `limit` (predefinito 20, massimo 100) e `starting_after`; le voci sono ordinate dalla più recente (per data e id) e `next_starting_after` porta alla pagina successiva. Prima di questa revisione l'elenco torna intero.",
            "en": "Pagination: `limit` (default 20, max 100) and `starting_after`; entries come newest first (by date, then id) and `next_starting_after` leads to the next page. Before this revision the list comes back whole."
          },
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "summary": "Check-in (Accettazioni) history for a vehicle",
        "x-summary-it": "Storico accettazioni del veicolo",
        "x-scope": "checkins.read",
        "x-description-it": "Le accettazioni registrate per il veicolo, dalla più recente.\nRichiede il permesso `checkins.read`.\n",
        "description": "Every check-in linked to the vehicle, newest first, without\nline items. Paged from revision 2026-10-13 (`limit`, `starting_after` → `next_starting_after`); older revisions get every row (`has_more` false).\n",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size from revision 2026-10-13 (ignored before).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "The `next_starting_after` of the previous page (revision 2026-10-13).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The vehicle's check-ins.",
            "x-description-it": "Le accettazioni del veicolo.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Checkin"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "checkin",
                      "id": "1c7a9e04-5f83-4b26-8d10-6e2b4a9c7f31",
                      "status": "convertita",
                      "vehicle_id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "created_at": "2026-05-17T16:44:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/vehicles/7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47/checkins"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/{id}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Vehicle id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Upload a document to a vehicle",
        "x-summary-it": "Allega un documento al veicolo",
        "x-scope": "vehicles.write",
        "x-description-it": "Carica un file fra i Documenti Allegati del veicolo. La richiesta e'\n`multipart/form-data` con il file nel campo `file`, non JSON.\n",
        "description": "`multipart/form-data` — mirrors the customer document upload:\npart `file` (required, max 25MB) and field `description`\n(display label; the app requires it, the server falls back to\nthe file name — web form parity). Storage layout matches the\nweb upload, so documents from either surface are identical.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "description": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored document.",
            "x-description-it": "Il documento allegato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VehicleDocument"
                },
                "example": {
                  "object": "vehicle_document",
                  "id": "e2a4b6c8-1d3f-4059-b7e8-2c9a5d1f3b64",
                  "name": "libretto.pdf",
                  "content_type": "application/pdf",
                  "description": "Carta di circolazione",
                  "file_size_bytes": 148213,
                  "url": "/v1/vehicles/7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47/documents/e2a4b6c8-1d3f-4059-b7e8-2c9a5d1f3b64/download",
                  "created_at": "2026-07-14T09:20:11Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/{id}/documents/{documentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Delete vehicle document",
        "x-summary-it": "Elimina un documento del veicolo",
        "x-scope": "vehicles.write",
        "x-description-it": "Rimuove definitivamente il file allegato.\n",
        "operationId": "vehicleDocumentDelete",
        "description": "VehicleDocumentDelete handles\nDELETE /v1/vehicles/{id}/documents/{documentID}.\n",
        "responses": {
          "200": {
            "description": "success (the removed document)",
            "x-description-it": "Il documento eliminato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VehicleDocument"
                },
                "example": {
                  "object": "vehicle_document",
                  "id": "e2a4b6c8-1d3f-4059-b7e8-2c9a5d1f3b64",
                  "name": "libretto.pdf",
                  "content_type": "application/pdf",
                  "file_size_bytes": 148213,
                  "created_at": "2026-07-14T09:20:11Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/{id}/documents/{documentID}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Vehicle id (UUID).",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Document id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Vehicles"
        ],
        "summary": "Download a vehicle document",
        "x-summary-it": "Scarica un documento del veicolo",
        "x-scope": "vehicles.read",
        "x-description-it": "Restituisce il contenuto del file, non JSON. È il percorso indicato\ndal campo `url` di ogni documento.\n",
        "description": "Streams the stored bytes with the document's Content-Type and\nan attachment Content-Disposition. The target of every\nvehicle_document `url`.\n",
        "responses": {
          "200": {
            "description": "The file bytes.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/{id}/quotes": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Vehicle id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-13",
            "it": "Paginazione: `limit` (predefinito 20, massimo 100) e `starting_after`; le voci sono ordinate dalla più recente (per data e id) e `next_starting_after` porta alla pagina successiva. Prima di questa revisione l'elenco torna intero.",
            "en": "Pagination: `limit` (default 20, max 100) and `starting_after`; entries come newest first (by date, then id) and `next_starting_after` leads to the next page. Before this revision the list comes back whole."
          },
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "summary": "Quote history for a vehicle",
        "x-summary-it": "Storico preventivi del veicolo",
        "x-scope": "quotes.read",
        "x-description-it": "I preventivi collegati al veicolo, dal più recente. Richiede il\npermesso `quotes.read`.\n",
        "description": "Every quote linked to the vehicle, newest first, without line\nitems. Paged from revision 2026-10-13 (`limit`, `starting_after` → `next_starting_after`); older revisions get every row (`has_more` false).\n",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size from revision 2026-10-13 (ignored before).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "The `next_starting_after` of the previous page (revision 2026-10-13).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The vehicle's quotes.",
            "x-description-it": "I preventivi del veicolo.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Quote"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "quote",
                      "id": "8d3f5b92-2a71-4c08-9e6b-1f4a7d0c3e85",
                      "seq_number": 87,
                      "status": "accettato",
                      "vehicle_id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "total_after_tax": 310.0,
                      "created_at": "2026-04-02T11:05:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/vehicles/7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47/quotes"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/vehicles/{id}/worksheets": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Vehicle id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-13",
            "it": "Paginazione: `limit` (predefinito 20, massimo 100) e `starting_after`; le voci sono ordinate dalla più recente (per data e id) e `next_starting_after` porta alla pagina successiva. Prima di questa revisione l'elenco torna intero.",
            "en": "Pagination: `limit` (default 20, max 100) and `starting_after`; entries come newest first (by date, then id) and `next_starting_after` leads to the next page. Before this revision the list comes back whole."
          },
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "summary": "Worksheet history for a vehicle",
        "x-summary-it": "Storico schede lavoro del veicolo",
        "x-scope": "worksheets.read",
        "x-description-it": "Le schede lavoro del veicolo, dalla più recente. Restituisce schede\nlavoro, quindi richiede il permesso `worksheets.read` e non\n`vehicles.read`.\n",
        "description": "Every worksheet linked to the vehicle, newest first, without\nline items — web vehicle-page parity. Paged from revision 2026-10-13 (`limit`, `starting_after` → `next_starting_after`); older revisions get every row (`has_more` false).\n",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size from revision 2026-10-13 (ignored before).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "The `next_starting_after` of the previous page (revision 2026-10-13).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The vehicle's worksheets.",
            "x-description-it": "Le schede lavoro del veicolo.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Worksheet"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "worksheet",
                      "id": "5e8c1a37-9d24-4f60-b3a8-7c2e9f1d4b05",
                      "seq_number": 412,
                      "status": "completata",
                      "vehicle_id": "7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "total_after_tax": 285.4,
                      "created_at": "2026-05-18T08:30:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/vehicles/7b1f4a92-3c8d-4e15-9a20-5f6c8d3e1b47/worksheets"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/work-categories": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Worksheets"
        ],
        "summary": "List work categories (Categoria)",
        "x-summary-it": "Elenca le categorie",
        "x-scope": "settings.read",
        "x-description-it": "Le categorie configurate dall'officina, da usare per\nvalorizzare `work_category_id`. È una voce di Impostazioni, quindi\nrichiede il permesso `settings.read`.\n",
        "description": "The company's work-category catalog behind the worksheet and\nquote forms. Intentionally unpaginated: the set is small and\ncompany-curated. `has_more` is always `false`.\n",
        "responses": {
          "200": {
            "description": "Work categories.",
            "x-description-it": "Le categorie configurate.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkCategory"
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    },
                    {
                      "object": "work_category",
                      "id": "5f2b7d94-1a63-48ec-9207-8d4e3c1f6a52",
                      "name": "Freni"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/work-categories"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Settings"
        ],
        "summary": "Create a work category",
        "x-summary-it": "Crea una categoria",
        "x-scope": "settings.write",
        "x-description-it": "Crea una categoria, usata per classificare schede lavoro,\npreventivi e articoli. Il nome è salvato esattamente come inviato (senza\nspazi iniziali e finali). I nomi sono unici per officina senza\ndistinguere maiuscole e minuscole: se esiste già una\ncategoria con lo stesso nome, la risposta è `200` con quella\nesistente invece di crearne un doppione.\n",
        "description": "Creates a work category. The name is stored exactly as sent\n(trimmed). Names are unique per company ignoring case: when one\nwith the same name already exists the response is `200` with that\ncategory instead of a duplicate; a new one is `201`.\n",
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Un nome già esistente (senza distinguere maiuscole e minuscole) restituisce `200` con la categoria esistente invece di crearne un doppione.",
            "en": "A name that already exists (ignoring case) returns `200` with the existing category instead of creating a duplicate."
          }
        ],
        "operationId": "workCategoryCreate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "name": "Nuovo nome"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A category with the same name (ignoring case) already existed; it is returned unchanged.",
            "x-description-it": "Esisteva già una categoria con lo stesso nome: viene restituito quello.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "work_category",
                  "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "name": "Tagliando"
                }
              }
            }
          },
          "201": {
            "description": "success",
            "x-description-it": "La categoria creata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "work_category",
                  "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "name": "Tagliando"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/work-categories/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Settings"
        ],
        "summary": "Delete work category",
        "x-summary-it": "Elimina una categoria",
        "x-scope": "settings.write",
        "x-description-it": "Elimina la categoria. I documenti e gli articoli che la citano\nrestano intatti.\n",
        "operationId": "workCategoryDelete",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "La categoria eliminata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "work_category",
                  "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "name": "Tagliando"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "tags": [
          "Settings"
        ],
        "summary": "Update work category",
        "x-summary-it": "Aggiorna una categoria",
        "x-scope": "settings.write",
        "x-description-it": "Aggiorna il nome della categoria. Se un'altra\ncategoria ha già quel nome (senza distinguere maiuscole e\nminuscole) la risposta è `409 conflict`.\n",
        "description": "Renames the work category. When another category already has that\nname (ignoring case) the response is `409 conflict`.\n",
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Rinominare con un nome già usato da un'altra categoria restituisce `409 conflict`.",
            "en": "Renaming onto a name another category already has returns `409 conflict`."
          }
        ],
        "operationId": "workCategoryPatch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "name": "Nuovo nome"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "La categoria aggiornata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "example": {
                  "object": "work_category",
                  "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "name": "Tagliando"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Worksheets"
        ],
        "summary": "List worksheets",
        "x-summary-it": "Elenca le schede lavoro",
        "x-scope": "worksheets.read",
        "x-description-it": "Le schede lavoro dell'officina, dalla più recente. Filtra per stato\ncon `status`, per cliente con `customer_id`, per veicolo con\n`vehicle_id` e per periodo con `from` / `to`. La paginazione usa un\ncursore opaco: passa `next_starting_after` invariato in\n`starting_after` e fermati quando `has_more` è `false`.\n",
        "description": "Returns worksheets in the authenticated user's company, newest\nfirst. Filter by status with `?status=`, by customer with\n`?customer_id=`, search customer names with `?q=`, and bound by\n`created_at` with `?from=` / `?to=` / `?preset=`. Paginate with\ncursor-based `?limit=` + `?starting_after=`.\n\nThe date window defaults to the last 12 months; set it with\n`from`/`to`/`preset`. The applied range is echoed in\n`date_filter`. Pass `?preset=all`\nto list across all time.\n\nLine items are **not** included in the list shape — fetch the\ndetail endpoint when full line-item data is needed.\n\nThe cursor is opaque — clients MUST NOT parse it. Pass back\nthe value verbatim.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on the customer's full name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter to a single lifecycle state. Sending an unrecognized\nvalue returns 422 instead of silently coercing.\n",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "waiting",
                "started",
                "completed",
                "delivered",
                "archived"
              ]
            }
          },
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "description": "Restrict results to a single customer's worksheets.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Sort key (default `date` = creation date). Named keys are\nmapped server-side to whitelisted columns.\n",
            "schema": {
              "type": "string",
              "enum": [
                "date",
                "number",
                "customer",
                "vehicle",
                "total",
                "outstanding"
              ]
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Sort direction (default `desc`).",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on `created_at` (YYYY-MM-DD).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "$ref": "#/components/parameters/WorkCategoryFilter"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, max 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Opaque cursor from a prior response. Omit on the first page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page of worksheets (without line items).",
            "x-description-it": "Una pagina di schede lavoro.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "date_filter",
                        "work_category_filter"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Worksheet"
                          }
                        },
                        "date_filter": {
                          "$ref": "#/components/schemas/DateFilter"
                        },
                        "work_category_filter": {
                          "$ref": "#/components/schemas/WorkCategoryFilter"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "worksheet",
                      "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "seq_number": 412,
                      "status": "started",
                      "title": "Tagliando 60.000 km",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "customer_name": "Mario Rossi",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "vehicle_name": "Fiat Panda",
                      "vehicle_license": "AB123CD",
                      "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "work_category_name": "Tagliando",
                      "work_category_ids": [
                        "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                      ],
                      "work_categories": [
                        {
                          "object": "work_category",
                          "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                          "name": "Tagliando"
                        }
                      ],
                      "km": 61240,
                      "total_before_tax": 233.9,
                      "total_tax": 51.46,
                      "total_after_tax": 285.36,
                      "total_discount": 0,
                      "total_payments": 150.0,
                      "outstanding_payments": 135.36,
                      "fiscal_document_type": "fattura",
                      "frozen": false,
                      "invoice_id": null,
                      "receipt_id": null,
                      "started_at": "2026-07-15T08:30:00Z",
                      "completed_at": null,
                      "delivered_at": null,
                      "created_at": "2026-07-15T08:12:00Z",
                      "updated_at": "2026-07-16T10:02:00Z"
                    }
                  ],
                  "has_more": true,
                  "next_starting_after": "eyJvIjoyMH0",
                  "url": "/v1/worksheets",
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids` per impostare più categorie (massimo 10); la risposta include `work_category_ids` e `work_categories`. `work_category_id` resta valido con il significato di sempre.",
            "en": "Accepts `work_category_ids` to set several work categories (at most 10); the response includes `work_category_ids` and `work_categories`. `work_category_id` still works with its usual meaning."
          }
        ],
        "tags": [
          "Worksheets"
        ],
        "summary": "Create a worksheet",
        "x-summary-it": "Crea una scheda lavoro",
        "x-scope": "worksheets.write",
        "x-description-it": "Crea una scheda lavoro in stato `draft`. Il numero progressivo\n(`seq_number`) è assegnato dal server. Le righe non si passano qui:\ncrea la scheda, poi aggiungile con\n`POST /v1/worksheets/{id}/line-items/batch`.\n",
        "description": "Creates a new worksheet, owned by the authenticated user's\ncompany. Server-controlled fields (`id`, `company_id`,\n`created_by`, `seq_number`, `status`, all `*_at` timestamps,\ntotals) are filled in automatically. Status always starts\nas `draft`.\n\n`customer_id` is required. Line items are not accepted at\ncreation time — a dedicated sub-resource will add/remove\nthem in a later pass.\n\nForeign-key references in the body (`customer_id`, `vehicle_id`,\n`work_category_id`, `assigned_to`) must resolve to resources\nowned by the authenticated user's company. Unreachable refs\nreturn 422 `parameter_invalid` pointing at the offending field.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorksheetCreateRequest"
              },
              "example": {
                "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                "title": "Tagliando 60.000 km",
                "km": 61240
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Worksheet created. Returns the fully-materialized resource.",
            "x-description-it": "La scheda lavoro creata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "started",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_discount": 0,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "fiscal_document_type": "fattura",
                  "frozen": false,
                  "invoice_id": null,
                  "receipt_id": null,
                  "started_at": "2026-07-15T08:30:00Z",
                  "completed_at": null,
                  "delivered_at": null,
                  "created_at": "2026-07-15T08:12:00Z",
                  "updated_at": "2026-07-16T10:02:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/status-counts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Count worksheets per lifecycle state",
        "x-summary-it": "Conta le schede per stato",
        "x-scope": "worksheets.read",
        "x-description-it": "Il numero di schede in ciascuno stato, con gli stessi filtri\ndell'elenco. Serve a popolare i contatori senza scaricare le schede.\n",
        "description": "Returns how many worksheets sit in each status, for the tab\ncounters above the mobile list. Accepts the same filters as\n`GET /v1/worksheets` minus `status` and pagination (`q`,\n`customer_id`, `from`/`to`/`preset` with the same date-filter\nsemantics), so the numbers always match what the\nlist shows. The applied range is echoed in `date_filter`.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match against customer name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "$ref": "#/components/parameters/DatePreset"
          },
          {
            "$ref": "#/components/parameters/WorkCategoryFilter"
          }
        ],
        "responses": {
          "200": {
            "description": "Counts per status.",
            "x-description-it": "Il numero di schede per stato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorksheetStatusCounts"
                },
                "example": {
                  "object": "worksheet_status_counts",
                  "draft": 3,
                  "waiting": 5,
                  "started": 2,
                  "completed": 8,
                  "delivered": 41,
                  "archived": 120,
                  "date_filter": {
                    "object": "date_filter",
                    "preset": "year",
                    "from": "2026-01-01",
                    "to": "2026-12-31"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Worksheet id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Worksheets"
        ],
        "summary": "Retrieve a worksheet",
        "x-summary-it": "Recupera una scheda lavoro",
        "x-scope": "worksheets.read",
        "x-description-it": "Restituisce la scheda con le righe (`line_items`), i documenti\nallegati e i totali calcolati. `frozen` indica che la scheda ha già\ngenerato un documento fiscale e non è più modificabile;\n`linked_invoices` e `linked_receipt` puntano ai documenti emessi.\n",
        "description": "Returns a single worksheet (with embedded line items) by id,\nscoped to the authenticated user's company. Unknown ids, ids\nfrom another company, and soft-deleted rows all return 404 —\nnever 403.\n",
        "responses": {
          "200": {
            "description": "Worksheet found. Includes `line_items`.",
            "x-description-it": "La scheda lavoro richiesta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "started",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_discount": 0,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "fiscal_document_type": "fattura",
                  "frozen": false,
                  "invoice_id": null,
                  "receipt_id": null,
                  "started_at": "2026-07-15T08:30:00Z",
                  "completed_at": null,
                  "delivered_at": null,
                  "created_at": "2026-07-15T08:12:00Z",
                  "updated_at": "2026-07-16T10:02:00Z",
                  "comments": "Sostituire anche il filtro abitacolo.",
                  "line_items": [
                    {
                      "object": "work_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "article_id": null,
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28,
                      "created_at": "2026-07-15T08:31:00Z"
                    },
                    {
                      "object": "work_line_item",
                      "id": "0b4a2d6f-1c3e-4f5a-8b9c-7d6e5f4a3b2c",
                      "article_id": null,
                      "description": "Manodopera tagliando",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 79.95,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 159.9,
                      "total_tax": 35.18,
                      "total_after_tax": 195.08,
                      "position": 1,
                      "created_at": "2026-07-15T08:31:00Z"
                    }
                  ],
                  "documents": []
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "patch": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          },
          {
            "since": "2026-10-03",
            "it": "Accetta `work_category_ids`: sostituisce l'intero elenco delle categorie (`[]` o `null` li rimuove tutti). `work_category_id` continua a sostituire solo il primo dell'elenco. La risposta include `work_category_ids` e `work_categories`.",
            "en": "Accepts `work_category_ids`: replaces the whole work-category list (`[]` or `null` removes them all). `work_category_id` still replaces only the first in the list. The response includes `work_category_ids` and `work_categories`."
          }
        ],
        "tags": [
          "Worksheets"
        ],
        "summary": "Update a worksheet",
        "x-summary-it": "Aggiorna una scheda lavoro",
        "x-scope": "worksheets.write",
        "x-description-it": "Aggiorna solo i campi presenti nel corpo: gli altri restano\ninvariati, e `null` svuota il campo. Una scheda `frozen` (già\nfatturata) rifiuta la modifica con `409`.\n",
        "description": "Partial update following the standard three-state PATCH\nsemantics (omitted = unchanged, `null` = clear, value = set).\n\nOnly top-level editable fields are exposed here. Use\n`POST /v1/worksheets/{id}/transitions` to change `status`.\nLine-item mutation is out of scope for v1 — a dedicated\nsub-resource will land in a later pass.\n\nFields intentionally NOT patchable: `id`, `company_id`,\n`created_by`, `seq_number`, `status`, `customer_id`,\n`vehicle_id`, all totals, all `*_at` timestamps,\n`line_items`, `documents`.\n\nForeign-key references in the body (`work_category_id`,\n`assigned_to`) must resolve to resources owned by the\nauthenticated user's company. Unreachable refs return 422\n`parameter_invalid` pointing at the offending field.\n\n**Frozen worksheets** — when the worksheet's linked invoice\nhas been issued, the worksheet is frozen: its totals are\nnow part of the invoice's accounting record and must not\ndesync. PATCH returns 409 `conflict`. Cancel the invoice\nwith a credit note before editing.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorksheetPatchRequest"
              },
              "example": {
                "title": "Tagliando 60.000 km — completato",
                "km": 61310,
                "internal_comments": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Worksheet updated. Returns the full post-update entity.",
            "x-description-it": "La scheda lavoro aggiornata.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "started",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_discount": 0,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "fiscal_document_type": "fattura",
                  "frozen": false,
                  "invoice_id": null,
                  "receipt_id": null,
                  "started_at": "2026-07-15T08:30:00Z",
                  "completed_at": null,
                  "delivered_at": null,
                  "created_at": "2026-07-15T08:12:00Z",
                  "updated_at": "2026-07-16T10:02:00Z",
                  "comments": "Sostituire anche il filtro abitacolo.",
                  "line_items": [
                    {
                      "object": "work_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "article_id": null,
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28,
                      "created_at": "2026-07-15T08:31:00Z"
                    },
                    {
                      "object": "work_line_item",
                      "id": "0b4a2d6f-1c3e-4f5a-8b9c-7d6e5f4a3b2c",
                      "article_id": null,
                      "description": "Manodopera tagliando",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 79.95,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 159.9,
                      "total_tax": 35.18,
                      "total_after_tax": 195.08,
                      "position": 1,
                      "created_at": "2026-07-15T08:31:00Z"
                    }
                  ],
                  "documents": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Soft-delete a worksheet",
        "x-summary-it": "Elimina una scheda lavoro",
        "x-scope": "worksheets.write",
        "x-description-it": "Eliminazione logica. Una scheda che ha già generato una fattura o\nuno scontrino non può essere eliminata: la richiesta risponde `409`\nfinché il documento fiscale esiste.\n",
        "description": "Marks the worksheet as deleted by stamping `deleted_at` with\nthe server clock. The row stays in the database; subsequent\nGET/PATCH/DELETE all 404.\n\nReturns the deleted resource (200) rather than 204 No Content\nfor shape consistency with the rest of the API.\n\n**Frozen worksheets** — refuses to soft-delete when the\nworksheet's linked invoice has been issued (would orphan\nthe invoice's basis). Returns 409 `conflict`. Cancel the\ninvoice with a credit note first.\n",
        "responses": {
          "200": {
            "description": "Worksheet soft-deleted. `deleted_at` is populated.",
            "x-description-it": "Conferma di eliminazione.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "started",
                  "title": "Tagliando 60.000 km",
                  "deleted_at": "2026-07-20T11:04:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/by-number/{year}/{number}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "year",
          "in": "path",
          "required": true,
          "description": "Year the number belongs to (the year of the document date).",
          "schema": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100
          },
          "example": 2026
        },
        {
          "name": "number",
          "in": "path",
          "required": true,
          "description": "The document number (`seq_number`).",
          "schema": {
            "type": "integer",
            "minimum": 1
          },
          "example": 12
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-04",
            "it": "Nuovo: recupera la scheda lavoro dal numero e dall'anno.",
            "en": "New: retrieve a worksheet by its number and year."
          }
        ],
        "tags": [
          "Worksheets"
        ],
        "summary": "Retrieve a worksheet by number",
        "x-summary-it": "Recupera la scheda lavoro dal numero",
        "x-scope": "worksheets.read",
        "x-description-it": "Restituisce la scheda lavoro con il numero `number` dell'anno `year` (anno della data della scheda): lo stesso numero mostrato nell'app, per esempio 12/2026.\n\nRisponde come il recupero per id. Se il numero corrisponde a più documenti — numeri ripetuti nello storico — restituisce `409 ambiguous_number` con gli id in `candidates`.\n",
        "description": "Returns the worksheet carrying number `number` in year `year` (the year of its document date) — the number shown in the app, e.g. 12/2026.\n\nThe response is identical to `GET /v1/worksheets/{id}`. A number that matches more than one document (repeated in historical data) returns 409 `ambiguous_number` with their ids in `candidates`.\n",
        "responses": {
          "200": {
            "description": "The worksheet, exactly as retrieving it by id returns it.",
            "x-description-it": "La scheda lavoro, con la stessa risposta del recupero per id.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "started",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_discount": 0,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "fiscal_document_type": "fattura",
                  "frozen": false,
                  "invoice_id": null,
                  "receipt_id": null,
                  "started_at": "2026-07-15T08:30:00Z",
                  "completed_at": null,
                  "delivered_at": null,
                  "created_at": "2026-07-15T08:12:00Z",
                  "updated_at": "2026-07-16T10:02:00Z",
                  "comments": "Sostituire anche il filtro abitacolo.",
                  "line_items": [
                    {
                      "object": "work_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "article_id": null,
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28,
                      "created_at": "2026-07-15T08:31:00Z"
                    },
                    {
                      "object": "work_line_item",
                      "id": "0b4a2d6f-1c3e-4f5a-8b9c-7d6e5f4a3b2c",
                      "article_id": null,
                      "description": "Manodopera tagliando",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 79.95,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 159.9,
                      "total_tax": 35.18,
                      "total_after_tax": 195.08,
                      "position": 1,
                      "created_at": "2026-07-15T08:31:00Z"
                    }
                  ],
                  "documents": []
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/AmbiguousNumber"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/checkins": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Worksheet id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-03",
            "it": "Nuovi campi `work_category_ids` e `work_categories`: tutte le categorie del documento, nell'ordine scelto (`work_category_id` riporta il primo, per i client che ne gestiscono uno solo).",
            "en": "New fields `work_category_ids` and `work_categories`: every work category of the document, in the order chosen (`work_category_id` reports the first, for clients that handle a single one)."
          }
        ],
        "tags": [
          "Worksheets"
        ],
        "summary": "Accettazioni linked to a scheda lavoro",
        "x-summary-it": "Accettazioni collegate alla scheda",
        "x-scope": "checkins.read",
        "x-description-it": "Le accettazioni da cui la scheda è stata generata. Restituisce\naccettazioni, quindi richiede il permesso `checkins.read` e non\n`worksheets.read`.\n",
        "description": "Every check-in whose `worksheet_id` points at this scheda (set by\n`POST /v1/checkins/{id}/convert` and the web conversion). Newest\nfirst, without line items. Unpaginated (`has_more` always false).\n",
        "responses": {
          "200": {
            "description": "Linked accettazioni.",
            "x-description-it": "Le accettazioni collegate alla scheda.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Checkin"
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "checkin",
                      "id": "1c7a9e04-5f83-4b26-8d10-6e2b4a9c7f31",
                      "status": "convertita",
                      "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                      "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                      "created_at": "2026-07-15T07:55:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/worksheets/a5d21e64-8f37-4b90-ae12-3c7d6b048f95/checkins"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Create a worksheet document",
        "x-summary-it": "Allega un documento alla scheda",
        "x-scope": "worksheets.write",
        "x-description-it": "Carica un file fra i documenti della scheda. La richiesta è\n`multipart/form-data` con il file nel campo `file`, non JSON.\n",
        "operationId": "worksheetDocumentCreate",
        "description": "multipart/form-data:\n\nfile         required — the document bytes (max 25MB)\ndescription  optional — display label; defaults to the file name\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "description": {
                    "type": "string",
                    "description": "Display label; defaults to the file name."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "success",
            "x-description-it": "Il documento allegato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorksheetDocument"
                },
                "example": {
                  "object": "worksheet_document",
                  "id": "6b3e9f10-2c84-4d57-91ab-5e8f0a2d7c63",
                  "name": "preventivo-firmato.pdf",
                  "content_type": "application/pdf",
                  "description": "Preventivo firmato",
                  "file_size_bytes": 92418,
                  "url": "/v1/worksheets/a5d21e64-8f37-4b90-ae12-3c7d6b048f95/documents/6b3e9f10-2c84-4d57-91ab-5e8f0a2d7c63/download",
                  "created_at": "2026-07-15T09:40:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/documents/{documentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "delete": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Delete worksheet document",
        "x-summary-it": "Elimina un documento della scheda",
        "x-scope": "worksheets.write",
        "x-description-it": "Rimuove definitivamente il file allegato.\n",
        "operationId": "worksheetDocumentDelete",
        "description": "WorksheetDocumentDelete handles\nDELETE /v1/worksheets/{id}/documents/{documentID}.\n",
        "responses": {
          "200": {
            "description": "success (the removed document)",
            "x-description-it": "Conferma di eliminazione.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorksheetDocument"
                },
                "example": {
                  "object": "worksheet_document",
                  "id": "6b3e9f10-2c84-4d57-91ab-5e8f0a2d7c63",
                  "name": "preventivo-firmato.pdf",
                  "content_type": "application/pdf",
                  "file_size_bytes": 92418,
                  "created_at": "2026-07-15T09:40:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/documents/{documentID}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        },
        {
          "name": "documentID",
          "in": "path",
          "required": true,
          "description": "Identifier of the target resource.",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Download worksheet document",
        "x-summary-it": "Scarica un documento della scheda",
        "x-scope": "worksheets.read",
        "x-description-it": "Restituisce il contenuto del file, non JSON. È il percorso indicato\ndal campo `url` di ogni documento.\n",
        "operationId": "worksheetDocumentDownload",
        "description": "WorksheetDocumentDownload handles\nGET /v1/worksheets/{id}/documents/{documentID}/download.\n",
        "responses": {
          "200": {
            "description": "success",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/waste-disposal": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Worksheet id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Preview the waste-disposal charge",
        "x-summary-it": "Anteprima smaltimento rifiuti",
        "x-scope": "worksheets.read",
        "x-changes": [
          {
            "since": "2026-10-06",
            "it": "Nuovo endpoint.",
            "en": "New endpoint."
          }
        ],
        "x-description-it": "Dice se lo smaltimento rifiuti si può applicare alla scheda lavoro e quanto\nvale (importo netto, prima dell'IVA), secondo le impostazioni\n`waste_disposal_*` dell'officina. `reason` spiega perché non è\napplicabile: `disabled`, `already_applied`, `nothing_to_charge`,\n`fully_paid` (scheda già interamente pagata) o `frozen`.\n",
        "description": "Reports whether the \"smaltimento rifiuti\" charge can be applied and\nits net amount, computed from the company's `waste_disposal_*`\nsettings: `rate`% of the net of the goods lines (`base: articles`)\nor of every line (`base: total`), rounded to cents.\n\n`reason` (null when applicable): `disabled` — off or no rate;\n`already_applied` — a line already carries the configured or the\ndefault description; `nothing_to_charge` — the amount rounds to 0.\n`frozen`: the worksheet is behind an issued fiscal document (POST\nanswers `409 conflict`); `fully_paid`: the worksheet is already paid\nin full (partially paid and unpaid ones can take the charge).\n",
        "operationId": "worksheetWasteDisposalPreview",
        "responses": {
          "200": {
            "description": "success",
            "x-description-it": "L'anteprima dell'addebito.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WasteDisposalPreview"
                },
                "example": {
                  "object": "waste_disposal_preview",
                  "applicable": true,
                  "reason": null,
                  "rate": 1.2,
                  "base": "articles",
                  "description": "Smaltimento rifiuti",
                  "amount": 2.5
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Apply the waste-disposal charge",
        "x-summary-it": "Applica smaltimento rifiuti",
        "x-scope": "worksheets.write",
        "x-changes": [
          {
            "since": "2026-10-06",
            "it": "Nuovo endpoint.",
            "en": "New endpoint."
          }
        ],
        "x-description-it": "Aggiunge la riga di smaltimento rifiuti (quantità 1, unità `lav`,\naliquota IVA predefinita dell'officina, in fondo al documento),\nricalcola i totali e restituisce il documento aggiornato. La riga\nsi aggiunge una sola volta: se non è applicabile la risposta è\n`409 waste_disposal_not_applicable` con il motivo in `error.reason`\n(anche `fully_paid` se la scheda è già interamente pagata). Una\nscheda collegata a un documento fiscale già emesso risponde `409 conflict`.\n",
        "description": "Appends the charge line — quantity 1 at the previewed amount, unit\n`lav`, the company's default VAT rate (its first enabled one), last\nposition — recomputes the totals and returns the updated document\n(as the line-items batch does).\n\nThe rules are re-checked server-side and the line is written in one\ntransaction holding the document's row lock, so concurrent calls run\none after the other and add the line once. When the charge can't be\napplied the response is `409 waste_disposal_not_applicable` with\n`error.reason` (`disabled`, `already_applied`, `nothing_to_charge`, and\non worksheets `fully_paid`). A worksheet behind an issued fiscal document answers `409 conflict`.\n",
        "operationId": "worksheetWasteDisposalApply",
        "responses": {
          "200": {
            "description": "Line added. Returns the updated document (with `line_items`).",
            "x-description-it": "Il documento con la riga aggiunta e i totali ricalcolati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`waste_disposal_not_applicable` (`error.reason` says why) or\n`conflict` (the worksheet is frozen).\n",
            "x-description-it": "Non applicabile (motivo in `error.reason`) oppure scheda bloccata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "https://api.officina.it/errors/waste_disposal_not_applicable",
                    "status": 409,
                    "code": "waste_disposal_not_applicable",
                    "message": "Lo smaltimento rifiuti è già presente su questo documento.",
                    "reason": "already_applied",
                    "trace_id": "abc-123"
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/line-items/batch": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Worksheet id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "x-changes": [
          {
            "since": "2026-10-22",
            "it": "`If-Match` obbligatorio da questa revisione: senza l'header la richiesta è rifiutata con 428 `precondition_required` (`If-Match: *` scrive comunque). Con una `version` superata risponde 409.",
            "en": "`If-Match` required from this revision: without the header the request is refused with 428 `precondition_required` (`If-Match: *` writes anyway). A superseded `version` answers 409."
          }
        ],
        "tags": [
          "Worksheets"
        ],
        "summary": "Mutate worksheet line items in a batch",
        "x-summary-it": "Modifica le righe in blocco",
        "x-scope": "worksheets.write",
        "x-description-it": "Aggiunge, aggiorna, rimuove e riordina le righe in una sola\nrichiesta, applicata come singola transazione: se una operazione\nfallisce non viene applicata nessuna. I totali della scheda vengono\nricalcolati e restituiti. Collega una riga al magazzino valorizzando\n`article_id`; lasciandolo `null` la riga resta libera.\n",
        "description": "Adds, updates, removes, and reorders the worksheet's line items\nin a single atomic diff, then returns the full worksheet with\nrecomputed totals and the updated `line_items` array. The\ntop-level worksheet PATCH is metadata-only; all line-item\nmutation flows through this endpoint.\n\n**Operations** (all arrays optional; empty body is a no-op):\n`add` creates new lines, `update` replaces an existing line's\nfields by id, `remove` deletes lines by id, `reorder` sets the\nposition of existing untouched lines. An id may appear in at\nmost one of `remove`/`update`/`reorder`.\n\n**Update is remove+add internally** so article inventory stock\nreconciles through the normal path; an updated line therefore\nreceives a NEW id in the response — re-read ids from the\nreturned `line_items` rather than assuming stability.\n\n**`tax_rate` is server-resolved** from each line's `vat_code`;\nclients never send rates or totals.\n\n**Frozen worksheets** — blocked when the worksheet's linked\ninvoice has been issued (mutating lines would desync invoice\ntotals). Returns 409 `conflict`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LineItemsBatchRequest"
              },
              "example": {
                "add": [
                  {
                    "description": "Brake pads (front)",
                    "quantity": 1,
                    "unit_type": "kit",
                    "unit_price": 89.9,
                    "unit_discount_rate": 0,
                    "vat_code": "22",
                    "article_id": null
                  }
                ],
                "update": [
                  {
                    "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                    "description": "Labour — brake service",
                    "quantity": 1.5,
                    "unit_type": "pz",
                    "unit_price": 40,
                    "unit_discount_rate": 10,
                    "vat_code": "22"
                  }
                ],
                "remove": [
                  "0b4a2d6f-1c3e-4f5a-8b9c-7d6e5f4a3b2c"
                ],
                "reorder": [
                  {
                    "id": "9d8c7b6a-5f4e-3d2c-1b0a-9e8d7c6b5a40",
                    "position": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch applied. Returns the updated worksheet (with `line_items`).",
            "x-description-it": "La scheda con le righe aggiornate e i totali ricalcolati.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "started",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_discount": 0,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "fiscal_document_type": "fattura",
                  "frozen": false,
                  "invoice_id": null,
                  "receipt_id": null,
                  "started_at": "2026-07-15T08:30:00Z",
                  "completed_at": null,
                  "delivered_at": null,
                  "created_at": "2026-07-15T08:12:00Z",
                  "updated_at": "2026-07-16T10:02:00Z",
                  "comments": "Sostituire anche il filtro abitacolo.",
                  "line_items": [
                    {
                      "object": "work_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "article_id": null,
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28,
                      "created_at": "2026-07-15T08:31:00Z"
                    },
                    {
                      "object": "work_line_item",
                      "id": "0b4a2d6f-1c3e-4f5a-8b9c-7d6e5f4a3b2c",
                      "article_id": null,
                      "description": "Manodopera tagliando",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 79.95,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 159.9,
                      "total_tax": 35.18,
                      "total_after_tax": 195.08,
                      "position": 1,
                      "created_at": "2026-07-15T08:31:00Z"
                    }
                  ],
                  "documents": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "428": {
            "$ref": "#/components/responses/PreconditionRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/payments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "get": {
        "tags": [
          "Worksheets"
        ],
        "summary": "List a worksheet's payments",
        "x-summary-it": "Elenca i pagamenti della scheda",
        "x-scope": "worksheets.read",
        "x-description-it": "Gli incassi registrati sulla scheda. Il residuo da incassare è\n`outstanding_payments` sulla scheda stessa.\n",
        "description": "Payments registered against the worksheet (acconti/saldi).\nThey physically hang off the worksheet's invoice, but the mobile\nAPI exposes them worksheet-scoped so clients never deal with the\ninvoice indirection. Unpaginated.\n",
        "responses": {
          "200": {
            "description": "Payments, oldest first.",
            "x-description-it": "I pagamenti della scheda.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "url"
                  ],
                  "properties": {
                    "object": {
                      "type": "string",
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Payment"
                      }
                    },
                    "has_more": {
                      "type": "boolean",
                      "const": false
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "object": "payment",
                      "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                      "amount": 150.0,
                      "payment_method": "MP01",
                      "payment_date": "2026-07-16",
                      "created_at": "2026-07-16T10:02:00Z"
                    }
                  ],
                  "has_more": false,
                  "url": "/v1/worksheets/a5d21e64-8f37-4b90-ae12-3c7d6b048f95/payments"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Register a payment on a worksheet",
        "x-summary-it": "Registra un pagamento",
        "x-scope": "worksheets.write",
        "x-description-it": "Registra un incasso sulla scheda. `payment_method` usa i codici\nministeriali (`MP01` contanti, `MP02` assegno, `MP05` bonifico,\n`MP08` carta). L'importo può essere parziale: registra più pagamenti\nfino a coprire il totale.\n",
        "description": "Mirrors the web worksheet flow: the payment attaches to the\nworksheet's invoice — a draft is created from the worksheet when\nnone exists yet — and the invoice paid-state is refreshed.\n`payment_method` falls back to the customer's preferred method,\nthen the company default, when missing or invalid.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PaymentCreateRequest"
              },
              "example": {
                "amount": 150.0,
                "payment_method": "MP01",
                "payment_date": "2026-07-16"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment registered.",
            "x-description-it": "Il pagamento registrato.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "object": "payment",
                  "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                  "amount": 150.0,
                  "payment_method": "MP01",
                  "payment_date": "2026-07-16",
                  "created_at": "2026-07-16T10:02:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/payments/{paymentID}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        },
        {
          "name": "paymentID",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "delete": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Remove a payment from a worksheet",
        "x-summary-it": "Elimina un pagamento",
        "x-scope": "worksheets.write",
        "x-description-it": "Rimuove l'incasso e riporta il residuo della scheda al valore\nprecedente.\n",
        "description": "Hard-deletes the payment (payments have no soft-delete) and\nrefreshes the invoice paid-state. The payment must belong to\nthe worksheet — otherwise 404.\n",
        "responses": {
          "200": {
            "description": "The removed payment.",
            "x-description-it": "Conferma di eliminazione.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                },
                "example": {
                  "object": "payment",
                  "id": "d47c0b58-3e91-42a6-8f05-1b9d6a3e2c74",
                  "amount": 150.0,
                  "payment_method": "MP01",
                  "payment_date": "2026-07-16",
                  "created_at": "2026-07-16T10:02:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/time": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Worksheet id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "x-changes": [
          {
            "since": "2026-10-25",
            "it": "Nuovo endpoint: il Marcatempo di una scheda lavoro, con ore lavorate, ore fatturate, differenza e resa.",
            "en": "New endpoint: a worksheet's Marcatempo, with hours worked, hours billed, difference and resa."
          }
        ],
        "tags": [
          "TimeTracking"
        ],
        "summary": "Retrieve a worksheet's times",
        "x-summary-it": "Recupera i tempi di una scheda",
        "x-scope": "worksheets.read",
        "operationId": "worksheetTimesGet",
        "x-description-it": "La scheda Marcatempo di una scheda lavoro, come la vede il titolare:\ntutti i tempi registrati (prima i timer accesi, poi dal più recente),\ni timer accesi dei collaboratori e il riepilogo.\n\nIl riepilogo (`summary`) confronta le ore lavorate con le ore\nfatturate in manodopera: righe in ore (`h`), minuti (`min`) o giorni\n(`gg`, con le ore di una giornata di `day_hours`). Le righe a corpo\n(`lav`) non hanno un tempo misurabile e sono contate a parte in\n`flat_rate_lines`. `difference_minutes` è fatturato meno lavorato,\n`resa_percent` il fatturato sul lavorato (`null` prima di un minuto\ndi lavoro), `labor_revenue` e `revenue_per_hour` il ricavo della\nmanodopera, imponibile, e il ricavo per ora lavorata. Il tempo di\nattesa (`wait_minutes`) non conta mai come lavorato. `people` riporta\nle ore per collaboratore con la quota sul totale, `type_totals` le\nore per tipo, `billed_lines` le righe di manodopera usate per il\nfatturato.\n\nCon l'API la chiave agisce come il titolare: la risposta è sempre il\nquadro completo (`full_picture: true`) ed è in sola lettura, quindi\n`timers` e `assignees` sono sempre vuoti, `can_add`, `can_add_wait`\ne i flag `can_*` dei tempi sempre `false`, e `colleagues` elenca ogni\ntimer di lavoro in corso sulla scheda, compreso quello del titolare.\nAvviare, fermare e correggere i tempi resta nell'app.\n`locked: true` indica che il piano non include la registrazione dei\ntempi; quanto registrato resta leggibile.\n\nServe il modulo Marcatempo attivo (`403` altrimenti). Una scheda\neliminata o di un'altra officina risponde `404`.\n",
        "description": "The worksheet's Marcatempo tab as the titolare sees it: every time\nrecorded (running timers first, then newest start first), the\ncolleagues' running timers and the summary.\n\n`summary` compares hours worked with the labor hours billed: lines\nin hours (`h`), minutes (`min`) or days (`gg`, at `day_hours` per\nday). Flat-rate lines (`lav`) have no measurable time and are only\ncounted in `flat_rate_lines`. `difference_minutes` is billed minus\nworked, `resa_percent` billed over worked (`null` before a minute of\nwork), `labor_revenue` and `revenue_per_hour` the net labor revenue\nand the revenue per worked hour. Attesa time (`wait_minutes`) never\ncounts as worked. `people` holds the hours per person with their\nshare, `type_totals` the hours per type, `billed_lines` the labor\nlines behind the billed hours.\n\nOn the public API the key acts as the titolare, so the answer is\nalways the full picture (`full_picture: true`), and it is read-only:\n`timers` and `assignees` are always empty, `can_add`, `can_add_wait`\nand every time's `can_*` flags always `false`, and `colleagues` lists\nevery running Lavoro timer on the worksheet, the titolare's included.\nStarting, stopping and correcting times stays in the app.\n`locked: true` means the plan does not include recording times;\nwhat was recorded stays readable.\n\nNeeds the Marcatempo module on (`403` otherwise). A deleted\nworksheet, or one of another company, answers `404`.\n",
        "responses": {
          "200": {
            "description": "The worksheet's times and summary.",
            "x-description-it": "I tempi della scheda con il riepilogo.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorksheetTimes"
                },
                "example": {
                  "object": "worksheet_times",
                  "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "now": "2026-10-23T10:30:00Z",
                  "closed": false,
                  "locked": false,
                  "full_picture": true,
                  "can_add": false,
                  "can_add_wait": false,
                  "day_hours": 8,
                  "notice": null,
                  "time_types": [
                    {
                      "object": "time_type",
                      "id": "3b8e2f71-6c4a-4d19-9e05-7a1c2d3e4f60",
                      "name": "Lavorazione",
                      "is_wait": false,
                      "position": 0
                    },
                    {
                      "object": "time_type",
                      "id": "7f4c1b98-2d6e-4a35-9c81-0e5b3a7d2f14",
                      "name": "Attesa ricambi",
                      "is_wait": true,
                      "position": 1
                    }
                  ],
                  "timers": [],
                  "colleagues": [
                    {
                      "object": "time_entry",
                      "id": "9c2e7a51-0b4d-4f68-a3e9-1d7c5b2f8a40",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "time_type_id": "3b8e2f71-6c4a-4d19-9e05-7a1c2d3e4f60",
                      "user": {
                        "object": "time_person",
                        "id": "2f3e4d5c-6b7a-4980-8f1e-2d3c4b5a6978",
                        "first_name": "Marco",
                        "last_name": "Esposito"
                      },
                      "started_at": "2026-10-23T09:45:00Z",
                      "ended_at": null,
                      "duration_seconds": 2700,
                      "running": true,
                      "note": "",
                      "source": "timer",
                      "forgotten": false,
                      "suggested_stop_at": null,
                      "can_edit": false,
                      "can_stop": false,
                      "can_discard": false
                    }
                  ],
                  "entries": [
                    {
                      "object": "time_entry",
                      "id": "9c2e7a51-0b4d-4f68-a3e9-1d7c5b2f8a40",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "time_type_id": "3b8e2f71-6c4a-4d19-9e05-7a1c2d3e4f60",
                      "user": {
                        "object": "time_person",
                        "id": "2f3e4d5c-6b7a-4980-8f1e-2d3c4b5a6978",
                        "first_name": "Marco",
                        "last_name": "Esposito"
                      },
                      "started_at": "2026-10-23T09:45:00Z",
                      "ended_at": null,
                      "duration_seconds": 2700,
                      "running": true,
                      "note": "",
                      "source": "timer",
                      "forgotten": false,
                      "suggested_stop_at": null,
                      "can_edit": false,
                      "can_stop": false,
                      "can_discard": false
                    },
                    {
                      "object": "time_entry",
                      "id": "4e9a0c17-3b5d-4a82-9f6e-2c1b7d8e5a93",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "time_type_id": "7f4c1b98-2d6e-4a35-9c81-0e5b3a7d2f14",
                      "user": null,
                      "started_at": "2026-10-23T08:30:00Z",
                      "ended_at": "2026-10-23T09:30:00Z",
                      "duration_seconds": 3600,
                      "running": false,
                      "note": "Pastiglie in arrivo dal fornitore",
                      "source": "manual",
                      "forgotten": false,
                      "suggested_stop_at": null,
                      "can_edit": false,
                      "can_stop": false,
                      "can_discard": false
                    },
                    {
                      "object": "time_entry",
                      "id": "1f6b3d82-7a9c-4e05-b1d4-6e8a2c9f0b37",
                      "worksheet_id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                      "time_type_id": "3b8e2f71-6c4a-4d19-9e05-7a1c2d3e4f60",
                      "user": {
                        "object": "time_person",
                        "id": "2f3e4d5c-6b7a-4980-8f1e-2d3c4b5a6978",
                        "first_name": "Marco",
                        "last_name": "Esposito"
                      },
                      "started_at": "2026-10-23T07:00:00Z",
                      "ended_at": "2026-10-23T08:30:00Z",
                      "duration_seconds": 5400,
                      "running": false,
                      "note": "Smontaggio freni anteriori",
                      "source": "timer",
                      "forgotten": false,
                      "suggested_stop_at": null,
                      "can_edit": false,
                      "can_stop": false,
                      "can_discard": false
                    }
                  ],
                  "summary": {
                    "object": "time_summary",
                    "worked_minutes": 135,
                    "billed_minutes": 120,
                    "wait_minutes": 60,
                    "difference_minutes": -15,
                    "flat_rate_lines": 1,
                    "resa_percent": 89,
                    "labor_revenue": 125,
                    "revenue_per_hour": 55.56,
                    "includes_running": true
                  },
                  "people": [
                    {
                      "object": "time_person_total",
                      "user": {
                        "object": "time_person",
                        "id": "2f3e4d5c-6b7a-4980-8f1e-2d3c4b5a6978",
                        "first_name": "Marco",
                        "last_name": "Esposito"
                      },
                      "worked_minutes": 135,
                      "share_percent": 100,
                      "running": true
                    }
                  ],
                  "type_totals": [
                    {
                      "object": "time_type_total",
                      "time_type_id": "3b8e2f71-6c4a-4d19-9e05-7a1c2d3e4f60",
                      "total_minutes": 135,
                      "running": true
                    },
                    {
                      "object": "time_type_total",
                      "time_type_id": "7f4c1b98-2d6e-4a35-9c81-0e5b3a7d2f14",
                      "total_minutes": 60,
                      "running": false
                    }
                  ],
                  "billed_lines": [
                    {
                      "object": "time_billed_line",
                      "description": "Sostituzione pastiglie freni anteriori",
                      "quantity": 2,
                      "unit_type": "h",
                      "minutes": 120
                    },
                    {
                      "object": "time_billed_line",
                      "description": "Tagliando completo",
                      "quantity": 1,
                      "unit_type": "lav",
                      "minutes": null
                    }
                  ],
                  "assignees": []
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/TimeTrackingForbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/worksheets/{id}/transitions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Worksheet id (UUID).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "tags": [
          "Worksheets"
        ],
        "summary": "Apply a status transition",
        "x-summary-it": "Cambia stato alla scheda",
        "x-scope": "worksheets.write",
        "x-description-it": "Applica una transizione di stato e registra la data corrispondente\n(`started_at`, `completed_at`, `delivered_at`, `archived_at`). È il\nmodo corretto di far avanzare una scheda: non impostare `status` via\nPATCH, perché non aggiornerebbe le date.\n",
        "description": "Moves the worksheet between lifecycle states. Modeled as an\nRPC rather than a PATCH on `status` so the lifecycle is\nvisible in the URL surface and the server retains exclusive\ncontrol over which `*_at` columns get stamped.\n\nSupported `action` values mirror the entity status constants:\n`draft`, `waiting`, `started`, `completed`, `delivered`,\n`archived`. Any other value is rejected with 422.\n\n**Frozen worksheets** — transitions are blocked when the\nworksheet's linked invoice has been issued. A status flip\non a frozen worksheet could trigger downstream side effects\n(e.g. moving back to draft would unwind the basis the\ninvoice was minted from). Returns 409 `conflict`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorksheetTransitionRequest"
              },
              "example": {
                "action": "started"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Transition applied. Returns the post-transition entity.",
            "x-description-it": "La scheda dopo la transizione.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Worksheet"
                },
                "example": {
                  "object": "worksheet",
                  "id": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "seq_number": 412,
                  "status": "started",
                  "title": "Tagliando 60.000 km",
                  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
                  "customer_name": "Mario Rossi",
                  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
                  "vehicle_name": "Fiat Panda",
                  "vehicle_license": "AB123CD",
                  "work_category_id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                  "work_category_name": "Tagliando",
                  "work_category_ids": [
                    "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40"
                  ],
                  "work_categories": [
                    {
                      "object": "work_category",
                      "id": "3a8e5c21-9d76-4f03-b418-2c6a9e7d5b40",
                      "name": "Tagliando"
                    }
                  ],
                  "km": 61240,
                  "total_before_tax": 233.9,
                  "total_tax": 51.46,
                  "total_after_tax": 285.36,
                  "total_discount": 0,
                  "total_payments": 150.0,
                  "outstanding_payments": 135.36,
                  "fiscal_document_type": "fattura",
                  "frozen": false,
                  "invoice_id": null,
                  "receipt_id": null,
                  "started_at": "2026-07-15T08:30:00Z",
                  "completed_at": null,
                  "delivered_at": null,
                  "created_at": "2026-07-15T08:12:00Z",
                  "updated_at": "2026-07-16T10:02:00Z",
                  "comments": "Sostituire anche il filtro abitacolo.",
                  "line_items": [
                    {
                      "object": "work_line_item",
                      "id": "7c0f6c4e-9b1a-4c2e-9a5d-2f3b1e8a4d10",
                      "article_id": null,
                      "description": "Olio motore 5W30",
                      "quantity": 4,
                      "unit_type": "pz",
                      "unit_price": 18.5,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 74.0,
                      "total_tax": 16.28,
                      "total_after_tax": 90.28,
                      "created_at": "2026-07-15T08:31:00Z"
                    },
                    {
                      "object": "work_line_item",
                      "id": "0b4a2d6f-1c3e-4f5a-8b9c-7d6e5f4a3b2c",
                      "article_id": null,
                      "description": "Manodopera tagliando",
                      "quantity": 2,
                      "unit_type": "pz",
                      "unit_price": 79.95,
                      "unit_discount_rate": 0,
                      "tax_rate": 22,
                      "vat_code": "22",
                      "total_before_tax": 159.9,
                      "total_tax": 35.18,
                      "total_after_tax": 195.08,
                      "position": 1,
                      "created_at": "2026-07-15T08:31:00Z"
                    }
                  ],
                  "documents": []
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/MalformedRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/whoami": {
      "parameters": [
        {
          "$ref": "#/components/parameters/APIVersion"
        }
      ],
      "get": {
        "tags": [
          "Authentication"
        ],
        "summary": "Describe the calling API key",
        "x-summary-it": "Verifica la chiave API",
        "x-description-it": "Restituisce la chiave che sta effettuando la richiesta: nome,\nambiente, permessi concessi e azienda a cui appartiene.\n\nÈ l'unico endpoint senza permesso richiesto: qualunque chiave valida\npuò chiamarlo, ed è il modo più rapido per verificare che una chiave\nfunzioni. Un `200` significa che la chiave è valida e che il piano\nattivo include l'accesso API; `401` che la chiave è sconosciuta,\nrevocata o scaduta; `402` che il piano non include l'accesso API.\n",
        "operationId": "whoAmI",
        "description": "Returns the key making the request: its name, environment and the\nscopes it was granted, plus the company it authorises.\n\nDeliberately unscoped — every integrator's first call, and the\ncheapest answer to \"is my key working\". It reveals nothing the\ncaller does not already hold.\n\nA 200 means the key is valid AND the company's plan includes API\naccess. 401 means the key is unknown, revoked or expired; 402\nmeans the plan does not include API access.\n",
        "responses": {
          "200": {
            "description": "The calling key.",
            "x-description-it": "La chiave che sta effettuando la richiesta.",
            "headers": {
              "X-API-Version": {
                "$ref": "#/components/headers/APIVersion"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WhoAmI"
                },
                "example": {
                  "object": "api_key",
                  "id": "5f1a8c36-2e74-4b90-8d25-6c3f9a1e7b48",
                  "name": "Gestionale contabilità",
                  "environment": "live",
                  "scopes": [
                    "customers.read",
                    "customers.write",
                    "invoices.read"
                  ],
                  "company": {
                    "object": "company",
                    "id": "1f8c4e73-2a95-4b06-9d31-7e5a2c8b6f40",
                    "name": "Officina Rossi S.r.l."
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PublicAPIKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "APIKey",
        "description": "Long-lived API key minted from Impostazioni → Accesso API.\nFormat `ofk_live_\u003c43 chars\u003e` (`ofk_test_` outside production).\nSend it as `Authorization: Bearer ofk_live_…`.\n\nA key authorises a COMPANY and carries per-resource scopes;\na call outside its scopes answers 403 `insufficient_scope`.\nKeys are shown once at creation and stored only as a digest.\n"
      }
    },
    "parameters": {
      "IfMatch": {
        "name": "If-Match",
        "in": "header",
        "required": false,
        "x-description-it": "Il campo `version` letto nel documento (anche come ETag, `\"5\"` o\n`W/\"5\"`). Se nel frattempo il documento è stato salvato la scrittura\nè rifiutata con 409 e nulla viene salvato. Obbligatorio dalla revisione\n2026-10-22 (428 `precondition_required` senza; per gli scontrini dalla 2026-10-23);\n`*` scrive senza confronto con la tua copia. In ogni caso il server salva\nsulla copia che ha appena letto: un salvataggio arrivato nello stesso istante\nrisponde 409.\n",
        "description": "The `version` you read on the record (plain or as an ETag, `\"5\"` or\n`W/\"5\"`). If the record was saved meanwhile the write is refused with\n409 and nothing is written. Required from revision\n2026-10-22 (428 `precondition_required` without it; receipts from 2026-10-23);\n`*` writes without comparing your copy. Either way the server saves over the\ncopy it has just read, so a save landing at the same instant answers 409.\nOptional on older revisions.\n",
        "schema": {
          "type": "string",
          "example": "5"
        }
      },
      "APIVersion": {
        "name": "X-API-Version",
        "in": "header",
        "required": true,
        "description": "The client's declared revision: the dated `/v1` contract this\nclient build was developed against. Floor-checked, not an\nallowlist — must be a `YYYY-MM-DD` date and at least the minimum\nsupported revision (`api_version_retired` below it, meaning the\nclient build is too old); dates newer than the deployed server\nare accepted and served with current behaviour. Clients should\nalso self-identify via the optional headers `X-Client`\n(`officina-mobile/1.0.0 (ios)`), `X-App-Version` (`1.0.0`),\n`X-Platform` (`ios`) and `X-OS-Version` (`18.5`) — all stamped\non the request logs (`client`, `app_version`, `platform`,\n`os_version`) so revision adoption and platform-specific issues\nare measurable before the floor is raised.\nCurrent: `2026-07-15`. Minimum: `2026-07-15` (single pre-launch\nrevision). See the README registry and\n`apihttp.CurrentAPIVersion` / `MinSupportedAPIVersion` for the\nsource of truth.\n",
        "schema": {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "example": "2026-07-15"
        }
      },
      "WorkCategoryFilter": {
        "name": "work_category_id",
        "in": "query",
        "required": false,
        "x-description-it": "Solo i documenti con almeno una di queste categorie (\"Categoria\").\nRipetibile (`?work_category_id=a\u0026work_category_id=b`). Le fatture\nseguono le categorie della loro scheda lavoro: quelle da vendita\nveicolo o noleggio non corrispondono mai. Un id sconosciuto non\ncorrisponde a nulla. Assente: tutti i documenti, anche senza\ncategoria. Il filtro applicato torna in `work_category_filter`.\n",
        "description": "Only documents with ANY of these work categories (\"Categoria\").\nRepeatable (`?work_category_id=a\u0026work_category_id=b`). Invoices\nfollow their worksheet's categories: vehicle-sale and rental\ninvoices never match. An unknown id matches nothing. Left out: every\ndocument, uncategorized ones included. Never stored between\nrequests; the applied filter is echoed in `work_category_filter`.\n",
        "schema": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "style": "form",
        "explode": true
      },
      "DatePreset": {
        "name": "preset",
        "in": "query",
        "required": false,
        "description": "Date filter preset. Relative presets (`rel30`/`rel90`/`rel365`)\nare resolved server-side from \"now\" and ignore `from`/`to`.\nAbsolute presets (`year`, `Q1`-`Q4`, `month`, `custom`) require\nboth `from` and `to`. `all` returns everything (no time bound).\nUnknown values are rejected with 422.\n\nOmitting `from`/`to`/`preset` gives the default window, the last\n12 months. This tier is stateless: a selection applies to the\nrequest that carries it and is never stored, so the same request\nalways covers the same range. The effective range is echoed back\nin `date_filter`.\n",
        "schema": {
          "type": "string",
          "enum": [
            "rel30",
            "rel90",
            "rel365",
            "year",
            "Q1",
            "Q2",
            "Q3",
            "Q4",
            "month",
            "custom",
            "all"
          ]
        }
      }
    },
    "headers": {
      "APIVersion": {
        "description": "Echoes the `X-API-Version` value the server processed.",
        "schema": {
          "type": "string"
        }
      },
      "Deprecation": {
        "description": "Present only when the declared `X-API-Version` is older than the current\nrevision (RFC 9745): the revision still works but is deprecated. The value\nis `@` + the Unix time of the current revision's date. Comes with\n`Link: \u003chttps://officina.it/api#deprecazione\u003e; rel=\"deprecation\"`.\n",
        "schema": {
          "type": "string",
          "example": "@1792022400"
        }
      },
      "Sunset": {
        "description": "Present only when the declared revision has a scheduled retirement\n(RFC 8594): the HTTP date from which calls with it answer\n`400 api_version_retired`. Announced at least 90 days ahead.\n",
        "schema": {
          "type": "string",
          "example": "Wed, 20 Jan 2027 00:00:00 GMT"
        }
      }
    },
    "schemas": {
      "WhoAmI": {
        "type": "object",
        "description": "The API key making the request, as returned by GET /v1/whoami.\n",
        "required": [
          "object",
          "id",
          "name",
          "environment",
          "scopes",
          "company"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"api_key\""
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "The label the workshop gave the key."
          },
          "environment": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Matches the token prefix (ofk_live_ / ofk_test_)."
          },
          "scopes": {
            "type": "array",
            "description": "Granted scopes, e.g. \"customers.read\".",
            "items": {
              "type": "string"
            }
          },
          "company": {
            "type": "object",
            "required": [
              "object",
              "id",
              "name"
            ],
            "properties": {
              "object": {
                "type": "string",
                "description": "always \"company\""
              },
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          }
        }
      },
      "LoginMethodRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "Email to resolve the credential step for."
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "format": "password",
            "minLength": 1
          }
        }
      },
      "SignOnTokenRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "LoginWithTokenRequest": {
        "type": "object",
        "required": [
          "email",
          "sign_on_token"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "sign_on_token": {
            "type": "string",
            "description": "One-time code received via email.",
            "minLength": 1
          }
        }
      },
      "Profile": {
        "type": "object",
        "required": [
          "object",
          "id",
          "first_name",
          "last_name",
          "email",
          "phone",
          "phone_national",
          "role",
          "permissions",
          "all_permissions",
          "modules"
        ],
        "description": "The authenticated user's own account (Profilo personale).\nEmail is read-only — the login email cannot be changed from\nthis surface, matching the web.\n\n`permissions` + `modules` are the app's gating surface: the\nuser's granted permission keys and the company's feature\nflags, resolved server-side with the same semantics the web\nsidebar (dashboard-sidebar.html) reads them. The client hides\nnavigation/actions from them; the API remains the enforcement\nlayer.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "profile"
          },
          "id": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored E.164 value."
          },
          "phone_national": {
            "type": [
              "string",
              "null"
            ],
            "description": "phone minus the +39 dial code, for the fixed-prefix input."
          },
          "role": {
            "type": [
              "string",
              "null"
            ]
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Sorted permission keys, e.g. \"invoices.read\"."
          },
          "all_permissions": {
            "type": "boolean",
            "description": "True when the user holds every permission in the system\n(owner-equivalent). Gates owner-only surfaces like the AI\nassistant — exposed because the client cannot know the\nfull permission catalog.\n"
          },
          "modules": {
            "type": "object",
            "required": [
              "checkins",
              "electronic_invoices",
              "expenses",
              "dealership",
              "vehicle_rental",
              "tyre_storage",
              "service_catalog",
              "inventory",
              "technical_data",
              "license_plate_search",
              "public_profile",
              "ai_assistant",
              "has_worksheets"
            ],
            "description": "Company feature flags. `technical_data` already folds in\nthe subscription check (Company.CanUseTechnicalData);\n`has_worksheets` backs the web's \"Situazione appears\nafter the first worksheet\" rule.\n",
            "properties": {
              "checkins": {
                "type": "boolean"
              },
              "electronic_invoices": {
                "type": "boolean"
              },
              "expenses": {
                "type": "boolean"
              },
              "dealership": {
                "type": "boolean"
              },
              "vehicle_rental": {
                "type": "boolean"
              },
              "tyre_storage": {
                "type": "boolean"
              },
              "service_catalog": {
                "type": "boolean"
              },
              "inventory": {
                "type": "boolean"
              },
              "technical_data": {
                "type": "boolean"
              },
              "license_plate_search": {
                "type": "boolean"
              },
              "public_profile": {
                "type": "boolean"
              },
              "ai_assistant": {
                "type": "boolean"
              },
              "has_worksheets": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "WritableTimestamp": {
        "type": "string",
        "format": "date-time",
        "description": "RFC 3339 timestamp supplied by the client. Must fall between\n1900-01-01 and 2100-12-31. A year outside that window is a\ntypo (2025 mistyped as 0025 is the common one), and the app's\nown date pickers refuse the same range — a record written with\none would not be editable in the web UI afterwards. Out of\nrange → 422 `parameter_invalid` with `param` pointing at the\noffending field.\n"
      },
      "WritableTimestampNullable": {
        "type": [
          "string",
          "null"
        ],
        "format": "date-time",
        "description": "Nullable client-supplied RFC 3339 timestamp. Same\n1900-01-01 … 2100-12-31 window as `WritableTimestamp`;\n`null` clears the field.\n"
      },
      "CustomerCreateRequest": {
        "type": "object",
        "required": [
          "customer_type"
        ],
        "description": "Body of `POST /v1/customers`. `company_id` and `created_by`\nare always derived from the authenticated bearer token and\nMUST NOT appear in the body.\n\nRequired: `customer_type`. Everything else is optional;\nomitted keys map to NULL on the entity. There is no\nthree-state semantic on create — there's nothing to \"leave\nuntouched\".\n\nFields not listed here are intentionally NOT settable on\ncreate: `id`, `company_id`, `created_by`, system timestamps,\nand all computed display fields.\n",
        "properties": {
          "customer_type": {
            "type": "string"
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "National number as typed (e.g. \"333 1234567\"). Combined\nwith `phone_dial_code` and validated server-side via the\nweb customer-form rules (\"+39\" allows Italian mobiles and\nlandlines; other known dial codes get a basic E.164 check);\nstored and returned as one E.164 string. Invalid pairs are\nrejected with 422 `parameter_invalid`.\n"
          },
          "phone_dial_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "E.164 dial code (default \"+39\"). Only read alongside `phone`."
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "province": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Partita IVA, or the VAT number of a business in another EU country.\nChecked on every revision against the country's format and check\ndigits (see \"Partita IVA e numeri IVA UE\"): 422 `parameter_invalid`\non `/tax_id` when it fails. Stored without country prefix, spaces\nor dots.\n"
          },
          "fiscal_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "pec": {
            "type": [
              "string",
              "null"
            ]
          },
          "destination_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "birthday": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "price_list_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "custom_fields": {
            "$ref": "#/components/schemas/CustomerCustomFieldValues"
          }
        },
        "additionalProperties": false
      },
      "CustomerPatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/customers/{id}`. Every field is optional on\nthe wire and carries three-state semantics:\n\n- Field omitted → leave the entity field untouched.\n- Field present with value `null` → clear the entity field.\n- Field present with a value → set the entity field.\n\nFields not listed here are intentionally NOT patchable through\nthe API: `id`, `company_id`, `created_by`, system timestamps\n(`created_at`, `updated_at`, `deleted_at`), and computed/derived\nfields (`total_spent`, `next_appointment`, `last_vehicle_*`).\nDocuments are managed via the documents sub-resource.\n\n`customer_type` is NOT NULL at the entity layer; sending it as\nexplicit `null` is rejected with 422 `parameter_invalid`.\n",
        "properties": {
          "customer_type": {
            "type": "string"
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "National number as typed (e.g. \"333 1234567\"). Combined\nwith `phone_dial_code` and validated server-side via the\nweb customer-form rules (\"+39\" allows Italian mobiles and\nlandlines; other known dial codes get a basic E.164 check);\nstored and returned as one E.164 string. Invalid pairs are\nrejected with 422 `parameter_invalid`.\n"
          },
          "phone_dial_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "E.164 dial code (default \"+39\"). Only read alongside `phone`."
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "province": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Partita IVA, or the VAT number of a business in another EU country.\nChecked on every revision against the country's format and check\ndigits (see \"Partita IVA e numeri IVA UE\"): 422 `parameter_invalid`\non `/tax_id` when it fails. Stored without country prefix, spaces\nor dots. Checked only when `tax_id` or `customer_type` changes.\n"
          },
          "fiscal_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "pec": {
            "type": [
              "string",
              "null"
            ]
          },
          "destination_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "birthday": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "price_list_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "custom_fields": {
            "$ref": "#/components/schemas/CustomerCustomFieldValues"
          }
        },
        "additionalProperties": false
      },
      "CustomerCustomFieldValues": {
        "type": [
          "object",
          "null"
        ],
        "description": "The customer's custom field values, keyed by definition `id` from\n`GET /v1/customer-field-settings` (e.g. `cf_9b20d3`). Same rules as\n`VehicleCustomFieldValues`: every key is checked against the\ncompany's definitions and each value against its field's `type`\n(`string` trimmed; `integer` as a JSON integer or a string holding\none; `datetime` as `YYYY-MM-DD` or RFC 3339, stored as\n`YYYY-MM-DD`, years 1900–2100; `list` as a JSON string equal to\none of the field's `options`, exact match after trimming, else\n422 \"Deve essere una delle opzioni del campo.\"). A date field\nwhose definition sets `reminder_days_before` (and is visible)\ngets an agenda event on that date.\n\nA `null` or empty value clears that field. An unknown key or a\nvalue that does not fit its type is a 422 `parameter_invalid`\nwith `param` pointing at it (`/custom_fields/\u003cid\u003e`). A value sent\nback exactly as the customer stores it is never refused. On PATCH\nthe object replaces the stored one as a whole: a visible field\nleft out is cleared, a hidden field keeps its stored value unless\nits key is sent.\n\nA stored value that no longer fits its field's type (text left\nunder a field later turned into a date or number, a choice since\nremoved from a list's `options`) is likewise kept\nunless its key is sent — clients cannot show it in a typed input,\nso leaving it out is not a request to clear it. Send the key with\n`null` to clear it.\n",
        "x-description-it": "I valori dei campi personalizzati del cliente, con chiave l'`id`\ndel campo definito in `GET /v1/customer-field-settings`. Stesse\nregole dei campi veicolo: stringa per `string`, intero (anche come\nstringa) per `integer`, data `YYYY-MM-DD` o timestamp RFC 3339 per\n`datetime`, una delle `options` del campo (stringa identica, spazi\nai bordi esclusi) per `list`. `null` o vuoto svuota il campo; una\nchiave sconosciuta o un valore del tipo sbagliato è un 422 (per\n`list`: \"Deve essere una delle opzioni del campo.\"). In PATCH l'oggetto\nsostituisce quello salvato: i campi visibili omessi vengono\nsvuotati, quelli nascosti conservano il valore salvato.\n\nAnche un valore salvato che non è più compatibile con il tipo del\ncampo (testo in un campo diventato data o numero, una voce poi\ntolta dalle `options` di una lista) resta invariato\nse la sua chiave non viene inviata; per svuotarlo invia la chiave\ncon `null`.\n",
        "additionalProperties": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ]
        },
        "example": {
          "cf_4c1e7a": "Idraulico",
          "cf_9b20d3": "2027-03-15"
        }
      },
      "VehicleDocument": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "content_type",
          "description",
          "url",
          "file_size_bytes",
          "created_at"
        ],
        "description": "One attachment on a vehicle (Documenti Allegati). `url` is the\nauthenticated download route — fetch it with the bearer and\nhand the bytes to the OS viewer/share sheet.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "vehicle_document"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Original file name."
          },
          "content_type": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "example": "/v1/vehicles/8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55/documents/3b91ee0c-19b4-4f0a-9c2e-7d51a0c88e14/download"
          },
          "file_size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "VehicleCreateRequest": {
        "type": "object",
        "required": [
          "manufacturer_id",
          "model"
        ],
        "description": "Body of `POST /v1/vehicles`. `company_id` and `created_by`\nare always derived from the authenticated bearer token and\nMUST NOT appear in the body. Rental fields (`is_rental`,\n`rental_*`) are also not accepted — rental setup is a\nseparate flow.\n",
        "properties": {
          "manufacturer_id": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "license": {
            "type": [
              "string",
              "null"
            ]
          },
          "variant": {
            "type": [
              "string",
              "null"
            ]
          },
          "vin": {
            "type": [
              "string",
              "null"
            ]
          },
          "transmission": {
            "type": [
              "string",
              "null"
            ]
          },
          "engine_power_kw": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_displacement_ccm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_code": {
            "type": [
              "string",
              "null"
            ],
            "x-since": "2026-10-05",
            "description": "Codice motore. Stored trimmed and upper case (max 64 characters); an empty string or `null` clears it. When empty, officina.it may fill it from the vehicle's registration data. Shown in the app when `field_visibility.engine_code` is on."
          },
          "vehicle_weight_kg": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_category": {
            "type": [
              "string",
              "null"
            ]
          },
          "registered_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          },
          "km_registered_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "last_inspection_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "next_inspection_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "custom_fields": {
            "$ref": "#/components/schemas/VehicleCustomFieldValues"
          },
          "allow_duplicate_license": {
            "type": "boolean",
            "default": false,
            "x-since": "2026-10-22",
            "description": "Save even though another live vehicle of the workshop carries the same plate, after the client showed the 409 `license_plate_taken` and the user chose to go on. Ignored before revision 2026-10-22."
          }
        },
        "additionalProperties": false
      },
      "VehicleCustomFieldValues": {
        "type": [
          "object",
          "null"
        ],
        "description": "The vehicle's custom field values, keyed by definition `id` from\n`GET /v1/vehicle-field-settings` (e.g. `cf_3f9b1c`). Every key is\nchecked against the company's definitions and each value against\nits field's `type`:\n\n- `string` — a JSON string, trimmed.\n- `integer` — a JSON integer or a string holding one (`90` or\n  `\"90\"`, so values echoed from `custom_field_values` are\n  accepted); stored as a JSON integer.\n- `datetime` — a `YYYY-MM-DD` date or an RFC 3339 timestamp,\n  stored as `YYYY-MM-DD` (the calendar day as written, in the\n  timestamp's own offset). Years must fall between 1900 and 2100.\n  A field whose definition sets `reminder_days_before` gets an\n  agenda event on that date.\n- `list` (from revision 2026-10-22) — a JSON string equal to one\n  of the field's `options` (exact match after trimming); stored\n  as written. Anything else is refused with \"Deve essere una\n  delle opzioni del campo.\".\n\nA `null` or empty value clears that field. An unknown key or a\nvalue that does not fit its type is a 422 `parameter_invalid`\nwith `param` pointing at it (`/custom_fields/\u003cid\u003e`). A value sent\nback exactly as the vehicle stores it is never refused: it is kept\nas is even if it predates these rules or a change of its field's\ntype, and a key of a deleted field sent back unchanged is dropped. The object\nreplaces the stored one as a whole: a visible field left out is\ncleared. A hidden field (`visible: false` in the settings) keeps\nits stored value unless its key is sent — `custom_field_values`\nlists visible fields only, so a client rebuilding this object\nfrom it never erases data it could not see.\n\nA stored value that no longer fits its field's type (text left\nunder a field later turned into a date or number, a choice since\nremoved from a list's `options`) is likewise kept\nunless its key is sent — clients cannot show it in a typed input,\nso leaving it out is not a request to clear it. Send the key with\n`null` to clear it.\n",
        "x-description-it": "I valori dei campi personalizzati del veicolo, con chiave l'`id`\ndel campo definito in `GET /v1/vehicle-field-settings`. Ogni\nvalore deve rispettare il `type` del campo: stringa per `string`,\nintero (anche come stringa, es. `\"90\"`) per `integer`, data\n`YYYY-MM-DD` o timestamp RFC 3339 per `datetime` (salvata come\n`YYYY-MM-DD`, anni fra 1900 e 2100), una delle `options` del campo\n(stringa identica, spazi ai bordi esclusi) per `list`. `null` o\nvuoto svuota il campo; una chiave sconosciuta o un valore del tipo\nsbagliato è un 422 (per `list`: \"Deve essere una delle opzioni del\ncampo.\"). L'oggetto sostituisce quello salvato: i campi visibili omessi\nvengono svuotati, quelli nascosti (`visible: false`) conservano il\nvalore salvato a meno che la loro chiave non sia presente.\n\nAnche un valore salvato che non è più compatibile con il tipo del\ncampo (testo in un campo diventato data o numero, una voce poi\ntolta dalle `options` di una lista) resta invariato\nse la sua chiave non viene inviata; per svuotarlo invia la chiave\ncon `null`.\n",
        "additionalProperties": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ]
        },
        "example": {
          "cf_8a2d6f": "Rosso",
          "cf_3f9b1c": "2027-03-31"
        }
      },
      "VehiclePatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/vehicles/{id}`. Every field is optional on\nthe wire and carries three-state semantics:\n\n- Field omitted → leave the entity field untouched.\n- Field present with value `null` → clear the entity field.\n- Field present with a value → set the entity field.\n\nFields not listed here are intentionally NOT patchable:\n`id`, `company_id`, `created_by`, system timestamps\n(`created_at`, `updated_at`, `deleted_at`), rental fields\n(`is_rental`, `rental_*`), and computed/joined fields\n(`next_appointment`, `manufacturer_name`, `customer_name`).\n\n`model` and `manufacturer_id` are NOT NULL at the entity\nlayer; sending either as explicit `null` is rejected with\n422 `parameter_invalid`.\n\n`custom_fields` is replaced wholesale — per-key merging is\nnot supported. Keys and values are validated against the\ncompany's definitions; see `VehicleCustomFieldValues`.\n",
        "properties": {
          "manufacturer_id": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "license": {
            "type": [
              "string",
              "null"
            ]
          },
          "variant": {
            "type": [
              "string",
              "null"
            ]
          },
          "vin": {
            "type": [
              "string",
              "null"
            ]
          },
          "transmission": {
            "type": [
              "string",
              "null"
            ]
          },
          "engine_power_kw": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_displacement_ccm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_code": {
            "type": [
              "string",
              "null"
            ],
            "x-since": "2026-10-05",
            "description": "Codice motore. Stored trimmed and upper case (max 64 characters); an empty string or `null` clears it. When empty, officina.it may fill it from the vehicle's registration data. Shown in the app when `field_visibility.engine_code` is on."
          },
          "vehicle_weight_kg": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_category": {
            "type": [
              "string",
              "null"
            ]
          },
          "registered_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          },
          "km_registered_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "last_inspection_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "next_inspection_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "custom_fields": {
            "$ref": "#/components/schemas/VehicleCustomFieldValues"
          },
          "allow_duplicate_license": {
            "type": "boolean",
            "default": false,
            "x-since": "2026-10-22",
            "description": "Save even though another live vehicle of the workshop carries the same plate, after the client showed the 409 `license_plate_taken` and the user chose to go on. Ignored before revision 2026-10-22."
          }
        },
        "additionalProperties": false
      },
      "WorksheetCreateRequest": {
        "type": "object",
        "required": [
          "customer_id"
        ],
        "description": "Body of `POST /v1/worksheets`. Only client-supplied fields\nappear here — server-managed columns (id, company_id,\ncreated_by, seq_number, status, all timestamps, totals)\nare filled in automatically.\n\nLine items are NOT accepted on creation; a created worksheet\nstarts with an empty `line_items` array.\n",
        "properties": {
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Single-category form, accepted in every revision: the\ndocument's only category. To set several, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "The document's work categories, in the order chosen\n(`work_category_id` in responses reports the first). Trimmed and\nde-duplicated (first occurrence wins); at most 10, each one of\nthe company's categories (`GET /v1/work-categories`) — an\nunknown id is reported on `/work_category_ids/{i}`. Do not send\ntogether with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "WorksheetPatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/worksheets/{id}`. Every field carries\nthree-state semantics:\n\n- Field omitted → leave the entity field untouched.\n- Field present with value `null` → clear the entity field.\n- Field present with a value → set the entity field.\n\nFields not listed here are intentionally NOT patchable:\n`id`, `company_id`, `created_by`, `seq_number`, `status`\n(use the transitions endpoint), `customer_id`, `vehicle_id`,\nall totals, all `*_at` timestamps, `line_items`, `documents`.\n",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Single-category form for clients that handle one category,\naccepted in every revision with the same meaning: a value\nreplaces the first category of the list and keeps the others;\n`null` removes the first one. To set or clear the full list, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "Replaces the document's whole category list, in the order\ngiven. Omitted → untouched; `[]` or `null` → removes every\ncategory; a list → becomes the new list. Same validation as on\ncreate (max 10, de-duplicated, company's categories only). Do\nnot send together with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          },
          "total_work_cost": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "x-since": "2026-10-15",
            "description": "Sets the internal labour cost by hand (0 included); line changes\nno longer move it. `null` goes back to automatic (the lines'\nsum). Omitted → untouched. Negative → `422`.\n"
          },
          "total_article_cost": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "x-since": "2026-10-15",
            "description": "Same as `total_work_cost`, for the parts cost."
          }
        },
        "additionalProperties": false
      },
      "WorksheetTransitionRequest": {
        "type": "object",
        "required": [
          "action"
        ],
        "description": "Body of `POST /v1/worksheets/{id}/transitions`. The server\nruns the matching state transition and stamps the\ncorresponding `*_at` column.\n",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "draft",
              "waiting",
              "started",
              "completed",
              "delivered",
              "archived"
            ]
          }
        },
        "additionalProperties": false
      },
      "LineItemInput": {
        "type": "object",
        "description": "A line item supplied by the client in an `add` or `update`\noperation. `tax_rate` is NOT accepted — it is resolved\nserver-side from `vat_code` so the stored rate always matches\nthe company VAT table. Money/quantity fields are in the working\ncurrency unit and natural units respectively.\n",
        "properties": {
          "line_type": {
            "type": "string",
            "enum": [
              "item",
              "note",
              "separator"
            ],
            "x-since": "2026-10-14",
            "description": "Optional, default `item`. A `note` needs only `description`; a\n`separator` needs nothing. For both, the numeric fields,\n`vat_code` and `article_id` are ignored and stored as zero. On\n`update`, leaving it out keeps the line's current type.\n"
          },
          "description": {
            "type": "string",
            "description": "Required for `item` and `note`, non-empty."
          },
          "quantity": {
            "type": "number",
            "description": "Required for `item`. May be negative or zero, as on the web; at most 2 decimals are kept."
          },
          "unit_type": {
            "type": "string",
            "description": "Required for `item` (e.g. \"pz\", \"kg\", \"L\", \"kit\")."
          },
          "unit_price": {
            "type": "number",
            "description": "Required for `item`. May be negative (a discount or a return line); rounded to the cent."
          },
          "unit_discount_rate": {
            "type": "number",
            "description": "Whole percent, 0..100 (a fraction is refused). Defaults to 0."
          },
          "vat_code": {
            "type": "string",
            "description": "Required for `item`; must exist in the company VAT table."
          },
          "article_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional link to a catalog article. When set it must\nreference an article owned by the caller's company. On\nworksheets, article-backed lines move inventory stock.\n"
          },
          "unit_cost": {
            "type": [
              "number",
              "null"
            ],
            "x-since": "2026-10-15",
            "description": "Optional internal cost per unit, ex-VAT, for the worksheet's\ninternal costs. Omitted: an article line takes the article's\npurchase price at that moment, a line without article has no\ncost; an `update` that keeps the same article keeps the line's\ncurrent cost. May be negative (a credit, like a negative\npurchase price); beyond ±99.999.999,99 → `422` on `/unit_cost`.\n"
          },
          "position": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Optional ordering position. When omitted the item is\nappended after the current maximum position.\n"
          }
        },
        "additionalProperties": false
      },
      "LineItemUpdate": {
        "description": "Identifies an existing line item by `id` and carries its full\nreplacement fields (a full replace of editable fields, not a\npartial patch). Applied as a remove-then-add internally so\narticle stock reconciles through the normal path; as a result\nthe updated line receives a NEW id in the response.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/LineItemInput"
          },
          {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string"
              }
            }
          }
        ]
      },
      "LineItemReorder": {
        "type": "object",
        "required": [
          "id",
          "position"
        ],
        "description": "Sets the position of an existing line item that is not otherwise\nadded, updated, or removed in the same batch.\n",
        "properties": {
          "id": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          }
        },
        "additionalProperties": false
      },
      "LineItemsBatchRequest": {
        "type": "object",
        "description": "Body of `POST /v1/worksheets/{id}/line-items/batch` and\n`POST /v1/quotes/{id}/line-items/batch`. Applies a single diff\nagainst the resource's current line-item set; totals are\nrecomputed server-side from the result and the full resource is\nreturned.\n\nAll four arrays are optional; an empty body is a no-op. An id may\nappear in at most one of `remove`/`update`/`reorder` — listing it\ntwice is a 422. Ids in `remove`/`update`/`reorder` must belong to\nthe target resource.\n",
        "properties": {
          "add": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItemInput"
            }
          },
          "update": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItemUpdate"
            }
          },
          "remove": {
            "type": "array",
            "description": "Ids of line items to delete.",
            "items": {
              "type": "string"
            }
          },
          "reorder": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItemReorder"
            }
          }
        },
        "additionalProperties": false
      },
      "QuoteCreateRequest": {
        "type": "object",
        "required": [
          "customer_id"
        ],
        "description": "Body of `POST /v1/quotes`. Server-managed columns (id,\ncompany_id, created_by, seq_number, status, all timestamps,\ntotals) are filled in automatically. Line items are not\naccepted on creation.\n",
        "properties": {
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Single-category form, accepted in every revision: the\ndocument's only category. To set several, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "The document's work categories, in the order chosen\n(`work_category_id` in responses reports the first). Trimmed and\nde-duplicated (first occurrence wins); at most 10, each one of\nthe company's categories (`GET /v1/work-categories`) — an\nunknown id is reported on `/work_category_ids/{i}`. Do not send\ntogether with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "x-since": "2026-10-27",
            "description": "Data scadenza: the last day the offer is valid, `YYYY-MM-DD`.\nNot before the quote's date (`422 parameter_invalid` on\n`/expires_at`). Omitted or `null` → no expiry. Available since\nrevision 2026-10-27.\n"
          }
        },
        "additionalProperties": false
      },
      "QuotePatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/quotes/{id}`. Three-state semantics\n(omitted = leave unchanged, explicit null = clear, concrete\nvalue = set). `status`, `seq_number`, `customer_id`,\n`vehicle_id`, totals, `worksheet_id`, and timestamps are\nNOT patchable.\n",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Single-category form for clients that handle one category,\naccepted in every revision with the same meaning: a value\nreplaces the first category of the list and keeps the others;\n`null` removes the first one. To set or clear the full list, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "Replaces the document's whole category list, in the order\ngiven. Omitted → untouched; `[]` or `null` → removes every\ncategory; a list → becomes the new list. Same validation as on\ncreate (max 10, de-duplicated, company's categories only). Do\nnot send together with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "x-since": "2026-10-27",
            "description": "Data scadenza, `YYYY-MM-DD`; `null` clears it. Not before the\nquote's date (`422 parameter_invalid` on `/expires_at`).\nAvailable since revision 2026-10-27.\n"
          }
        },
        "additionalProperties": false
      },
      "QuoteConvertRequest": {
        "type": "object",
        "x-since": "2026-10-28",
        "description": "Optional body of `POST /v1/quotes/{id}/convert`.\n",
        "properties": {
          "mark_accepted": {
            "type": "boolean",
            "default": true,
            "description": "`true` (default) marks the quote `accepted`, whatever its status;\n`false` links the new worksheet and leaves the status as it is.\n"
          }
        },
        "additionalProperties": false
      },
      "QuoteTransitionRequest": {
        "type": "object",
        "required": [
          "action"
        ],
        "description": "Body of `POST /v1/quotes/{id}/transitions`. Unknown action\nvalues are rejected with 422 `parameter_invalid`.\n",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "waiting",
              "accepted",
              "rejected"
            ]
          }
        },
        "additionalProperties": false
      },
      "InvoiceCreateRequest": {
        "type": "object",
        "required": [
          "worksheet_id",
          "payment_method"
        ],
        "description": "Body of `POST /v1/invoices`. A mobile invoice is always\ncreated from an existing worksheet — there is no \"blank\ninvoice + add lines\" flow on this tier.\n",
        "properties": {
          "worksheet_id": {
            "type": "string",
            "description": "Source worksheet UUID (must belong to caller's company)."
          },
          "payment_method": {
            "type": "string",
            "description": "Free-form payment method label.",
            "minLength": 1
          },
          "payment_due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Optional. RFC 3339 between 1900-01-01 and 2100-12-31\n(see `WritableTimestampNullable`). Defaults to today + the\ncompany's configured payment term.\n"
          }
        },
        "additionalProperties": false
      },
      "InvoicePatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/invoices/{id}`. Three-state per field:\nomitted (leave alone), `null` (clear, where nullable),\nconcrete value (set). Non-nullable columns reject `null`\nwith 422 `parameter_invalid`.\n\nFields not listed here are intentionally NOT patchable:\n`id`, `seq_number`, `status`, `customer_id`, `vehicle_id`,\n`worksheet_id`, all totals, all timestamps except\n`payment_due_at`. Line item add/remove is not exposed.\n\n`title`, `comments`, `internal_comments` are deliberately\nabsent today: the service-layer Update contract doesn't\ncarry these columns yet, so accepting them on the wire\nwould silently drop the write. They will be added once\nthe service is extended.\n",
        "properties": {
          "payment_method": {
            "type": "string"
          },
          "payment_condition": {
            "type": "string"
          },
          "payment_due_at": {
            "$ref": "#/components/schemas/WritableTimestamp"
          },
          "vat_exigibility": {
            "type": "string"
          },
          "sezionale_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_document_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_document_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_document_date": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "pa_cig": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_cup": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "InvoiceIssueRequest": {
        "type": "object",
        "description": "Body of `POST /v1/invoices/{id}/issue`. All fields are\noptional; an empty body uses server defaults.\n",
        "properties": {
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Override the server clock. Must fall between 1900-01-01\nand 2100-12-31 (see `WritableTimestampNullable`).\nBack-dates that would violate the monotonic per-company\nsequence return 409.\n"
          }
        },
        "additionalProperties": false
      },
      "CreditNoteCreateRequest": {
        "type": "object",
        "required": [
          "invoice_id"
        ],
        "description": "Body of `POST /v1/credit-notes`. Credit notes are derivative\ndocuments — created from an existing invoice. The source\ninvoice MUST belong to the authenticated user's company.\n\nServer-derived / immutable fields are NOT accepted: `id`,\n`company_id` (from source invoice), `created_by` (from\nauth), `seq_number` (server-assigned), totals (copied from\ninvoice), timestamps.\n",
        "properties": {
          "invoice_id": {
            "type": "string",
            "description": "Source invoice UUID (must belong to caller's company)."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "sezionale_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "CreditNotePatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/credit-notes/{id}`. `title` is the ONLY\nmutable field on a credit note. All other fields are derived\nfrom the source invoice, immutable identity, or\nserver-managed.\n\nThree-state for `title`:\n\n- Omitted → leave unchanged.\n- Explicit JSON `null` → clear (SQL NULL).\n- Concrete value → set.\n\nSending any other key returns 400 `malformed_request`.\n",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "CheckinCreateRequest": {
        "type": "object",
        "required": [
          "customer_id",
          "vehicle_id"
        ],
        "description": "Body of `POST /v1/checkins`. Server-managed fields (`id`,\n`company_id`, `created_by`, `seq_number`, `status`,\ntimestamps) are NOT accepted. Line items start empty — a\ndedicated sub-resource will land later.\n",
        "properties": {
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Single-category form, accepted in every revision: the\ndocument's only category. To set several, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "The document's work categories, in the order chosen\n(`work_category_id` in responses reports the first). Trimmed and\nde-duplicated (first occurrence wins); at most 10, each one of\nthe company's categories (`GET /v1/work-categories`) — an\nunknown id is reported on `/work_category_ids/{i}`. Do not send\ntogether with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "fuel_level": {
            "type": [
              "integer",
              "null"
            ]
          },
          "section1": {
            "type": [
              "string",
              "null"
            ]
          },
          "section2": {
            "type": [
              "string",
              "null"
            ]
          },
          "section3": {
            "type": [
              "string",
              "null"
            ]
          },
          "section4": {
            "type": [
              "string",
              "null"
            ]
          },
          "section5": {
            "type": [
              "string",
              "null"
            ]
          },
          "section6": {
            "type": [
              "string",
              "null"
            ]
          },
          "section7": {
            "type": [
              "string",
              "null"
            ]
          },
          "section8": {
            "type": [
              "string",
              "null"
            ]
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "CheckinPatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/checkins/{id}`. Three-state per field:\n\n- Field omitted → leave the entity field untouched.\n- Field present with value `null` → clear the entity field.\n- Field present with a value → set the entity field.\n\nFields not listed here are intentionally NOT patchable: `id`,\n`company_id`, `created_by`, `seq_number`, system timestamps,\n`status` (use the transitions endpoint), `customer_id`,\n`vehicle_id` (immutable), and `line_items` (separate\nsub-resource).\n",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "fuel_level": {
            "type": [
              "integer",
              "null"
            ]
          },
          "section1": {
            "type": [
              "string",
              "null"
            ]
          },
          "section2": {
            "type": [
              "string",
              "null"
            ]
          },
          "section3": {
            "type": [
              "string",
              "null"
            ]
          },
          "section4": {
            "type": [
              "string",
              "null"
            ]
          },
          "section5": {
            "type": [
              "string",
              "null"
            ]
          },
          "section6": {
            "type": [
              "string",
              "null"
            ]
          },
          "section7": {
            "type": [
              "string",
              "null"
            ]
          },
          "section8": {
            "type": [
              "string",
              "null"
            ]
          },
          "km": {
            "type": [
              "number",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Single-category form for clients that handle one category,\naccepted in every revision with the same meaning: a value\nreplaces the first category of the list and keeps the others;\n`null` removes the first one. To set or clear the full list, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "Replaces the document's whole category list, in the order\ngiven. Omitted → untouched; `[]` or `null` → removes every\ncategory; a list → becomes the new list. Same validation as on\ncreate (max 10, de-duplicated, company's categories only). Do\nnot send together with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "quote_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "CheckinTransitionRequest": {
        "type": "object",
        "required": [
          "action"
        ],
        "description": "Body of `POST /v1/checkins/{id}/transitions`. The target\nlifecycle state. Unknown values are rejected with 422\n`parameter_invalid`.\n",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "active",
              "completed"
            ]
          }
        },
        "additionalProperties": false
      },
      "CheckinLineItemInput": {
        "type": "object",
        "required": [
          "description"
        ],
        "description": "A checkin line item supplied in an `add` or `update`. Checkin\nline items are description-only notes — no quantity, price, VAT,\narticle, or position.\n",
        "properties": {
          "description": {
            "type": "string",
            "description": "Required, non-empty."
          }
        },
        "additionalProperties": false
      },
      "CheckinLineItemUpdate": {
        "description": "Identifies an existing checkin line item by `id` and carries its\nreplacement description. Applied as a remove-then-add internally,\nso the updated line receives a NEW id in the response.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/CheckinLineItemInput"
          },
          {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string"
              }
            }
          }
        ]
      },
      "CheckinLineItemsBatchRequest": {
        "type": "object",
        "description": "Body of `POST /v1/checkins/{id}/line-items/batch`. Applies a\nsingle diff against the checkin's current line items and returns\nthe full checkin. Narrower than the worksheet/quote batch: no\n`reorder` (rows have no position), description-only fields.\n\nAll arrays are optional; an empty body is a no-op. An id may\nappear in at most one of `remove`/`update`, and ids in\n`remove`/`update` must belong to the target checkin.\n",
        "properties": {
          "add": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckinLineItemInput"
            }
          },
          "update": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckinLineItemUpdate"
            }
          },
          "remove": {
            "type": "array",
            "description": "Ids of line items to delete.",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": false
      },
      "CalendarEntryCreateRequest": {
        "type": "object",
        "required": [
          "title",
          "start_time",
          "end_time",
          "color"
        ],
        "description": "Body of `POST /v1/calendar-entries`. `end_time` must be\nstrictly after `start_time`. `user_id` (ownership),\n`company_id`, and audit timestamps are server-managed and\nnot accepted on the wire.\n\n`worksheet_id` links the appointment to a scheda lavoro.\n`customer_id` and `vehicle_id`, when sent, must be the\nworksheet's (a worksheet without a vehicle fits any vehicle of\nits customer); when omitted they are taken from the worksheet.\nA mismatch returns 422 `parameter_invalid` on `/worksheet_id`.\n\n`quote_id` / `checkin_id` are response-only: the underlying\nrepository does not persist them on create today, so they are\nintentionally absent from this request.\n",
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "start_time": {
            "$ref": "#/components/schemas/WritableTimestamp"
          },
          "end_time": {
            "$ref": "#/components/schemas/WritableTimestamp"
          },
          "color": {
            "type": "string"
          },
          "email_reminder": {
            "type": "boolean"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Single-category form, accepted in every revision: the\ndocument's only category. To set several, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "The document's work categories, in the order chosen\n(`work_category_id` in responses reports the first). Trimmed and\nde-duplicated (first occurrence wins); at most 10, each one of\nthe company's categories (`GET /v1/work-categories`) — an\nunknown id is reported on `/work_category_ids/{i}`. Do not send\ntogether with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "calendar_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "auto_reminder_enabled": {
            "type": "boolean"
          },
          "auto_reminder_channel": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "email",
              "sms",
              "whatsapp",
              null
            ]
          }
        },
        "additionalProperties": false
      },
      "CalendarEntryPatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/calendar-entries/{id}`. Three-state PATCH\nsemantics: omitted = unchanged, explicit `null` = clear (only\nfor nullable fields). Sending `null` on a non-nullable field\n(`title`, `color`, `start_time`, `end_time`,\n`email_reminder`, `auto_reminder_enabled`) returns 422\n`parameter_invalid`. When `start_time` or `end_time` is\npatched the resulting pair must satisfy\n`end_time \u003e start_time`.\n\n`worksheet_id` links the appointment to a scheda lavoro;\n`null` unlinks. The customer and vehicle after the patch must\nbe the worksheet's (a worksheet without a vehicle fits any\nvehicle of its customer); the ones the entry is left without\nare taken from the worksheet, and a mismatch returns 422\n`parameter_invalid` on `/worksheet_id`. A patch that moves\n`customer_id` or `vehicle_id` off the linked worksheet, without\nsending `worksheet_id`, unlinks it.\n",
        "properties": {
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "start_time": {
            "$ref": "#/components/schemas/WritableTimestamp"
          },
          "end_time": {
            "$ref": "#/components/schemas/WritableTimestamp"
          },
          "color": {
            "type": "string"
          },
          "email_reminder": {
            "type": "boolean"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Single-category form for clients that handle one category,\naccepted in every revision with the same meaning: a value\nreplaces the first category of the list and keeps the others;\n`null` removes the first one. To set or clear the full list, send\n`work_category_ids` instead (not both).\n"
          },
          "work_category_ids": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 10,
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "Replaces the document's whole category list, in the order\ngiven. Omitted → untouched; `[]` or `null` → removes every\ncategory; a list → becomes the new list. Same validation as on\ncreate (max 10, de-duplicated, company's categories only). Do\nnot send together with `work_category_id`: both →\n`422 parameter_invalid`. Available since revision 2026-10-03.\n"
          },
          "calendar_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "auto_reminder_enabled": {
            "type": "boolean"
          },
          "auto_reminder_channel": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "email",
              "sms",
              "whatsapp",
              null
            ]
          }
        },
        "additionalProperties": false
      },
      "AuthMethodResponse": {
        "type": "object",
        "required": [
          "object",
          "email",
          "method"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "auth_method",
            "description": "Polymorphic discriminator."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Echoes the email from the request."
          },
          "method": {
            "type": "string",
            "enum": [
              "password",
              "sign_on_token",
              "not_found"
            ],
            "description": "Credential step to show next. `password` → submit to\n`/v1/auth/login`; `sign_on_token` → request a code via\n`/v1/auth/sign-on-token`, then submit to\n`/v1/auth/login-with-token`; `not_found` → no account for\nthis email, show the contact-assistance sheet (web-parity\ndisclosure — the public login already reveals this).\n"
          }
        }
      },
      "AuthTokenResponse": {
        "type": "object",
        "required": [
          "object",
          "token_type",
          "access_token",
          "expires_at",
          "user"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "auth_token",
            "description": "Polymorphic discriminator."
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "access_token": {
            "type": "string",
            "description": "Mobile JWT. Send as `Authorization: Bearer \u003caccess_token\u003e`\non subsequent requests. Carries `aud=mobile`.\n",
            "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Absolute expiration timestamp in ISO 8601 / RFC 3339 UTC.\nClients should refresh a few minutes before this elapses.\n",
            "example": "2026-06-05T00:00:00Z"
          },
          "user": {
            "$ref": "#/components/schemas/User"
          }
        }
      },
      "SignOnTokenResponse": {
        "type": "object",
        "required": [
          "object",
          "email",
          "message"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "sign_on_token_request"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Echoes the email from the request."
          },
          "message": {
            "type": "string",
            "description": "Human-readable confirmation string."
          }
        }
      },
      "User": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "email"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "user"
          },
          "id": {
            "type": "string",
            "description": "Stable opaque ID (UUID at the entity layer).",
            "example": "9921d5f7-83ba-4c60-a1e2-cf7b4d0925a8"
          },
          "name": {
            "type": "string",
            "description": "Display name (first + last joined)."
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "type": "string",
            "description": "Workspace role. Omitted for users without a role set.\n"
          }
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "object",
          "id",
          "customer_type",
          "created_at",
          "updated_at"
        ],
        "description": "A workshop's client. Multi-tenant: belongs to one company.\nMost descriptive fields are optional (nullable) because\ncustomer records are commonly created from a phone call\nwith only a name and phone number.\n",
        "properties": {
          "seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true,
            "minimum": 1,
            "example": 123,
            "description": "Number within this company's creation year (the year of created_at). Null for unnumbered legacy records."
          },
          "version": {
            "type": "integer",
            "description": "Detail, create and PATCH responses only. Send it back as\n`If-Match` on PATCH: a save from an older copy is refused with 409.\n"
          },
          "object": {
            "type": "string",
            "const": "customer"
          },
          "id": {
            "type": "string",
            "example": "4c911f08-7713-4e83-bdf6-a0692a1b43fd"
          },
          "customer_type": {
            "type": "string",
            "description": "Tax-status discriminator (e.g. \"private\", \"company\")."
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored E.164 value, e.g. \"+393331234567\"."
          },
          "phone_dial_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "`phone` split server-side for the edit form's dial-code\nselector (e.g. \"+39\"). Null when `phone` is null.\n"
          },
          "phone_national": {
            "type": [
              "string",
              "null"
            ],
            "description": "`phone` minus the dial code, for the edit form's\nnational-number input. Null when `phone` is null.\n"
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "province": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Partita IVA for an Italian customer; the VAT number without\ncountry prefix for a business elsewhere in the EU; free text\notherwise. See \"Partita IVA e numeri IVA UE\".\n"
          },
          "fiscal_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Italian fiscal code (codice fiscale) for individuals."
          },
          "pec": {
            "type": [
              "string",
              "null"
            ],
            "description": "Italian certified-email address for e-invoicing."
          },
          "destination_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Italian SDI destination code for e-invoicing."
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "birthday": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_vehicle_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display string of the customer's most recent vehicle\n(e.g. \"Fiat Panda\"). Pre-computed on the entity for\nlist-row rendering without a second request.\n"
          },
          "last_vehicle_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_spent": {
            "type": "number",
            "format": "double",
            "description": "Lifetime spend (worksheet totals net of discounts), computed\nby the list query for row rendering. Money — render via the\nlocale formatter; avoid arithmetic.\n"
          },
          "documents": {
            "type": "array",
            "description": "Attachments (Documenti Allegati). Present on the detail\nresponse only; omitted on lists.\n",
            "items": {
              "$ref": "#/components/schemas/CustomerDocument"
            }
          },
          "custom_fields": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Single-customer responses only (GET, POST, PATCH). Raw stored\nvalues keyed by custom field definition `id` (see\n`CustomerCustomFieldValues` for the stored shapes). Includes\nhidden fields. Omitted on lists.\n"
          },
          "custom_field_values": {
            "type": "array",
            "description": "`GET /v1/customers/{id}` only. The company's visible custom\nfield definitions (settings → Campi cliente) resolved against\nthis customer: configured order, display-ready names, \"\"\nwhen the customer has no value.\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "type",
                "value"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "string",
                    "integer",
                    "datetime",
                    "list"
                  ]
                },
                "options": {
                  "type": "array",
                  "x-since": "2026-10-23",
                  "items": {
                    "type": "string"
                  },
                  "description": "`list` fields only (omitted on the other types): the\ndropdown choices in order, so the picker renders\nwithout a settings lookup. `value` is one of them, or\n\"\" when unset.\n"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "field_visibility": {
            "type": "object",
            "description": "`GET /v1/customers/{id}` only. The company's optional-field\ntoggles (settings → Campi cliente): the edit form hides — and\nomits from its PATCH — exactly what the web form hides. All\ntrue when never configured.\n",
            "properties": {
              "birthday": {
                "type": "boolean"
              },
              "comments": {
                "type": "boolean"
              }
            }
          },
          "next_appointment": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Soft-deletion timestamp. Populated on the response from\nDELETE /v1/customers/{id}. Customers with a non-null\ndeleted_at are not returned by list/detail endpoints —\nthis field appears for the delete response only.\n"
          }
        }
      },
      "ReportV2Envelope": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "One report card. `visible: false` (with `data: null`) is the hidden\nstate: nothing to show for this company or period. `data` depends on\n`widget`: see the `ReportV2*` schemas (cards kept from the first\nreport use the `/v1/report/{widget}` shapes; `calendar-heatmap` is\n`ReportV2CalendarHeatmap`).\n",
        "required": [
          "object",
          "widget",
          "tab",
          "preset",
          "from",
          "to",
          "grain",
          "visible",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "report_widget"
            ]
          },
          "widget": {
            "type": "string"
          },
          "tab": {
            "type": "string",
            "enum": [
              "officina",
              "economico"
            ]
          },
          "preset": {
            "type": "string",
            "description": "The applied date filter preset."
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "grain": {
            "type": "string",
            "enum": [
              "week",
              "month"
            ]
          },
          "visible": {
            "type": "boolean"
          },
          "data": {
            "description": "The card's data, or null when hidden.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ReportV2OfficinaKpis"
              },
              {
                "$ref": "#/components/schemas/ReportV2LavoratoChart"
              },
              {
                "$ref": "#/components/schemas/ReportV2Categories"
              },
              {
                "$ref": "#/components/schemas/ReportV2QuoteFunnel"
              },
              {
                "$ref": "#/components/schemas/ReportV2CustomersChart"
              },
              {
                "$ref": "#/components/schemas/ReportV2Duration"
              },
              {
                "$ref": "#/components/schemas/ReportV2CalendarHeatmap"
              },
              {
                "$ref": "#/components/schemas/ReportV2TimeKpis"
              },
              {
                "$ref": "#/components/schemas/ReportV2TimeWeeks"
              },
              {
                "$ref": "#/components/schemas/ReportV2TimePeople"
              },
              {
                "$ref": "#/components/schemas/ReportV2TimeCats"
              },
              {
                "$ref": "#/components/schemas/ReportV2TimeCheck"
              },
              {
                "$ref": "#/components/schemas/ReportV2EconomicoKpis"
              },
              {
                "$ref": "#/components/schemas/ReportV2FatturatoChart"
              },
              {
                "$ref": "#/components/schemas/ReportV2ToInvoice"
              },
              {
                "$ref": "#/components/schemas/ReportV2FatturatoSources"
              },
              {
                "$ref": "#/components/schemas/ReportV2IncassatoChart"
              },
              {
                "$ref": "#/components/schemas/ReportV2PaymentMethods"
              },
              {
                "$ref": "#/components/schemas/ReportV2Receivables"
              },
              {
                "$ref": "#/components/schemas/ReportV2ExpenseCategories"
              },
              {
                "$ref": "#/components/schemas/ReportV2Composition"
              },
              {
                "$ref": "#/components/schemas/ReportV2TopCustomers"
              },
              {
                "$ref": "#/components/schemas/ReportWorksheetStatus"
              },
              {
                "$ref": "#/components/schemas/ReportRankedList"
              },
              {
                "$ref": "#/components/schemas/ReportChart"
              },
              {
                "$ref": "#/components/schemas/ReportOutstandingWorksheets"
              }
            ]
          }
        }
      },
      "ReportV2Delta": {
        "type": [
          "number",
          "null"
        ],
        "description": "Change against the previous value, percent; null when the previous value is not positive."
      },
      "ReportV2OfficinaKpis": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`officina-kpis`. `lavorato` null without prices.read, `quotes` null without quotes.read.",
        "properties": {
          "lavorato": {
            "type": [
              "object",
              "null"
            ],
            "description": "Non-draft worksheets by worksheet date, IVA excluded. Not the fatturato.",
            "properties": {
              "total": {
                "type": "number"
              },
              "labour": {
                "type": "number"
              },
              "parts": {
                "type": "number"
              },
              "previous_total": {
                "type": "number"
              },
              "delta_percent": {
                "$ref": "#/components/schemas/ReportV2Delta"
              }
            }
          },
          "closed_worksheets": {
            "type": "object",
            "description": "Worksheets completed or delivered in the period, by close date.",
            "properties": {
              "count": {
                "type": "integer"
              },
              "previous_count": {
                "type": "integer"
              },
              "delta_percent": {
                "$ref": "#/components/schemas/ReportV2Delta"
              },
              "open_now": {
                "type": "integer"
              }
            }
          },
          "quotes": {
            "type": [
              "object",
              "null"
            ],
            "description": "Quotes created in the period. The rate needs 10 quotes, the change in points 10 in the previous period too.",
            "properties": {
              "created": {
                "type": "integer"
              },
              "accepted": {
                "type": "integer"
              },
              "previous_created": {
                "type": "integer"
              },
              "previous_accepted": {
                "type": "integer"
              },
              "acceptance_rate": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "rate_delta_points": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "customers": {
            "type": "object",
            "properties": {
              "served": {
                "type": "integer"
              },
              "new": {
                "type": "integer",
                "description": "First worksheet ever in the period."
              }
            }
          }
        }
      },
      "ReportV2LavoratoChart": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`lavorato-chart`. `buckets_with_work` 0 = empty period, 1 = no comparison yet.",
        "properties": {
          "grain": {
            "type": "string",
            "enum": [
              "week",
              "month"
            ]
          },
          "buckets_with_work": {
            "type": "integer"
          },
          "buckets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "start": {
                  "type": "string",
                  "format": "date"
                },
                "labour": {
                  "type": "number"
                },
                "parts": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2Categories": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`categories`: the 5 biggest work categories, the rest in `other_count`. A worksheet with several categories counts in each; `categorised` counts it once.",
        "properties": {
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          },
          "other_count": {
            "type": "integer"
          },
          "categorised": {
            "type": "integer"
          },
          "uncategorised": {
            "type": "integer"
          }
        }
      },
      "ReportV2QuoteFunnel": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`quote-funnel`: quotes created in the period and how far they went. `enough` false below 10 quotes.",
        "properties": {
          "enough": {
            "type": "boolean"
          },
          "created": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "accepted": {
            "type": "integer"
          },
          "converted": {
            "type": "integer"
          },
          "waiting": {
            "type": "integer"
          }
        }
      },
      "ReportV2CustomersChart": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`customers-chart`: hidden when nobody was served. Returning: already had a worksheet before that bucket.",
        "properties": {
          "served": {
            "type": "integer"
          },
          "new": {
            "type": "integer"
          },
          "returning": {
            "type": "integer"
          },
          "returning_percent": {
            "type": "number"
          },
          "grain": {
            "type": "string",
            "enum": [
              "week",
              "month"
            ]
          },
          "buckets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "start": {
                  "type": "string",
                  "format": "date"
                },
                "returning": {
                  "type": "integer"
                },
                "new": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "ReportV2Duration": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`duration`: days from worksheet date to delivery. `count` 0 = nothing delivered yet (a hint, not hidden).",
        "properties": {
          "count": {
            "type": "integer"
          },
          "median_days": {
            "type": "number"
          },
          "buckets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "ReportV2CalendarHeatmap": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`calendar-heatmap`: appointments per weekday and hour. `days[0]` is Monday; `days[d][0]` is 8:00 … `days[d][11]` 19:00.",
        "properties": {
          "days": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          }
        }
      },
      "ReportV2TimeKpis": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`time-kpis` (marcatempo; hidden unless the company tracks time on a\nplan that includes it). `state`: never_tracked, no_closed, low (under\n10 closed worksheets with time: KPIs only) or full.\n",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "never_tracked",
              "no_closed",
              "low",
              "full"
            ]
          },
          "worksheets_with_time": {
            "type": "integer"
          },
          "worked_minutes": {
            "type": "number"
          },
          "billed_minutes": {
            "type": "number"
          },
          "flat_lines": {
            "type": "integer",
            "description": "\"A corpo\" labour lines, not measurable."
          },
          "resa_percent": {
            "type": [
              "number",
              "null"
            ],
            "description": "Billed ÷ worked."
          },
          "resa_tone": {
            "type": "string",
            "enum": [
              "good",
              "warn",
              "danger"
            ]
          },
          "revenue_per_worked_hour": {
            "type": [
              "number",
              "null"
            ],
            "description": "Null without prices.read."
          }
        }
      },
      "ReportV2TimeWeeks": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`time-weeks`: hours per week by close date (state full only).",
        "properties": {
          "weeks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "start": {
                  "type": "string",
                  "format": "date"
                },
                "worked_hours": {
                  "type": "number"
                },
                "billed_hours": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2TimePeople": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`time-people`: each person's share of the worked hours; hidden with fewer than two people.",
        "properties": {
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "worksheets": {
                  "type": "integer"
                },
                "worked_minutes": {
                  "type": "number"
                },
                "share_percent": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2TimeCats": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`time-cats`: resa per work category; `resa_percent` null when billed only \"a corpo\".",
        "properties": {
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "worksheets": {
                  "type": "integer"
                },
                "worked_minutes": {
                  "type": "number"
                },
                "billed_minutes": {
                  "type": "number"
                },
                "avg_worked_minutes": {
                  "type": "number"
                },
                "measurable": {
                  "type": "boolean"
                },
                "resa_percent": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "resa_tone": {
                  "type": "string",
                  "enum": [
                    "good",
                    "warn",
                    "danger"
                  ]
                }
              }
            }
          }
        }
      },
      "ReportV2TimeCheck": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`time-check`: worksheets with resa under 85%, biggest gap first.",
        "properties": {
          "worksheets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "number": {
                  "type": "string",
                  "description": "YYYY-NNNN"
                },
                "plate": {
                  "type": "string"
                },
                "category": {
                  "type": "string"
                },
                "worked_minutes": {
                  "type": "number"
                },
                "billed_minutes": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2EconomicoKpis": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`economico-kpis`. Without `ever_issued` the fatturato is \"—\", not 0.\n`incassato` is IVA included, every payment of the period (also on\nuninvoiced worksheets); compared with a year before. `margine.state`:\nexpenses_disabled, not_enough_costs (costs in under 2/3 of the\nmonths, or nothing issued) or shown. `receivables`: open invoices\ntoday, IVA included, not following the period.\n",
        "properties": {
          "ever_issued": {
            "type": "boolean"
          },
          "fatturato": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number"
              },
              "previous_total": {
                "type": "number"
              },
              "delta_percent": {
                "$ref": "#/components/schemas/ReportV2Delta"
              },
              "documents": {
                "type": "integer"
              }
            }
          },
          "incassato": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number"
              },
              "payments": {
                "type": "integer"
              },
              "last_year_total": {
                "type": "number"
              },
              "last_year_payments": {
                "type": "integer"
              },
              "delta_percent": {
                "$ref": "#/components/schemas/ReportV2Delta"
              }
            }
          },
          "margine": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "expenses_disabled",
                  "not_enough_costs",
                  "shown"
                ]
              },
              "value": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "percent_of_fatturato": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "receivables": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number"
              },
              "invoices": {
                "type": "integer"
              },
              "overdue": {
                "type": "number"
              }
            }
          }
        }
      },
      "ReportV2FatturatoChart": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`fatturato-chart`: hidden without any issued document ever. `spese`/`margine` null unless `with_costs`.",
        "properties": {
          "grain": {
            "type": "string",
            "enum": [
              "week",
              "month"
            ]
          },
          "with_costs": {
            "type": "boolean"
          },
          "expenses_enabled": {
            "type": "boolean"
          },
          "any_documents": {
            "type": "boolean"
          },
          "buckets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "start": {
                  "type": "string",
                  "format": "date"
                },
                "fatturato": {
                  "type": "number"
                },
                "spese": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "margine": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "ReportV2ToInvoice": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`to-invoice`: completed or delivered worksheets of the period without an issued invoice or receipt, IVA excluded.",
        "properties": {
          "count": {
            "type": "integer"
          },
          "total": {
            "type": "number"
          }
        }
      },
      "ReportV2FatturatoSources": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`fatturato-sources`: by document type (credit notes negative) and, with vehicle sales or rentals, by source.",
        "properties": {
          "invoices": {
            "type": "number"
          },
          "receipts": {
            "type": "number"
          },
          "credit_notes": {
            "type": "number"
          },
          "by_source": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "worksheets": {
                "type": "number"
              },
              "vehicle_sales": {
                "type": "number"
              },
              "rentals": {
                "type": "number"
              }
            }
          }
        }
      },
      "ReportV2IncassatoChart": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`incassato-chart`: payments by payment date, IVA included, against a year before. Hidden without any payment ever; `total` 0 = empty period.",
        "properties": {
          "grain": {
            "type": "string",
            "enum": [
              "week",
              "month"
            ]
          },
          "total": {
            "type": "number"
          },
          "last_year_total": {
            "type": "number"
          },
          "buckets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "start": {
                  "type": "string",
                  "format": "date"
                },
                "current": {
                  "type": "number"
                },
                "last_year": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2PaymentMethods": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`payment-methods`: the 4 biggest groups, the rest as `other`. Hidden when nothing was cashed.",
        "properties": {
          "total": {
            "type": "number"
          },
          "methods": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "cash",
                    "bank",
                    "card",
                    "cheque",
                    "riba",
                    "unspecified",
                    "other"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "amount": {
                  "type": "number"
                },
                "share_percent": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2Receivables": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`receivables`: open invoices today by days past due (the first bucket\nis not yet due). Does not follow the period. Debtors only with\ncustomers.read (`debtors_shown`).\n",
        "properties": {
          "total": {
            "type": "number"
          },
          "overdue": {
            "type": "number"
          },
          "invoices": {
            "type": "integer"
          },
          "aging": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "invoices": {
                  "type": "integer"
                },
                "amount": {
                  "type": "number"
                }
              }
            }
          },
          "debtors_shown": {
            "type": "boolean"
          },
          "debtors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "customer_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "invoices": {
                  "type": "integer"
                },
                "amount": {
                  "type": "number"
                },
                "oldest_days": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "ReportV2ExpenseCategories": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`expense-categories`: hidden when the expenses module is off; the 8 biggest categories, `total` over all. Empty list = no expenses yet.",
        "properties": {
          "total": {
            "type": "number"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "expenses": {
                  "type": "integer"
                },
                "amount": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2Composition": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`composition`: what the fatturato is made of; parts with no amount left out.",
        "properties": {
          "total": {
            "type": "number"
          },
          "parts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "labour",
                    "parts",
                    "vehicles",
                    "receipts_only"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "amount": {
                  "type": "number"
                },
                "percent": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportV2TopCustomers": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`top-customers-fatturato`: the 6 biggest customers by fatturato imponibile.",
        "properties": {
          "customers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "documents": {
                  "type": "integer"
                },
                "amount": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportFiscalProfile": {
        "type": "object",
        "x-since": "2026-10-19",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "profile",
              "forfettario",
              "full"
            ]
          },
          "fiscal_regime": {
            "type": "string",
            "description": "RF01, RF19, … or empty."
          },
          "vat_settlement_period": {
            "type": "string",
            "enum": [
              "monthly",
              "quarterly",
              ""
            ]
          },
          "vat_advance_method": {
            "type": "string",
            "enum": [
              "storico",
              "previsionale",
              ""
            ]
          },
          "regime_locked": {
            "type": "boolean",
            "description": "The regime belongs to the e-invoice settings."
          },
          "can_edit": {
            "type": "boolean",
            "description": "settings_company.read."
          }
        }
      },
      "ReportVATPeriod": {
        "type": "object",
        "x-since": "2026-10-19",
        "properties": {
          "key": {
            "type": "string",
            "example": "2026-Q3"
          },
          "label": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "current": {
            "type": "boolean",
            "description": "Still running today."
          },
          "due_date": {
            "type": "string",
            "format": "date",
            "description": "F24 deadline."
          },
          "due_code": {
            "type": "string",
            "description": "Codice tributo."
          }
        }
      },
      "ReportFiscal": {
        "type": "object",
        "x-since": "2026-10-19",
        "required": [
          "object",
          "profile",
          "periods"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "report_fiscal"
            ]
          },
          "profile": {
            "$ref": "#/components/schemas/ReportFiscalProfile"
          },
          "periods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReportVATPeriod"
            }
          },
          "default_period": {
            "type": "string"
          }
        }
      },
      "ReportFiscalProfileInput": {
        "type": "object",
        "x-since": "2026-10-19",
        "properties": {
          "fiscal_regime": {
            "type": "string",
            "enum": [
              "RF01",
              "RF19"
            ]
          },
          "vat_settlement_period": {
            "type": "string",
            "enum": [
              "monthly",
              "quarterly"
            ]
          },
          "vat_advance_method": {
            "type": "string",
            "enum": [
              "storico",
              "previsionale"
            ]
          }
        }
      },
      "ReportFiscalWidgetEnvelope": {
        "type": "object",
        "x-since": "2026-10-19",
        "required": [
          "object",
          "widget",
          "period",
          "visible",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "report_fiscal_widget"
            ]
          },
          "widget": {
            "type": "string"
          },
          "period": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ReportVATPeriod"
              }
            ]
          },
          "visible": {
            "type": "boolean"
          },
          "data": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ReportVATSummary"
              },
              {
                "$ref": "#/components/schemas/ReportVATRates"
              },
              {
                "$ref": "#/components/schemas/ReportVATHistory"
              },
              {
                "$ref": "#/components/schemas/ReportVATAdvance"
              },
              {
                "$ref": "#/components/schemas/ReportVATDeadlines"
              },
              {
                "$ref": "#/components/schemas/ReportVATExportCounts"
              }
            ]
          }
        }
      },
      "ReportVATSummary": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`kpis` / `iva`: the liquidation. `status`: nothing, credit (carried\nover; `to_pay` is the credit), below_minimum (under 25,82 €: carried\nover) or due. Quarterly adds 1% interest. `split_payment`: IVA on\nsplit-payment invoices, listed but not owed by the shop.\n",
        "properties": {
          "debit": {
            "type": "number"
          },
          "credit": {
            "type": "number"
          },
          "balance": {
            "type": "number"
          },
          "interest": {
            "type": "number"
          },
          "to_pay": {
            "type": "number"
          },
          "status": {
            "type": "string",
            "enum": [
              "nothing",
              "credit",
              "below_minimum",
              "due"
            ]
          },
          "quarterly": {
            "type": "boolean"
          },
          "sales_documents": {
            "type": "integer"
          },
          "purchase_documents": {
            "type": "integer"
          },
          "split_payment": {
            "type": "number"
          },
          "due_date": {
            "type": "string",
            "format": "date"
          },
          "due_code": {
            "type": "string"
          },
          "days_until_due": {
            "type": "integer"
          }
        }
      },
      "ReportVATRates": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`rates`: IVA per aliquota. `kind` margin groups vehicle sales and rentals with other rates or the margin scheme.",
        "properties": {
          "rates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "rate",
                    "exempt",
                    "margin"
                  ]
                },
                "rate": {
                  "type": "number"
                },
                "vat_code": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "imponibile": {
                  "type": "number"
                },
                "imposta": {
                  "type": "number"
                }
              }
            }
          },
          "total_imponibile": {
            "type": "number"
          },
          "total_imposta": {
            "type": "number"
          },
          "split_payment": {
            "type": "number"
          }
        }
      },
      "ReportVATHistory": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`history`: the balance of the period and the 3 before it, oldest first.",
        "properties": {
          "periods": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "short_label": {
                  "type": "string"
                },
                "balance": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportVATAdvance": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`advance`: acconto IVA due 27/12. Storico: 88% of the last period of the year before (under 103,29 € not paid); previsionale: computed by the commercialista, `amount` null.",
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "storico",
              "previsionale"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "due",
              "below_minimum",
              "previsionale"
            ]
          },
          "amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "base": {
            "type": [
              "number",
              "null"
            ]
          },
          "base_period": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ReportVATPeriod"
              }
            ]
          },
          "due_date": {
            "type": "string",
            "format": "date"
          },
          "tax_code": {
            "type": "string",
            "enum": [
              "6013",
              "6035"
            ]
          }
        }
      },
      "ReportVATDeadlines": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`deadlines`: the next IVA payments from today, soonest first, at most 4.",
        "properties": {
          "deadlines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "liquidation",
                    "advance"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "period_key": {
                  "type": "string"
                },
                "days_until": {
                  "type": "integer"
                },
                "next": {
                  "type": "boolean"
                }
              }
            }
          }
        }
      },
      "ReportVATExportCounts": {
        "type": "object",
        "x-since": "2026-10-19",
        "description": "`export`: how many documents the period export holds.",
        "properties": {
          "sales_documents": {
            "type": "integer"
          },
          "purchase_documents": {
            "type": "integer"
          }
        }
      },
      "ReportWidgetEnvelope": {
        "type": "object",
        "required": [
          "object",
          "widget",
          "preset",
          "from",
          "to",
          "data"
        ],
        "description": "Shell of every report-widget response. `preset`/`from`/`to` echo\nthe date filter the server applied; `from`/`to` are always\nconcrete dates —\n`all` resolves to company-creation → today. `data` carries the\nwidget-specific shape (one of the Report* schemas below, keyed\nby the widget path parameter). New widgets add a key and a Data\nschema; the envelope never changes.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "report_widget"
          },
          "widget": {
            "type": "string"
          },
          "preset": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "data": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ReportKPIs"
              },
              {
                "$ref": "#/components/schemas/ReportChart"
              },
              {
                "$ref": "#/components/schemas/ReportQuoteConversion"
              },
              {
                "$ref": "#/components/schemas/ReportWorksheetStatus"
              },
              {
                "$ref": "#/components/schemas/ReportOutstandingWorksheets"
              },
              {
                "$ref": "#/components/schemas/ReportAppointments"
              },
              {
                "$ref": "#/components/schemas/ReportRankedList"
              }
            ]
          }
        }
      },
      "ReportKPIs": {
        "type": "object",
        "required": [
          "kpis"
        ],
        "description": "Data of widget `kpis`.",
        "properties": {
          "kpis": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "key",
                "value",
                "is_currency",
                "previous_value",
                "delta",
                "delta_percentage"
              ],
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "sales",
                    "outstanding",
                    "worksheets",
                    "customers"
                  ]
                },
                "value": {
                  "type": "number"
                },
                "is_currency": {
                  "type": "boolean"
                },
                "previous_value": {
                  "type": "number"
                },
                "delta": {
                  "type": "number"
                },
                "delta_percentage": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "ReportChart": {
        "type": "object",
        "required": [
          "x_axis",
          "series"
        ],
        "description": "Data of the stacked-chart widgets (`sales-chart`,\n`expenses-chart`, `worksheet-margin-chart`). `x_axis` entries\nare YYYY-MM-DD bucket starts (daily ≤31-day periods, else\nmonthly); each series' `values` align with `x_axis`.\n",
        "properties": {
          "x_axis": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "series": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "key",
                "values"
              ],
              "properties": {
                "key": {
                  "type": "string"
                },
                "values": {
                  "type": "array",
                  "items": {
                    "type": "number"
                  }
                }
              }
            }
          }
        }
      },
      "ReportQuoteConversion": {
        "type": "object",
        "required": [
          "sent",
          "rejected",
          "converted",
          "conversion_rate"
        ],
        "description": "Data of widget `quote-conversion`.",
        "properties": {
          "sent": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "converted": {
            "type": "integer"
          },
          "conversion_rate": {
            "type": "number"
          }
        }
      },
      "ReportWorksheetStatus": {
        "type": "object",
        "required": [
          "draft",
          "waiting",
          "started",
          "completed",
          "delivered",
          "completion_rate"
        ],
        "description": "Data of widget `worksheet-status`.",
        "properties": {
          "draft": {
            "type": "integer"
          },
          "waiting": {
            "type": "integer"
          },
          "started": {
            "type": "integer"
          },
          "completed": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "completion_rate": {
            "type": "number"
          }
        }
      },
      "ReportOutstandingWorksheets": {
        "type": "object",
        "required": [
          "worksheets"
        ],
        "description": "Data of widget `outstanding-worksheets`.",
        "properties": {
          "worksheets": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "worksheet_id",
                "title",
                "customer_name",
                "outstanding",
                "total",
                "bar_width"
              ],
              "properties": {
                "worksheet_id": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "customer_name": {
                  "type": "string"
                },
                "outstanding": {
                  "type": "number"
                },
                "total": {
                  "type": "number"
                },
                "bar_width": {
                  "type": "number",
                  "description": "Server-normalized bar length, 0-100."
                }
              }
            }
          }
        }
      },
      "ReportAppointments": {
        "type": "object",
        "required": [
          "appointments"
        ],
        "description": "Data of widget `upcoming-appointments`.",
        "properties": {
          "appointments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "title",
                "customer_name",
                "start_time"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "customer_name": {
                  "type": "string"
                },
                "start_time": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "ReportRankedList": {
        "type": "object",
        "required": [
          "rows"
        ],
        "description": "Data of the ranked-list widgets (`top-customers`,\n`top-services`, `worksheet-margins`). `amount` carries the sort\nmetric; `bar_width` is server-normalized 0-100. The margins\nwidget also fills `sublabel` (document number) and `percent`.\n",
        "properties": {
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "label",
                "amount",
                "bar_width"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "sublabel": {
                  "type": "string"
                },
                "amount": {
                  "type": "number"
                },
                "percent": {
                  "type": "number"
                },
                "bar_width": {
                  "type": "number"
                }
              }
            }
          }
        }
      },
      "TimeType": {
        "type": "object",
        "x-since": "2026-10-25",
        "x-description-it": "Un tipo di tempo del Marcatempo (Lavorazione, Diagnosi, Attesa ricambi…).",
        "description": "One of the workshop's Marcatempo time types, set up in\nImpostazioni → Tempi. The first use seeds a single `Lavorazione`.\nA Lavoro type (`is_wait: false`) is a person's own timer and counts\nas worked time; an Attesa type (`is_wait: true`) is the worksheet's\ntime with no person (car in the bay, waiting for parts) and never\ncounts as worked.\n",
        "required": [
          "object",
          "id",
          "name",
          "is_wait",
          "position"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_type"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "is_wait": {
            "type": "boolean",
            "description": "Attesa type — the worksheet's time, no person, never counted as worked."
          },
          "position": {
            "type": "integer",
            "description": "Display order, 0 first. The first type is the big Avvia."
          }
        }
      },
      "TimePerson": {
        "type": "object",
        "x-since": "2026-10-25",
        "description": "The collaboratore a time belongs to.",
        "required": [
          "object",
          "id",
          "first_name",
          "last_name"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_person"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          }
        }
      },
      "TimeEntry": {
        "type": "object",
        "x-since": "2026-10-25",
        "x-description-it": "Un tempo registrato su una scheda lavoro: un timer acceso o fermato, oppure un tempo inserito a mano.",
        "description": "One time recorded on a worksheet: a running timer, a stopped one,\nor a time typed by hand. Instants are UTC. The `can_*` flags are\nalways `false` on the public API (it is read-only); in the app they\nare the server's answers for the caller: `can_edit` only on saved\ntimes, `can_stop` / `can_discard` only on running ones.\n",
        "required": [
          "object",
          "id",
          "worksheet_id",
          "time_type_id",
          "user",
          "started_at",
          "ended_at",
          "duration_seconds",
          "running",
          "note",
          "source",
          "forgotten",
          "suggested_stop_at",
          "can_edit",
          "can_stop",
          "can_discard"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_entry"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "worksheet_id": {
            "type": "string",
            "format": "uuid"
          },
          "time_type_id": {
            "type": "string",
            "format": "uuid"
          },
          "user": {
            "description": "Whose work it is; `null` for an Attesa time (the worksheet's, no person).",
            "oneOf": [
              {
                "$ref": "#/components/schemas/TimePerson"
              },
              {
                "type": "null"
              }
            ]
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "`null` while the timer runs."
          },
          "duration_seconds": {
            "type": "integer",
            "description": "Length in seconds; a running timer counts up to the response's `now`."
          },
          "running": {
            "type": "boolean"
          },
          "note": {
            "type": "string",
            "description": "Free note, empty when none."
          },
          "source": {
            "type": "string",
            "enum": [
              "timer",
              "manual"
            ],
            "description": "`timer` started with Avvia, `manual` typed by hand."
          },
          "forgotten": {
            "type": "boolean",
            "description": "A running Lavoro timer left on (past midnight in Rome, or past\nthe shop's \"dimenticato dopo\" hours). Stopping it needs an\nexplicit `ended_at`.\n"
          },
          "suggested_stop_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The proposed end of a forgotten timer; `null` otherwise."
          },
          "can_edit": {
            "type": "boolean",
            "description": "The caller may correct or delete this saved time (always false while running)."
          },
          "can_stop": {
            "type": "boolean",
            "description": "The caller may stop this running timer (and answer \"Sto ancora lavorando\")."
          },
          "can_discard": {
            "type": "boolean",
            "description": "The caller may throw this running timer away."
          }
        }
      },
      "Timer": {
        "type": "object",
        "x-since": "2026-10-25",
        "description": "One time type's timer card. A Lavoro card is the caller's own timer;\nan Attesa card is the worksheet's, shown to everyone on it. Idle cards\nare listed only while the worksheet is open; on a closed one only\nrunning timers remain.\n",
        "required": [
          "object",
          "time_type",
          "primary",
          "running",
          "today_minutes"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "timer"
          },
          "time_type": {
            "$ref": "#/components/schemas/TimeType"
          },
          "primary": {
            "type": "boolean",
            "description": "The first type, the big Avvia."
          },
          "running": {
            "description": "The running timer of this card, `null` when idle.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/TimeEntry"
              },
              {
                "type": "null"
              }
            ]
          },
          "today_minutes": {
            "type": "integer",
            "description": "Minutes the caller already worked here today (Rome day, stopped times); 0 on Attesa cards."
          }
        }
      },
      "TimeNotice": {
        "type": "object",
        "x-since": "2026-10-25",
        "description": "What an action did besides the obvious, to show above the times (e.g. another timer was closed).",
        "required": [
          "object",
          "title",
          "message"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_notice"
          },
          "title": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "TimeSummary": {
        "type": "object",
        "x-since": "2026-10-25",
        "x-description-it": "Il riepilogo della scheda: ore lavorate, ore fatturate, differenza e resa.",
        "description": "The worksheet's figures (full picture only). Minutes are rounded to\nthe whole minute. Worked = Lavoro times, running ones up to `now`;\nbilled = labor lines in hours (`h`), minutes (`min`) or days (`gg`,\nat `day_hours`); flat-rate lines (`lav`) have no measurable time and\nare only counted in `flat_rate_lines`.\n",
        "required": [
          "object",
          "worked_minutes",
          "billed_minutes",
          "wait_minutes",
          "difference_minutes",
          "flat_rate_lines",
          "resa_percent",
          "labor_revenue",
          "revenue_per_hour",
          "includes_running"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_summary"
          },
          "worked_minutes": {
            "type": "integer",
            "description": "Ore lavorate",
            "in minutes.": null
          },
          "billed_minutes": {
            "type": "integer",
            "description": "Ore fatturate in manodopera",
            "in minutes.": null
          },
          "wait_minutes": {
            "type": "integer",
            "description": "Attesa time",
            "never counted as worked.": null
          },
          "difference_minutes": {
            "type": "integer",
            "description": "Billed minus worked; negative when more was worked than billed."
          },
          "flat_rate_lines": {
            "type": "integer",
            "description": "Number of flat-rate labor lines (`lav`, non misurabile)."
          },
          "resa_percent": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Resa, billed over worked in percent; `null` before a minute of work."
          },
          "labor_revenue": {
            "type": [
              "number",
              "null"
            ],
            "description": "Net labor revenue (quantity × price after the line discount, before VAT, flat-rate lines included); `null` without `prices.read`."
          },
          "revenue_per_hour": {
            "type": [
              "number",
              "null"
            ],
            "description": "Labor revenue per worked hour; `null` without `prices.read` or before a minute of work."
          },
          "includes_running": {
            "type": "boolean",
            "description": "A timer is running, so the figures keep growing."
          }
        }
      },
      "TimePersonTotal": {
        "type": "object",
        "x-since": "2026-10-25",
        "description": "One collaboratore's worked time on the worksheet (full picture only).",
        "required": [
          "object",
          "user",
          "worked_minutes",
          "share_percent",
          "running"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_person_total"
          },
          "user": {
            "$ref": "#/components/schemas/TimePerson"
          },
          "worked_minutes": {
            "type": "integer"
          },
          "share_percent": {
            "type": "integer",
            "description": "Share of the worksheet's worked minutes, in percent."
          },
          "running": {
            "type": "boolean",
            "description": "Their timer is running here."
          }
        }
      },
      "TimeTypeTotal": {
        "type": "object",
        "x-since": "2026-10-25",
        "description": "The time recorded per type (full picture only), one row per type, Attesa included.",
        "required": [
          "object",
          "time_type_id",
          "total_minutes",
          "running"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_type_total"
          },
          "time_type_id": {
            "type": "string",
            "format": "uuid"
          },
          "total_minutes": {
            "type": "integer"
          },
          "running": {
            "type": "boolean"
          }
        }
      },
      "TimeBilledLine": {
        "type": "object",
        "x-since": "2026-10-25",
        "description": "A labor line of the worksheet that makes up the billed hours (full picture only).",
        "required": [
          "object",
          "description",
          "quantity",
          "unit_type",
          "minutes"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "time_billed_line"
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "unit_type": {
            "type": "string",
            "enum": [
              "h",
              "min",
              "gg",
              "lav"
            ]
          },
          "minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The line in minutes; `null` for a flat-rate line (`lav`, non misurabile)."
          }
        }
      },
      "WorksheetTimes": {
        "type": "object",
        "x-since": "2026-10-25",
        "x-description-it": "Il Marcatempo di una scheda lavoro: timer, tempi registrati e, per il titolare, il riepilogo.",
        "description": "Everything the worksheet's Marcatempo tab shows, already filtered for\nthe caller and with the permission answers on every row. The titolare\n(`full_picture: true`, permission `settings_company.read`) sees\neveryone's times, the colleagues' running timers, `summary`, `people`,\n`type_totals` and `billed_lines`; anyone else sees only their own\ntimes, and those arrays are empty and `summary` is `null`.\n",
        "required": [
          "object",
          "worksheet_id",
          "now",
          "closed",
          "locked",
          "full_picture",
          "can_add",
          "can_add_wait",
          "day_hours",
          "notice",
          "time_types",
          "timers",
          "colleagues",
          "entries",
          "summary",
          "people",
          "type_totals",
          "billed_lines",
          "assignees"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "worksheet_times"
          },
          "worksheet_id": {
            "type": "string",
            "format": "uuid"
          },
          "now": {
            "type": "string",
            "format": "date-time",
            "description": "The server's clock for this answer; running durations count up to it."
          },
          "closed": {
            "type": "boolean",
            "description": "Worksheet delivered or archived — no timer starts, times can still be typed by hand."
          },
          "locked": {
            "type": "boolean",
            "description": "The plan has no Marcatempo — reading works, recording answers 402; a timer left running can still be stopped."
          },
          "full_picture": {
            "type": "boolean",
            "description": "The titolare's view."
          },
          "can_add": {
            "type": "boolean",
            "description": "The caller may type a time by hand (shown even when `locked`, the write then answers 402). Always `false` on the public API."
          },
          "can_add_wait": {
            "type": "boolean",
            "description": "The caller may type an Attesa time by hand (the titolare only). Always `false` on the public API."
          },
          "day_hours": {
            "type": "number",
            "description": "Hours in a working day, used to turn `gg` labor lines into minutes."
          },
          "notice": {
            "description": "What the action did besides the obvious; `null` on reads.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/TimeNotice"
              },
              {
                "type": "null"
              }
            ]
          },
          "time_types": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimeType"
            }
          },
          "timers": {
            "type": "array",
            "description": "The caller's own timer card per time type, in type order. Always empty on the public API.",
            "items": {
              "$ref": "#/components/schemas/Timer"
            }
          },
          "colleagues": {
            "type": "array",
            "description": "Other people's running Lavoro timers on this worksheet (full picture only); on the public API every running Lavoro timer.",
            "items": {
              "$ref": "#/components/schemas/TimeEntry"
            }
          },
          "entries": {
            "type": "array",
            "description": "Running first, then newest start first; the caller's own unless full picture.",
            "items": {
              "$ref": "#/components/schemas/TimeEntry"
            }
          },
          "summary": {
            "description": "The figures; `null` unless full picture.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/TimeSummary"
              },
              {
                "type": "null"
              }
            ]
          },
          "people": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimePersonTotal"
            }
          },
          "type_totals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimeTypeTotal"
            }
          },
          "billed_lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TimeBilledLine"
            }
          },
          "assignees": {
            "type": "array",
            "description": "Always empty on the public API (the team is `/v1/team`); the app's Meccanico picker.",
            "items": {
              "$ref": "#/components/schemas/TimePerson"
            }
          }
        }
      },
      "DeletedRecord": {
        "type": "object",
        "x-since": "2026-10-22",
        "description": "One soft-deleted record of the Cestino, whatever its type,\nnormalised to the page's columns. Amounts of invoices and receipts\nare derived from the source worksheet, sale or rental, never from\nthe document's own snapshot.\n",
        "required": [
          "object",
          "type",
          "id",
          "seq_number",
          "label",
          "meta",
          "customer_name",
          "amount",
          "issued",
          "number_unused",
          "numbered_at",
          "deleted_at",
          "deleted_by"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "deleted_record"
          },
          "type": {
            "type": "string",
            "enum": [
              "invoice",
              "receipt",
              "credit_note",
              "worksheet",
              "quote",
              "customer",
              "vehicle",
              "article",
              "calendar_entry",
              "checkin",
              "expense",
              "order",
              "supplier",
              "tyre_set",
              "delivery_note",
              "vehicle_sale",
              "rental_schedule"
            ]
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Progressive number; null for drafts and records without a series."
          },
          "label": {
            "type": "string",
            "description": "Main line when there is no number, such as the name, the title or the plate."
          },
          "meta": {
            "type": "string",
            "description": "Second line, such as the vehicle, the type, the code or the date. Empty when none."
          },
          "customer_name": {
            "type": "string"
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Document total net of discount; null for records without one."
          },
          "issued": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Fiscal documents only, whether it had a number or an issue date when deleted. Null otherwise."
          },
          "number_unused": {
            "type": "boolean",
            "description": "A numbered fiscal document deleted before the finality rule, whose number sits unused in the series."
          },
          "numbered_at": {
            "type": "string",
            "format": "date-time",
            "description": "The date its number is formatted with (issue date, else creation)."
          },
          "deleted_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Name of the user who deleted it, from the events log; null when unknown."
          }
        }
      },
      "TrashPeriod": {
        "type": "object",
        "x-since": "2026-10-22",
        "description": "The history window applied to the page.",
        "required": [
          "object",
          "key",
          "label",
          "days"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "trash_period"
          },
          "key": {
            "type": "string",
            "enum": [
              "c30",
              "c60",
              "c180",
              "c365",
              "all"
            ]
          },
          "label": {
            "type": "string"
          },
          "days": {
            "type": "integer",
            "description": "-1 = the whole history."
          },
          "from": {
            "type": "string",
            "format": "date",
            "description": "Omitted for `all`."
          },
          "to": {
            "type": "string",
            "format": "date",
            "description": "Omitted for `all`."
          }
        }
      },
      "TrashPeriodOption": {
        "type": "object",
        "x-since": "2026-10-22",
        "description": "One entry of the period control with its plan state.",
        "required": [
          "object",
          "key",
          "label",
          "days",
          "allowed"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "trash_period_option"
          },
          "key": {
            "type": "string",
            "enum": [
              "c30",
              "c60",
              "c180",
              "c365",
              "all"
            ]
          },
          "label": {
            "type": "string"
          },
          "days": {
            "type": "integer"
          },
          "allowed": {
            "type": "boolean",
            "description": "Whether the plan window covers it; a request for a locked one answers 402."
          }
        }
      },
      "TrashCounts": {
        "type": "object",
        "x-since": "2026-10-22",
        "description": "Per-tab counts for the active search and period.",
        "required": [
          "object",
          "all",
          "invoice",
          "receipt",
          "worksheet",
          "quote",
          "customer",
          "vehicle",
          "article",
          "other"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "trash_counts"
          },
          "all": {
            "type": "integer"
          },
          "invoice": {
            "type": "integer"
          },
          "receipt": {
            "type": "integer"
          },
          "worksheet": {
            "type": "integer"
          },
          "quote": {
            "type": "integer"
          },
          "customer": {
            "type": "integer"
          },
          "vehicle": {
            "type": "integer"
          },
          "article": {
            "type": "integer"
          },
          "other": {
            "type": "integer"
          }
        }
      },
      "BillingInfo": {
        "type": "object",
        "required": [
          "object",
          "state",
          "is_legacy_plan",
          "cancel_at_period_end",
          "unlimited_credits"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "billing_info"
          },
          "state": {
            "type": "string",
            "enum": [
              "trial",
              "trial_expired",
              "active",
              "payment_processing",
              "payment_issue"
            ]
          },
          "plan_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_legacy_plan": {
            "type": "boolean"
          },
          "billing_interval": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "monthly",
              "yearly",
              null
            ]
          },
          "trial_ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "current_period_end": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancel_at_period_end": {
            "type": "boolean"
          },
          "credit_balance": {
            "type": [
              "number",
              "null"
            ]
          },
          "unlimited_credits": {
            "type": "boolean"
          }
        }
      },
      "SupportChannel": {
        "type": "object",
        "description": "One support channel's schedule and current availability. Labels\narrive display-ready in Italian; `open_now` is evaluated in Rome\ntime at request time, so the app renders availability badges\nwithout owning the schedule.\n",
        "required": [
          "schedule_label",
          "open_now",
          "response_time"
        ],
        "properties": {
          "schedule_label": {
            "type": "string",
            "example": "Dal lunedì al sabato dalle 9:00 alle 21:00"
          },
          "open_now": {
            "type": "boolean"
          },
          "response_time": {
            "type": "string",
            "example": "30 minuti"
          }
        }
      },
      "SupportInfo": {
        "type": "object",
        "required": [
          "object",
          "agent_name",
          "email",
          "whatsapp_number",
          "email_channel",
          "whatsapp_channel",
          "team_note",
          "priority_support"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "support_info"
          },
          "agent_name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "whatsapp_number": {
            "type": "string",
            "description": "Display form, e.g. \"+39 351 761 1637\"."
          },
          "email_channel": {
            "$ref": "#/components/schemas/SupportChannel"
          },
          "whatsapp_channel": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SupportChannel"
              }
            ],
            "description": "`open_now` covers calls — outside the window messages still\nwork (badge \"Solo messaggi\").\n"
          },
          "team_note": {
            "type": "string",
            "description": "Ready-made availability footer for the support hub."
          },
          "priority_support": {
            "type": "boolean"
          }
        }
      },
      "Calendar": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "default_color"
        ],
        "description": "A calendar container grouping entries (e.g. Riparazioni,\nRevisioni). Entries carry their own `color`; `default_color`\nis the calendar's colour used for its filter dot and as the\ndefault for new entries.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "calendar"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "default_color": {
            "type": "string"
          }
        }
      },
      "Manufacturer": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name"
        ],
        "description": "A vehicle make from the manufacturer catalog (global rows plus\ncompany-specific additions). Read-only on this tier.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "manufacturer"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "Article": {
        "type": "object",
        "required": [
          "object",
          "id",
          "code",
          "name",
          "kind",
          "unit_type",
          "stock",
          "sale_price",
          "vat_code",
          "unit_discount_rate"
        ],
        "description": "A catalog row backing both the magazzino (parts) and the listino\nmanodopera (labor) — `kind` discriminates the two from the unit\ntype. `sale_price` is the NET unit price (VAT excluded), with\nthe customer's price-list override applied when the request\ncarried a `customer_id`; feed it straight into a line item's\n`unit_price`.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "article"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "item",
              "work"
            ],
            "description": "\"item\" = magazzino (kg, L, kit, pz); \"work\" = manodopera (gg, h, lav, min)."
          },
          "unit_type": {
            "type": "string"
          },
          "stock": {
            "type": "number"
          },
          "sale_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "vat_code": {
            "type": "string"
          },
          "unit_discount_rate": {
            "type": [
              "integer",
              "null"
            ]
          },
          "minimum_stock": {
            "type": [
              "number",
              "null"
            ]
          },
          "purchase_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "supplier_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "supplier_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "version": {
            "type": "integer",
            "description": "Detail responses only. Send it back as `If-Match` on PATCH: a save\nfrom an older copy is refused with 409. Sales do not change it;\nreceiving a supplier order (which updates the purchase price)\ndoes.\n"
          },
          "work_category_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-04",
            "description": "The work categories (\"Categoria\") the article is used for,\nin the shop's order. `[]` when none — never null. On list rows\nand detail alike.\n"
          },
          "work_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkCategory"
            },
            "x-since": "2026-10-04",
            "description": "The same list as `work_category_ids` with each category's name,\nsame order. Available since revision 2026-10-04.\n"
          }
        }
      },
      "SendContext": {
        "type": "object",
        "required": [
          "object",
          "email",
          "sms",
          "whatsapp",
          "credit_balance"
        ],
        "description": "Defaults for the Invia al cliente sheet (quotes and worksheets):\nrecipients, precompiled bodies (share link included), the\nread-only WhatsApp template preview, per-channel credit costs\n(0 = included in the plan) and the current balance.\n\nSMS/WhatsApp recipients are Italian-only: when the stored phone\nis empty or +39 the client shows an editable +39 national-number\nfield (the typed number is backfilled onto the customer on a\nsuccessful send when previously empty); a stored non-+39 number\nrenders read-only with the send disabled (web parity).\n",
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "quote_send_context",
              "worksheet_send_context"
            ]
          },
          "email": {
            "type": "object",
            "required": [
              "recipient",
              "subject",
              "body",
              "senders"
            ],
            "properties": {
              "recipient": {
                "type": "string"
              },
              "subject": {
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "senders": {
                "type": "array",
                "description": "The web \"Mittente\" dropdown options: internal always;\nsmtp/gmail when the company configured them (in the web\nsettings — configuration is not exposed here). Exactly\none entry has default=true.\n",
                "items": {
                  "type": "object",
                  "required": [
                    "key",
                    "label",
                    "default"
                  ],
                  "properties": {
                    "key": {
                      "type": "string",
                      "enum": [
                        "internal",
                        "smtp",
                        "gmail"
                      ]
                    },
                    "label": {
                      "type": "string",
                      "example": "Account Gmail (x@gmail.com)"
                    },
                    "default": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "sms": {
            "type": "object",
            "required": [
              "recipient",
              "body",
              "max_length",
              "credit_cost"
            ],
            "properties": {
              "recipient": {
                "type": "string"
              },
              "body": {
                "type": "string"
              },
              "max_length": {
                "type": "integer",
                "const": 160
              },
              "credit_cost": {
                "type": "number"
              }
            }
          },
          "whatsapp": {
            "type": "object",
            "required": [
              "recipient",
              "template_preview",
              "credit_cost"
            ],
            "properties": {
              "recipient": {
                "type": "string"
              },
              "template_preview": {
                "type": "string",
                "description": "Pre-approved template — display-only."
              },
              "credit_cost": {
                "type": "number"
              }
            }
          },
          "credit_balance": {
            "type": "number"
          }
        }
      },
      "SendRequest": {
        "type": "object",
        "required": [
          "channel",
          "recipient"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "whatsapp"
            ]
          },
          "recipient": {
            "type": "string"
          },
          "subject": {
            "type": "string",
            "maxLength": 50,
            "description": "email only (required there)."
          },
          "message": {
            "type": "string",
            "description": "email/sms body; ignored for whatsapp."
          },
          "sender_method": {
            "type": "string",
            "enum": [
              "internal",
              "smtp",
              "gmail"
            ],
            "description": "email only; omitted = company default. A missing or broken\nsmtp/gmail configuration falls back to the internal sender\n(web parity).\n"
          }
        }
      },
      "SendResult": {
        "type": "object",
        "required": [
          "object",
          "channel",
          "recipient",
          "credits_spent",
          "credit_balance"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "quote_send",
              "worksheet_send"
            ]
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "whatsapp"
            ]
          },
          "recipient": {
            "type": "string"
          },
          "credits_spent": {
            "type": "number"
          },
          "credit_balance": {
            "type": "number"
          }
        }
      },
      "CustomerMessage": {
        "type": "object",
        "required": [
          "object",
          "id",
          "channel",
          "title",
          "preview",
          "content",
          "recipient",
          "sent_at"
        ],
        "description": "One entry of the document's send log (Messaggi Cliente).",
        "properties": {
          "object": {
            "type": "string",
            "const": "customer_message"
          },
          "id": {
            "type": "string"
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "sms",
              "whatsapp"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "Email subject; null otherwise."
          },
          "preview": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "recipient": {
            "type": "string"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ShareLink": {
        "type": "object",
        "required": [
          "object",
          "url"
        ],
        "description": "Public customer-facing URL of a document. Get-or-create, so the\nURL is stable across calls; append it to the message when sending\nvia a device channel (WhatsApp / SMS / email deep links).\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "share_link"
          },
          "url": {
            "type": "string",
            "example": "https://officina.it/dl/aB3xK9"
          }
        }
      },
      "Sezionale": {
        "type": "object",
        "required": [
          "object",
          "id",
          "code",
          "label"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "sezionale"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        }
      },
      "WorkCategory": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "work_category"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "VatRate": {
        "type": "object",
        "required": [
          "object",
          "code",
          "rate",
          "description"
        ],
        "description": "A VAT rate enabled for the company. `code` is what line items\nreference via `vat_code`; `rate` is for display and client-side\ntotal previews — the server always re-resolves the rate from\nthe code when persisting.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "vat_rate"
          },
          "code": {
            "type": "string"
          },
          "rate": {
            "type": "number"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Vehicle": {
        "type": "object",
        "required": [
          "object",
          "id",
          "manufacturer_id",
          "manufacturer_name",
          "model",
          "is_rental",
          "created_at",
          "updated_at"
        ],
        "description": "A vehicle owned (or managed) by a workshop client.\nMulti-tenant: belongs to one company. `manufacturer_name`\nand `customer_name` are joined-in convenience fields\npopulated by the repository so list rows can render\n`Fiat Panda — Mario Rossi` without a follow-up request.\n\nRental fields are read-only on this resource. Mutate them\nvia the dedicated rental setup flow.\n",
        "properties": {
          "seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true,
            "minimum": 1,
            "example": 123,
            "description": "Number within this company's creation year (the year of created_at). Null for unnumbered legacy records."
          },
          "version": {
            "type": "integer",
            "description": "Detail, create and PATCH responses only. Send it back as\n`If-Match` on PATCH: a save from an older copy is refused with 409.\n"
          },
          "object": {
            "type": "string",
            "const": "vehicle"
          },
          "id": {
            "type": "string",
            "example": "2200ab13-7c48-4d52-b6e9-0a4f81c27d3e"
          },
          "license": {
            "type": [
              "string",
              "null"
            ],
            "description": "License plate (regional format, free-form string)."
          },
          "manufacturer_id": {
            "type": "string"
          },
          "manufacturer_name": {
            "type": "string",
            "description": "Joined from the manufacturers table for display.\nRead-only; mutate via `manufacturer_id`.\n"
          },
          "model": {
            "type": "string"
          },
          "variant": {
            "type": [
              "string",
              "null"
            ]
          },
          "vin": {
            "type": [
              "string",
              "null"
            ],
            "description": "Vehicle Identification Number."
          },
          "transmission": {
            "type": [
              "string",
              "null"
            ]
          },
          "engine_power_kw": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_displacement_ccm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_code": {
            "type": [
              "string",
              "null"
            ],
            "x-since": "2026-10-05",
            "description": "Codice motore. Stored trimmed and upper case (max 64 characters); an empty string or `null` clears it. When empty, officina.it may fill it from the vehicle's registration data. Shown in the app when `field_visibility.engine_code` is on."
          },
          "vehicle_weight_kg": {
            "type": [
              "integer",
              "null"
            ]
          },
          "engine_category": {
            "type": [
              "string",
              "null"
            ]
          },
          "registered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Joined from the owning customer's `full_name` for\ndisplay. Read-only; mutate via `customer_id`.\n"
          },
          "km": {
            "type": [
              "number",
              "null"
            ],
            "description": "Most recently recorded odometer reading."
          },
          "km_registered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_inspection_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_inspection_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_appointment": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Earliest upcoming calendar appointment for this vehicle,\ncomputed at read time.\n"
          },
          "custom_fields": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Raw stored values keyed by custom field definition `id`\n(see `VehicleCustomFieldValues` for the stored shapes).\nIncludes hidden fields, and on older records keys of\ndeleted definitions (dropped on the next PATCH that sends\n`custom_fields`).\n"
          },
          "custom_field_values": {
            "type": "array",
            "description": "Detail responses only. The company's visible custom field\ndefinitions (settings → Campi veicolo) resolved against\nthis vehicle: configured order, display-ready names, \"\"\nwhen the vehicle has no value. Clients render these\ndirectly — no definition lookup needed.\n",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "type",
                "value"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "string",
                    "integer",
                    "datetime",
                    "list"
                  ]
                },
                "options": {
                  "type": "array",
                  "x-since": "2026-10-23",
                  "items": {
                    "type": "string"
                  },
                  "description": "`list` fields only (omitted on the other types): the\ndropdown choices in order, so the picker renders\nwithout a settings lookup. `value` is one of them, or\n\"\" when unset.\n"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "documents": {
            "type": "array",
            "description": "Detail responses only — the vehicle's Documenti Allegati.\n`url` is the authenticated download route\n(`/v1/vehicles/{id}/documents/{documentID}/download`).\n",
            "items": {
              "$ref": "#/components/schemas/VehicleDocument"
            }
          },
          "field_visibility": {
            "type": "object",
            "description": "Detail responses only. The company's optional-field\ntoggles (settings → Campi veicolo): the edit form hides —\nand omits from its PATCH — exactly what the web form\nhides. All true when never configured.\n",
            "properties": {
              "km": {
                "type": "boolean"
              },
              "numero_telaio": {
                "type": "boolean"
              },
              "transmission": {
                "type": "boolean"
              },
              "engine_category": {
                "type": "boolean"
              },
              "engine_power_kw": {
                "type": "boolean"
              },
              "engine_displacement_ccm": {
                "type": "boolean"
              },
              "engine_code": {
                "type": "boolean",
                "x-since": "2026-10-05"
              },
              "vehicle_weight_kg": {
                "type": "boolean"
              },
              "registration_date": {
                "type": "boolean"
              },
              "last_inspection_date": {
                "type": "boolean"
              },
              "next_inspection_date": {
                "type": "boolean"
              }
            }
          },
          "is_rental": {
            "type": "boolean",
            "description": "Whether this vehicle is part of the workshop's rental\nfleet. Read-only here — managed by the rental setup flow.\n"
          },
          "rental_daily_price_before_tax": {
            "type": [
              "number",
              "null"
            ]
          },
          "rental_daily_price_tax": {
            "type": [
              "number",
              "null"
            ]
          },
          "rental_daily_price_after_tax": {
            "type": [
              "number",
              "null"
            ]
          },
          "rental_vat_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "rental_daily_discount_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Soft-deletion timestamp. Populated on the response from\nDELETE /v1/vehicles/{id}. Vehicles with a non-null\ndeleted_at are not returned by list/detail endpoints —\nthis field appears for the delete response only.\n"
          }
        }
      },
      "Worksheet": {
        "type": "object",
        "required": [
          "object",
          "id",
          "work_category_ids",
          "work_categories",
          "seq_number",
          "status",
          "customer_id",
          "created_by",
          "created_at",
          "updated_at"
        ],
        "description": "A workshop work order. Belongs to one customer and (usually)\none vehicle. Lifecycle: draft → waiting → started →\ncompleted → delivered → archived. Each state has a matching\nnullable `*_at` timestamp populated by the server when the\ntransition fires.\n\nTotals are server-computed from the embedded line items and\nrounded to the working currency precision. They are\nread-only on the wire.\n\n`line_items` is populated on detail / write responses and\nomitted (or `null`) on list responses to keep page payloads\nsmall.\n",
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "const": "worksheet"
          },
          "id": {
            "type": "string",
            "example": "a5d21e64-8f37-4b90-ae12-3c7d6b048f95"
          },
          "seq_number": {
            "type": "integer",
            "description": "Per-company human-friendly sequence number. Allocated\nserver-side at creation; never reused.\n"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "waiting",
              "started",
              "completed",
              "delivered",
              "archived"
            ]
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ],
            "description": "Workshop-internal notes; surfaced through the mobile\napp for the workshop's own users (no customer-facing\nchannel exposes this field).\n"
          },
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "assigned_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "km": {
            "type": [
              "number",
              "null"
            ],
            "description": "Odometer reading captured when the worksheet was opened.\nFree-form decimal because some workshops record fuel\nmileage or hour-meter values in the same field.\n"
          },
          "created_by": {
            "type": "string"
          },
          "created_by_full_name": {
            "type": "string",
            "description": "Display string of the user who created the worksheet —\ndenormalized for list rendering without a join.\n"
          },
          "customer_name": {
            "type": "string"
          },
          "vehicle_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "x-since": "2026-10-03",
            "description": "Every work category (\"categoria\") of the document, in\nthe order chosen. `work_category_id` (kept for clients that\nhandle a single category) always equals the first. `[]` when the document has none, never\nnull. Available since revision 2026-10-03.\n"
          },
          "work_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkCategory"
            },
            "x-since": "2026-10-03",
            "description": "The same list as `work_category_ids` with each category's\nname, same order. `work_category_name` is the first one's\nname. Available since revision 2026-10-03.\n"
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "total_after_tax": {
            "type": "number"
          },
          "total_after_discount": {
            "type": "number"
          },
          "total_discount": {
            "type": "number"
          },
          "total_price": {
            "type": "number"
          },
          "total_payments": {
            "type": "number"
          },
          "outstanding_payments": {
            "type": "number"
          },
          "fiscal_document_type": {
            "type": "string",
            "description": "Which fiscal document the worksheet produces: \"fattura\"\n(default) or \"scontrino\".\n"
          },
          "invoice_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detail responses only — the first linked invoice, when one\nexists. Drives the Vedi Fattura action.\n"
          },
          "receipt_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detail responses only — the linked scontrino for\nscontrino-type worksheets, when one exists.\n"
          },
          "linked_invoices": {
            "type": "array",
            "description": "Detail responses only — compact rows for the Fattura tab\n(fattura-type worksheets).\n",
            "items": {
              "$ref": "#/components/schemas/WorksheetFiscalDocument"
            }
          },
          "linked_receipt": {
            "description": "Detail responses only — the compact row for the Scontrino\ntab (scontrino-type worksheets), when one exists.\n",
            "allOf": [
              {
                "$ref": "#/components/schemas/WorksheetFiscalDocument"
              }
            ]
          },
          "frozen": {
            "type": "boolean",
            "description": "Detail responses only (since 2026-07-19) — true when a linked\ninvoice has a non-draft electronic invoice; mutations are\nrejected with 409 and the app hides/blocks Modifica.\n"
          },
          "total_work_cost": {
            "type": "number",
            "description": "Internal labour cost ex-VAT (Costi di lavorazione): the value\ntyped by hand when `work_cost_manual` is true, otherwise the sum\nof the lines' `unit_cost × quantity` for unit types h, gg, lav,\nmin. Correct on every revision and on list responses since\n2026-10-15 (lists used to return 0).\n"
          },
          "total_article_cost": {
            "type": "number",
            "description": "Internal parts cost ex-VAT: the value typed by hand when\n`article_cost_manual` is true, otherwise the sum of the other\nlines' `unit_cost × quantity`.\n"
          },
          "work_cost_manual": {
            "type": "boolean",
            "x-since": "2026-10-15",
            "description": "True when `total_work_cost` was typed by hand; line changes no\nlonger move it. Reset with `PATCH` `total_work_cost: null`.\n"
          },
          "article_cost_manual": {
            "type": "boolean",
            "x-since": "2026-10-15",
            "description": "True when `total_article_cost` was typed by hand."
          },
          "waiting_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Soft-deletion timestamp. Populated on the response from\nDELETE /v1/worksheets/{id}. Worksheets with a non-null\ndeleted_at are not returned by list/detail endpoints.\n"
          },
          "line_items": {
            "type": [
              "array",
              "null"
            ],
            "description": "Embedded line items. Populated on detail / create / patch\n/ delete / transition responses. List responses set this\nto `null` to keep the payload small.\n",
            "items": {
              "$ref": "#/components/schemas/WorkLineItem"
            }
          },
          "documents": {
            "type": [
              "array",
              "null"
            ],
            "description": "Attachments (Documenti Allegati). Populated on the same\nresponses as `line_items`; null on lists.\n",
            "items": {
              "$ref": "#/components/schemas/WorksheetDocument"
            }
          }
        }
      },
      "WorksheetStatusCounts": {
        "type": "object",
        "required": [
          "object",
          "draft",
          "waiting",
          "started",
          "completed",
          "delivered",
          "archived",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "worksheet_status_counts"
          },
          "draft": {
            "type": "integer"
          },
          "waiting": {
            "type": "integer"
          },
          "started": {
            "type": "integer"
          },
          "completed": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "archived": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "QuoteStatusCounts": {
        "type": "object",
        "required": [
          "object",
          "waiting",
          "accepted",
          "rejected",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "quote_status_counts"
          },
          "draft": {
            "type": "integer",
            "x-since": "2026-10-28",
            "description": "Drafts. Omitted for older revisions, whose `waiting` includes\nthem.\n"
          },
          "waiting": {
            "type": "integer"
          },
          "accepted": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "InvoicePaymentCounts": {
        "type": "object",
        "required": [
          "object",
          "pending",
          "settled",
          "date_filter"
        ],
        "description": "Tab counters for the invoices list. pending = outstanding payments \u003e 0, settled = fully paid.",
        "properties": {
          "object": {
            "type": "string",
            "const": "invoice_payment_counts"
          },
          "pending": {
            "type": "integer"
          },
          "settled": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "CustomerDocument": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "content_type",
          "url",
          "file_size_bytes",
          "created_at"
        ],
        "description": "One customer attachment. `url` is the document's authenticated\ndownload route\n(`/v1/customers/{id}/documents/{documentID}/download`) — fetch it\nwith the bearer to stream the bytes.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "customer_document"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "example": "/v1/customers/0b6b…/documents/77c2…/download"
          },
          "file_size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WorksheetDocument": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name",
          "content_type",
          "url",
          "file_size_bytes",
          "created_at"
        ],
        "description": "One worksheet attachment; `url` opens the stored file.",
        "properties": {
          "object": {
            "type": "string",
            "const": "worksheet_document"
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string"
          },
          "file_size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Payment": {
        "type": "object",
        "required": [
          "object",
          "id",
          "amount",
          "payment_method",
          "payment_date",
          "created_at"
        ],
        "description": "One registered payment on a worksheet's invoice. payment_method\nis an SDI ModalitaPagamento code (MP01–MP23).\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "payment"
          },
          "id": {
            "type": "string"
          },
          "amount": {
            "type": "number",
            "format": "double"
          },
          "payment_method": {
            "type": "string"
          },
          "payment_date": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PaymentCreateRequest": {
        "type": "object",
        "required": [
          "amount"
        ],
        "properties": {
          "amount": {
            "type": "number",
            "format": "double",
            "description": "Must be greater than zero."
          },
          "payment_method": {
            "type": "string",
            "description": "MP01–MP23. Omitted/invalid values fall back to the\ncustomer's preferred method, then the company default.\n"
          },
          "payment_date": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD between 1900-01-01 and 2100-12-31 (same window as\n`WritableTimestamp`); default today. Out of range → 422\n`parameter_invalid`.\n"
          },
          "financial_account_id": {
            "type": "string",
            "description": "Conto di saldo (Banca, Cassa, …) — one of the company's\naccounts from GET /v1/financial-accounts. Optional; an\naccount belonging to another company is rejected with 422.\n"
          }
        },
        "additionalProperties": false
      },
      "WorkLineItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "line_type",
          "description",
          "quantity",
          "unit_type",
          "unit_price",
          "tax_rate",
          "total_price",
          "position",
          "created_at"
        ],
        "description": "A single line on a worksheet. Returned read-only on every\nworksheet response; mutate via\n`POST /v1/worksheets/{id}/line-items/batch`.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "work_line_item"
          },
          "id": {
            "type": "string"
          },
          "line_type": {
            "type": "string",
            "enum": [
              "item",
              "note",
              "separator"
            ],
            "x-since": "2026-10-14",
            "description": "`item` is a priced line. `note` is a description-only line and\n`separator` an empty spacer row: both print without quantity,\nprice and VAT and carry zero amounts (ignore their numeric\nfields). Lines from documents without types report `item`.\n"
          },
          "article_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional link to a catalog article. When set, edits\nflow through the article inventory bookkeeping.\n"
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "unit_type": {
            "type": "string"
          },
          "unit_price": {
            "type": "number"
          },
          "unit_discount_rate": {
            "type": "number"
          },
          "tax_rate": {
            "type": "number"
          },
          "vat_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_price": {
            "type": "number"
          },
          "unit_cost": {
            "type": [
              "number",
              "null"
            ],
            "x-since": "2026-10-15",
            "description": "Internal cost per unit, ex-VAT, never printed on documents. Fixed\nwhen the line was written: the article's purchase price at that\nmoment, or the `unit_cost` sent. `null` when unknown.\n"
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "total_after_tax": {
            "type": "number"
          },
          "position": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Quote": {
        "type": "object",
        "required": [
          "object",
          "id",
          "work_category_ids",
          "work_categories",
          "seq_number",
          "status",
          "customer_id",
          "customer_name",
          "created_at",
          "updated_at"
        ],
        "description": "A customer-facing job estimate. Lifecycle: `draft` → `waiting` →\n`accepted` / `rejected` (a draft can also be accepted or rejected\ndirectly; nothing moves a quote back to `draft`). Totals are server-computed from the\nembedded line items and are read-only on the wire.\n",
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "const": "quote"
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "waiting",
              "accepted",
              "rejected"
            ],
            "description": "`draft` (revision 2026-10-28): internal, not yet sent to the\ncustomer — every new quote starts here. It becomes `waiting` when\nmarked as sent or when any message about it goes to the customer.\nOlder revisions receive drafts as `waiting`. A `waiting` quote\nwhose `expires_at` has passed and that has no `worksheet_id` is\nclosed as `rejected` by a nightly job (00:15 Rome), on every\nrevision.\n"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": "string"
          },
          "customer_name": {
            "type": "string"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "x-since": "2026-10-03",
            "description": "Every work category (\"categoria\") of the document, in\nthe order chosen. `work_category_id` (kept for clients that\nhandle a single category) always equals the first. `[]` when the document has none, never\nnull. Available since revision 2026-10-03.\n"
          },
          "work_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkCategory"
            },
            "x-since": "2026-10-03",
            "description": "The same list as `work_category_ids` with each category's\nname, same order. `work_category_name` is the first one's\nname. Available since revision 2026-10-03.\n"
          },
          "km": {
            "type": [
              "number",
              "null"
            ],
            "format": "double"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "x-since": "2026-10-27",
            "description": "Data scadenza: the last day the offer is valid (`YYYY-MM-DD`),\nexpired from the next day. `null` = no expiry. Available since\nrevision 2026-10-27.\n"
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "worksheet_seq_number": {
            "type": [
              "integer",
              "null"
            ]
          },
          "worksheet_created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "total_before_tax": {
            "type": "number",
            "format": "double"
          },
          "total_tax": {
            "type": "number",
            "format": "double"
          },
          "total_after_tax": {
            "type": "number",
            "format": "double"
          },
          "total_discount": {
            "type": "number",
            "format": "double"
          },
          "total_discount_before_tax": {
            "type": "number",
            "format": "double"
          },
          "total_after_discount": {
            "type": "number",
            "format": "double"
          },
          "total_work": {
            "type": "number",
            "format": "double"
          },
          "total_articles": {
            "type": "number",
            "format": "double"
          },
          "hide_discounts": {
            "type": "boolean"
          },
          "line_items": {
            "type": [
              "array",
              "null"
            ],
            "description": "Embedded line items. Populated on detail / write\nresponses; list responses set this to `null` to keep\npayloads small.\n",
            "items": {
              "$ref": "#/components/schemas/QuoteLineItem"
            }
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "QuoteLineItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "line_type",
          "description",
          "unit_type",
          "quantity",
          "unit_price",
          "position",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "quote_line_item"
          },
          "id": {
            "type": "string"
          },
          "line_type": {
            "type": "string",
            "enum": [
              "item",
              "note",
              "separator"
            ],
            "x-since": "2026-10-14",
            "description": "`item` is a priced line. `note` is a description-only line and\n`separator` an empty spacer row: both print without quantity,\nprice and VAT and carry zero amounts (ignore their numeric\nfields). Lines from documents without types report `item`.\n"
          },
          "article_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": "string"
          },
          "unit_type": {
            "type": "string"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit_price": {
            "type": "number",
            "format": "double"
          },
          "unit_discount_rate": {
            "type": "number",
            "format": "double"
          },
          "tax_rate": {
            "type": "number",
            "format": "double"
          },
          "vat_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_price": {
            "type": "number",
            "format": "double"
          },
          "unit_cost": {
            "type": [
              "number",
              "null"
            ],
            "x-since": "2026-10-15",
            "description": "Internal cost per unit, ex-VAT, never printed on documents. Fixed\nwhen the line was written (the article's purchase price at that\nmoment, or the `unit_cost` sent); converting the quote carries it\nto the worksheet's line. `null` when unknown.\n"
          },
          "total_before_tax": {
            "type": "number",
            "format": "double"
          },
          "total_tax": {
            "type": "number",
            "format": "double"
          },
          "total_after_tax": {
            "type": "number",
            "format": "double"
          },
          "position": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Invoice": {
        "type": "object",
        "required": [
          "object",
          "id",
          "work_category_names",
          "status",
          "customer_id",
          "seq_number",
          "payment_method",
          "payment_condition",
          "payment_due_at",
          "vat_exigibility",
          "total_before_tax",
          "total_tax",
          "total_after_tax",
          "total_discount",
          "total_after_discount",
          "total_payments",
          "outstanding_payments",
          "customer_name",
          "created_by_full_name",
          "sdi_status",
          "created_at",
          "updated_at"
        ],
        "description": "A workshop invoice. Multi-tenant: belongs to one company.\nCreated from a worksheet; transitions draft → issued via\n`POST /v1/invoices/{id}/issue`.\n",
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "const": "invoice"
          },
          "id": {
            "type": "string",
            "example": "4412e7b9-0c35-4a18-92d7-5b6ea1f38c04"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "issued"
            ],
            "description": "Derived from `issued_at`. Soft-deleted invoices 404\nbefore reaching the client — \"deleted\" is never emitted.\n"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_method": {
            "type": "string"
          },
          "payment_condition": {
            "type": "string"
          },
          "payment_due_at": {
            "type": "string",
            "format": "date-time"
          },
          "vat_exigibility": {
            "type": "string"
          },
          "sezionale_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_document_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_document_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_document_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pa_cig": {
            "type": [
              "string",
              "null"
            ]
          },
          "pa_cup": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "seq_number": {
            "type": "integer",
            "description": "Per-company monotonic invoice number. 0 on drafts;\nassigned atomically at issuance.\n"
          },
          "total_before_tax": {
            "type": "number",
            "format": "double",
            "description": "Money. Float64 — clients render via the workshop locale\nformatter; avoid arithmetic.\n"
          },
          "total_tax": {
            "type": "number",
            "format": "double"
          },
          "total_after_tax": {
            "type": "number",
            "format": "double"
          },
          "total_discount": {
            "type": "number",
            "format": "double"
          },
          "total_after_discount": {
            "type": "number",
            "format": "double"
          },
          "total_payments": {
            "type": "number",
            "format": "double"
          },
          "outstanding_payments": {
            "type": "number",
            "format": "double"
          },
          "customer_name": {
            "type": "string"
          },
          "vehicle_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by_full_name": {
            "type": "string"
          },
          "sezionale_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_names": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "x-since": "2026-10-03",
            "description": "Names of the linked worksheet's work categories, in order.\n`[]` when the invoice has no worksheet or the worksheet has\nnone. `work_category_name` is the first one.\nAvailable since revision 2026-10-03.\n"
          },
          "sdi_status": {
            "type": "string",
            "description": "Italian SDI e-invoice lifecycle status. Empty string\nwhen no e-invoice exists for this invoice. Possible\nnon-empty values: \"sent\", \"rejected\", \"pending_normal\",\n\"pending_warning\", \"pending_late\".\n"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Populated once the invoice is issued."
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Soft-deletion timestamp. Populated only on the response\nfrom DELETE /v1/invoices/{id}.\n"
          },
          "line_items": {
            "type": "array",
            "description": "Populated on detail and post-mutation responses. Omitted\nfrom list rows to keep payloads small.\n",
            "items": {
              "$ref": "#/components/schemas/InvoiceLineItem"
            }
          }
        }
      },
      "InvoiceLineItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "line_type",
          "description",
          "quantity",
          "unit_type",
          "unit_price",
          "unit_discount_rate",
          "tax_rate",
          "total_price",
          "total_before_tax",
          "total_tax",
          "total_after_tax",
          "created_at"
        ],
        "description": "A single row on an invoice. Read-only via this API — line\nitem add/remove is a separate concern not yet exposed on\nthe mobile surface.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "invoice_line_item"
          },
          "id": {
            "type": "string"
          },
          "line_type": {
            "type": "string",
            "enum": [
              "item",
              "note",
              "separator"
            ],
            "x-since": "2026-10-14",
            "description": "`item` is a priced line. `note` is a description-only line and\n`separator` an empty spacer row: both print without quantity,\nprice and VAT and carry zero amounts (ignore their numeric\nfields). Lines from documents without types report `item`.\n"
          },
          "article_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit_type": {
            "type": "string"
          },
          "unit_price": {
            "type": "number",
            "format": "double"
          },
          "unit_cost": {
            "type": [
              "number",
              "null"
            ],
            "format": "double"
          },
          "unit_discount_rate": {
            "type": "number",
            "format": "double"
          },
          "tax_rate": {
            "type": "number",
            "format": "double"
          },
          "vat_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "vat_description": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_price": {
            "type": "number",
            "format": "double"
          },
          "total_before_tax": {
            "type": "number",
            "format": "double"
          },
          "total_tax": {
            "type": "number",
            "format": "double"
          },
          "total_after_tax": {
            "type": "number",
            "format": "double"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreditNote": {
        "type": "object",
        "required": [
          "object",
          "id",
          "invoice_id",
          "customer_name",
          "vehicle_name",
          "vehicle_license",
          "sdi_status",
          "latest_sdi_status",
          "total_before_tax",
          "total_tax",
          "total_after_tax",
          "created_at",
          "updated_at"
        ],
        "description": "A credit note (nota di credito) — a derivative document\ncreated from a source invoice. Multi-tenant: belongs to one\ncompany. Totals mirror the source invoice and are not\nclient-editable. The only mutable field is `title`.\n",
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "const": "credit_note"
          },
          "id": {
            "type": "string",
            "example": "cnt_4412"
          },
          "invoice_id": {
            "type": "string",
            "description": "Source invoice UUID. Immutable."
          },
          "seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Per-company / per-sezionale monotonic number. May be\nnull on freshly created credit notes that have not yet\nbeen issued — clients render \"Bozza\" / draft when\nabsent.\n"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "sezionale_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "sezionale_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Denormalized sezionale code for display. Read-only;\nmutate via `sezionale_id` at creation time.\n"
          },
          "customer_name": {
            "type": "string",
            "description": "Denormalized display string of the source invoice's\ncustomer.\n"
          },
          "vehicle_name": {
            "type": "string",
            "description": "Denormalized display string of the linked vehicle."
          },
          "vehicle_license": {
            "type": "string"
          },
          "sdi_status": {
            "type": "string",
            "description": "Italian SDI e-invoice lifecycle status. Empty string\nwhen no e-credit-note exists.\n"
          },
          "latest_sdi_status": {
            "type": "string",
            "description": "Most recent terminal SDI status observed for this\ncredit note's electronic counterpart.\n"
          },
          "total_before_tax": {
            "type": "number",
            "format": "double"
          },
          "total_tax": {
            "type": "number",
            "format": "double"
          },
          "total_after_tax": {
            "type": "number",
            "format": "double"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Soft-deletion timestamp. Populated on the response from\nDELETE /v1/credit-notes/{id}. Credit notes with a\nnon-null deleted_at are not returned by list/detail\nendpoints — this field appears for the delete response\nonly.\n"
          }
        }
      },
      "Checkin": {
        "type": "object",
        "required": [
          "object",
          "id",
          "work_category_ids",
          "work_categories",
          "seq_number",
          "status",
          "customer_id",
          "vehicle_id",
          "customer_name",
          "vehicle_name",
          "created_by",
          "created_at",
          "updated_at"
        ],
        "description": "A vehicle intake / inspection snapshot. Belongs to one\ncompany, one customer, and one vehicle. List responses omit\n`line_items` to keep payloads small; detail responses\npopulate it.\n",
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "const": "checkin"
          },
          "id": {
            "type": "string",
            "example": "chk_4412"
          },
          "seq_number": {
            "type": "integer",
            "description": "Per-company human-readable sequence (e.g. \"Checkin #142\").\nAssigned at create time and stable afterwards.\n"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "completed"
            ],
            "description": "Lifecycle state. Mutated only through\n`POST /v1/checkins/{id}/transitions`, never via PATCH.\n"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ],
            "description": "Notes visible only to workshop staff."
          },
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": "string"
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when the checkin has been converted into a worksheet."
          },
          "quote_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when the checkin has been converted into a quote."
          },
          "fuel_level": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Free-form integer captured at intake. Convention is\n0–100 (percentage) but the API enforces no bounds today.\n"
          },
          "section1": {
            "type": [
              "string",
              "null"
            ]
          },
          "section2": {
            "type": [
              "string",
              "null"
            ]
          },
          "section3": {
            "type": [
              "string",
              "null"
            ]
          },
          "section4": {
            "type": [
              "string",
              "null"
            ]
          },
          "section5": {
            "type": [
              "string",
              "null"
            ]
          },
          "section6": {
            "type": [
              "string",
              "null"
            ]
          },
          "section7": {
            "type": [
              "string",
              "null"
            ]
          },
          "section8": {
            "type": [
              "string",
              "null"
            ]
          },
          "km": {
            "type": [
              "number",
              "null"
            ],
            "description": "Odometer reading at intake."
          },
          "created_by": {
            "type": "string",
            "description": "User id of the staff member who created the checkin."
          },
          "customer_name": {
            "type": "string",
            "description": "Denormalized display string of the linked customer.\nPre-computed on the entity for list-row rendering\nwithout a second request.\n"
          },
          "vehicle_name": {
            "type": "string",
            "description": "Denormalized display string of the linked vehicle."
          },
          "vehicle_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "x-since": "2026-10-03",
            "description": "Every work category (\"categoria\") of the document, in\nthe order chosen. `work_category_id` (kept for clients that\nhandle a single category) always equals the first. `[]` when the document has none, never\nnull. Available since revision 2026-10-03.\n"
          },
          "work_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkCategory"
            },
            "x-since": "2026-10-03",
            "description": "The same list as `work_category_ids` with each category's\nname, same order. `work_category_name` is the first one's\nname. Available since revision 2026-10-03.\n"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Soft-deletion timestamp. Populated on the response from\nDELETE /v1/checkins/{id}. Checkins with a non-null\ndeleted_at are not returned by list/detail endpoints —\nthis field appears for the delete response only.\n"
          },
          "line_items": {
            "type": [
              "array",
              "null"
            ],
            "description": "Embedded line items (description-only notes). Populated on\ndetail / batch responses; list GET emits `null` to keep\npayloads small. Mutate via\nPOST /v1/checkins/{id}/line-items/batch.\n",
            "items": {
              "$ref": "#/components/schemas/CheckinLineItem"
            }
          }
        }
      },
      "CheckinLineItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "description",
          "created_at"
        ],
        "description": "A description-only line item (note) on a checkin. Returned\nread-only on checkin responses; mutate via\nPOST /v1/checkins/{id}/line-items/batch.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "checkin_line_item"
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CalendarEntry": {
        "type": "object",
        "required": [
          "object",
          "id",
          "work_category_ids",
          "work_categories",
          "title",
          "user_id",
          "start_time",
          "end_time",
          "color",
          "email_reminder",
          "auto_reminder_enabled",
          "created_at",
          "updated_at"
        ],
        "description": "A workshop appointment. Belongs to one company (the\ncaller's) and is owned by the user who created it. May\nreference an optional Customer, Vehicle, scheda lavoro\n(`worksheet_id`), work category, and parent Calendar. While\n`worksheet_id` is set, `customer_id` is the worksheet's and so\nis `vehicle_id` whenever the worksheet has one.\n",
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "const": "calendar_entry"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "start_time": {
            "type": "string",
            "format": "date-time"
          },
          "end_time": {
            "type": "string",
            "format": "date-time"
          },
          "color": {
            "type": "string",
            "description": "Hex colour, e.g. #2563eb"
          },
          "email_reminder": {
            "type": "boolean"
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "quote_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "checkin_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "work_category_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "work_category_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_category_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "x-since": "2026-10-03",
            "description": "Every work category (\"categoria\") of the document, in\nthe order chosen. `work_category_id` (kept for clients that\nhandle a single category) always equals the first. `[]` when the document has none, never\nnull. Available since revision 2026-10-03.\n"
          },
          "work_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkCategory"
            },
            "x-since": "2026-10-03",
            "description": "The same list as `work_category_ids` with each category's\nname, same order. `work_category_name` is the first one's\nname. Available since revision 2026-10-03.\n"
          },
          "calendar_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "auto_reminder_enabled": {
            "type": "boolean"
          },
          "auto_reminder_channel": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "email",
              "sms",
              "whatsapp",
              null
            ]
          },
          "auto_reminder_sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "customer": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CalendarEntryCustomer"
              },
              {
                "type": "null"
              }
            ]
          },
          "vehicle": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CalendarEntryVehicle"
              },
              {
                "type": "null"
              }
            ]
          },
          "user": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CalendarEntryUser"
              },
              {
                "type": "null"
              }
            ]
          },
          "calendar": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CalendarEntryCalendar"
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "CalendarEntryCustomer": {
        "type": "object",
        "required": [
          "object",
          "id"
        ],
        "description": "Slim denormalized view of the linked customer, embedded\ninside a CalendarEntry. The full Customer resource lives at\n`/v1/customers/{id}`.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "customer"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CalendarEntryVehicle": {
        "type": "object",
        "required": [
          "object",
          "id",
          "model",
          "manufacturer_name"
        ],
        "description": "Slim denormalized view of the linked vehicle, embedded\ninside a CalendarEntry. The full Vehicle resource lives at\n`/v1/vehicles/{id}`.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "vehicle"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "license": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": "string"
          },
          "manufacturer_name": {
            "type": "string"
          }
        }
      },
      "CalendarEntryUser": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name"
        ],
        "description": "Slim denormalized view of the owning user, embedded inside a\nCalendarEntry.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "user"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "CalendarEntryCalendar": {
        "type": "object",
        "required": [
          "object",
          "id",
          "name"
        ],
        "description": "Slim denormalized view of the parent calendar, embedded\ninside a CalendarEntry. The Calendar container itself is\nnot exposed on this tier.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "calendar"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "CashBookEntry": {
        "type": "object",
        "required": [
          "object",
          "id",
          "payment_type",
          "amount",
          "payment_date",
          "financial_account_name",
          "running_balance"
        ],
        "description": "One row of the cashbook: either an income (linked to an\ninvoice or worksheet) or an expense (linked to a supplier\nbill). Computed from underlying payments — no first-class\nlifecycle.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "cashbook_entry"
          },
          "id": {
            "type": "string",
            "description": "Underlying payment UUID. Opaque to clients."
          },
          "payment_type": {
            "type": "string",
            "enum": [
              "income",
              "expense"
            ],
            "description": "Side of the ledger. Clients branch on this to decide\nwhich side-specific fields (invoice_/worksheet_ vs\nexpense_, customer_name vs supplier_name) are populated.\n"
          },
          "amount": {
            "type": "number",
            "format": "double",
            "description": "Signed payment amount in the workshop's currency.\nFloat64 matches the entity layer; the cashbook computes\na running balance, so integer cents would force clients\nto redo the math.\n"
          },
          "payment_date": {
            "type": "string",
            "format": "date-time",
            "description": "When the payment was recorded. RFC 3339 UTC."
          },
          "financial_account_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Account this payment hit. Nullable — legacy entries may\npredate the financial-accounts feature.\n"
          },
          "financial_account_name": {
            "type": "string",
            "description": "Denormalized account name. Empty string for legacy\nentries with no account assignment.\n"
          },
          "customer_name": {
            "type": "string",
            "description": "Income-side counterparty. Omitted from the JSON entirely\non expense rows (no empty key).\n"
          },
          "supplier_name": {
            "type": "string",
            "description": "Expense-side counterparty. Omitted from the JSON\nentirely on income rows.\n"
          },
          "invoice_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Source invoice UUID (income rows)."
          },
          "invoice_seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Sequence number of the linked issued invoice, if any.\nSet on income rows backed by an issued invoice.\n"
          },
          "invoice_issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Source worksheet UUID, set on income rows backed by a\nworksheet whose invoice is still a draft.\n"
          },
          "worksheet_seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Sequence number of the linked worksheet, set on income\nrows backed by a worksheet whose invoice is still a draft.\n"
          },
          "worksheet_created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "receipt_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Source receipt (scontrino) UUID (income rows)."
          },
          "receipt_seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Sequence number of the linked standalone scontrino."
          },
          "receipt_issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "expense_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Source expense UUID (expense rows)."
          },
          "expense_seq_number": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Sequence number of the linked supplier expense."
          },
          "expense_created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "manual_entry_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set on a manual prima nota movement — a row typed straight\ninto the ledger with no source document, so every other\n`*_id` above is null. Its value is the id used to edit or\ndelete the movement on the web app.\n"
          },
          "manual_description": {
            "type": "string",
            "description": "Free-text label of a manual movement, shown in place of the\ndocument reference other rows carry. Empty on every other row.\n"
          },
          "manual_payment_method": {
            "type": "string",
            "description": "Payment method code (MP01, MP05, …) of a manual movement.\nEmpty on every other row.\n"
          },
          "running_balance": {
            "type": "number",
            "format": "double",
            "description": "Cumulative balance after applying this row, starting from\n`opening_balance` on the list envelope. Computed by the\nserver in `order_direction` — do not re-sort client-side\nwithout recomputing.\n"
          }
        }
      },
      "WorkCategoryFilter": {
        "type": "object",
        "required": [
          "object",
          "work_category_ids",
          "work_categories",
          "label",
          "visible"
        ],
        "description": "The \"Categorie\" filter the server applied to a worksheet, quote or\ninvoice list (`work_category_id`), echoed on the list envelope.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "work_category_filter"
          },
          "work_category_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The ids filtered on, as sent. Empty = no filter."
          },
          "work_categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WorkCategory"
            },
            "description": "Those ids that are the company's categories, in order. An id\nmissing here matched nothing.\n"
          },
          "label": {
            "type": "string",
            "description": "Short text for \"Categorie: …\" — `tutte` with no filter, else the\nnames (`Freni, Impianto`) collapsing to `Freni +2` when long.\n",
            "example": "Freni +2"
          },
          "visible": {
            "type": "boolean",
            "description": "The company actively uses categories (its set differs from the\nfour defaults Tagliando, Revisione, Cambio Gomme, Carrozzeria).\nApps show the filter only when true.\n"
          }
        }
      },
      "DateFilter": {
        "type": "object",
        "required": [
          "object",
          "preset"
        ],
        "description": "The effective date range the server applied to a date-scoped\nlist request, echoed on the list envelope so clients can render\nthe active filter without re-deriving it.\n\nA request that sends no `from`/`to`/`preset` gets the default\nwindow, the last 12 months (`rel365`); nothing is carried over\nbetween requests. Relative presets are resolved here to concrete\ndates and roll forward over time.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "date_filter"
          },
          "preset": {
            "type": "string",
            "description": "Effective selection label. Relative (`rel30`/`rel90`/`rel365`)\nand absolute (`year`/`Q1`-`Q4`/`month`/`custom`) carry\n`from`/`to`; `all` omits them.\n",
            "enum": [
              "rel30",
              "rel90",
              "rel365",
              "year",
              "Q1",
              "Q2",
              "Q3",
              "Q4",
              "month",
              "custom",
              "all"
            ]
          },
          "from": {
            "type": "string",
            "format": "date",
            "description": "Inclusive lower bound (YYYY-MM-DD). Omitted when `preset` is `all`."
          },
          "to": {
            "type": "string",
            "format": "date",
            "description": "Inclusive upper bound (YYYY-MM-DD). Omitted when `preset` is `all`."
          }
        }
      },
      "CalendarFilter": {
        "type": "object",
        "required": [
          "object",
          "calendar_id"
        ],
        "description": "The effective calendar selection the server applied to a calendar-entry\ntimeframe listing, echoed on the list envelope so clients can render the\nactive calendar without re-deriving it.\n\nAn empty `calendar_id` means \"all calendars\"; a selection is not\ncarried over between requests.\nEchoed only on the default timeframe listing — `customer_id` /\n`vehicle_id` lookups don't apply a calendar filter.\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "calendar_filter"
          },
          "calendar_id": {
            "type": "string",
            "description": "Selected parent calendar id, or empty for all calendars."
          }
        }
      },
      "ListEnvelope": {
        "type": "object",
        "required": [
          "object",
          "data",
          "has_more",
          "url"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "list",
            "description": "Polymorphic discriminator."
          },
          "data": {
            "type": "array",
            "description": "Page of items. Each item carries its own `object` field.",
            "items": {}
          },
          "has_more": {
            "type": "boolean",
            "description": "True if more results exist beyond this page. Use this\nfor the \"load more\" UI — `data.length` alone is\nunreliable because a full page may still be the last\npage.\n"
          },
          "next_starting_after": {
            "type": "string",
            "description": "Opaque cursor for the next page. Present (and non-empty)\nwhen `has_more` is `true`; omitted otherwise. Pass the\nvalue verbatim as `?starting_after=` on the follow-up\nrequest. Clients MUST NOT parse it — today it encodes\nan offset, tomorrow it may encode a `(created_at, id)`\ntuple without a wire-visible change.\n"
          },
          "url": {
            "type": "string",
            "description": "The request path that produced this list."
          }
        }
      },
      "WorksheetFiscalDocument": {
        "type": "object",
        "required": [
          "object",
          "id",
          "seq_number",
          "issued_at",
          "created_at",
          "total_after_discount",
          "outstanding_payments"
        ],
        "description": "One compact linked fiscal document on a worksheet detail —\nseq_number 0 means a draft (\"Non emessa\" / \"Bozza\").\n",
        "properties": {
          "object": {
            "type": "string",
            "const": "worksheet_fiscal_document"
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_after_discount": {
            "type": "number",
            "format": "double"
          },
          "outstanding_payments": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "CashBookListEnvelope": {
        "description": "Extends `ListEnvelope` with the two fields the cashbook\nneeds to be self-consistent: `opening_balance` (the\nrunning-balance anchor) and `total_items` (the full\nfiltered count). The envelope's `{object, data, has_more,\nurl}` shape stays wire-compatible with the generic list\nparser; the extra fields are inert to clients that don't\nknow about them.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/ListEnvelope"
          },
          {
            "type": "object",
            "required": [
              "opening_balance",
              "total_items"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CashBookEntry"
                }
              },
              "opening_balance": {
                "type": "number",
                "format": "double",
                "description": "Running-balance anchor at the start of the `from`\nwindow. Zero when `from` is unset.\n"
              },
              "total_items": {
                "type": "integer",
                "description": "Full filtered count, ignoring pagination."
              },
              "date_filter": {
                "$ref": "#/components/schemas/DateFilter"
              }
            }
          }
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBody"
          }
        }
      },
      "ErrorBody": {
        "type": "object",
        "required": [
          "type",
          "status",
          "code",
          "message"
        ],
        "description": "Single error per response (fast-fail). Inspired by RFC 9457\nProblem Details, wrapped in an `error` root key so success\nand error bodies are visually distinguishable.\n",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI identifying the error class. Stable.",
            "example": "https://api.officina.it/errors/invalid_credentials"
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code, mirrored from the response.",
            "example": 401
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable identifier (snake_case). Clients\nbranch on this, never on `message`.\n",
            "example": "invalid_credentials"
          },
          "message": {
            "type": "string",
            "description": "Human-readable English explanation."
          },
          "param": {
            "type": "string",
            "description": "JSON Pointer (RFC 6901) to the offending request field.\nOmitted when the error isn't tied to a specific input\nfield (e.g. auth errors).\n",
            "example": "/email"
          },
          "trace_id": {
            "type": "string",
            "description": "Request trace ID for support correlation."
          },
          "candidates": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Only on `ambiguous_number`: the ids of every record the\nnumber matched, to retrieve the right one by id.\n"
          },
          "reason": {
            "type": "string",
            "enum": [
              "disabled",
              "fully_paid",
              "already_applied",
              "nothing_to_charge"
            ],
            "x-since": "2026-10-06",
            "description": "Only on `waste_disposal_not_applicable`: why the charge can't be\napplied to the document.\n"
          }
        }
      },
      "ArticleCreateRequest": {
        "type": "object",
        "description": "ArticleCreateRequest / ArticlePatchRequest — the magazzino and\nlistino manodopera editors. unit_type decides the kind.\n",
        "required": [
          "name",
          "code",
          "unit_type",
          "stock",
          "minimum_stock",
          "purchase_price",
          "sale_price",
          "vat_code",
          "unit_discount_rate",
          "location",
          "comments",
          "supplier_id"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "unit_type": {
            "type": "string"
          },
          "stock": {
            "type": [
              "number",
              "null"
            ]
          },
          "minimum_stock": {
            "type": [
              "number",
              "null"
            ]
          },
          "purchase_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "sale_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "vat_code": {
            "type": "string"
          },
          "unit_discount_rate": {
            "type": [
              "integer",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "supplier_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ArticleMovement": {
        "type": "object",
        "description": "ArticleMovement is one \"Storico lavorazioni\" row for an article /\nlavorazione — a sale movement where the item was used. Exactly one\nsource (worksheet / order / receipt) is set; SourceID is the id\nthe mobile client navigates to.\n",
        "required": [
          "object",
          "id",
          "quantity",
          "source",
          "source_id",
          "seq_number",
          "document_date",
          "customer_name",
          "vehicle_name",
          "total",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"article_movement\""
          },
          "id": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "source": {
            "type": "string",
            "description": "\"worksheet\" | \"order\" | \"receipt\" | \"\""
          },
          "source_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "seq_number": {
            "type": [
              "integer",
              "null"
            ]
          },
          "document_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CalendarListEnvelope": {
        "type": "object",
        "description": "CalendarListEnvelope: success body for GET /v1/calendars. Follows\nthe standard list envelope; the collection is unpaginated\n(workshops have a handful of calendars at most) so HasMore is\nalways false.\n",
        "required": [
          "object",
          "data",
          "has_more",
          "url"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"list\""
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Calendar"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "CheckinDocument": {
        "type": "object",
        "description": "CheckinDocument: one attachment on a checkin (Documenti Allegati).\nPopulated on detail responses only. URL points at the\nauthenticated download route.\n",
        "required": [
          "object",
          "id",
          "name",
          "content_type",
          "description",
          "url",
          "file_size_bytes",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"checkin_document\""
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string"
          },
          "file_size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CheckinStatusCounts": {
        "type": "object",
        "description": "CheckinStatusCounts: tab counters for the checkins list (web\ncheckin-list StatusCount parity), computed with the same q + date\nfilters as the list.\n",
        "required": [
          "object",
          "active",
          "completed",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"checkin_status_counts\""
          },
          "active": {
            "type": "integer"
          },
          "completed": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "WasteDisposalPreview": {
        "type": "object",
        "x-since": "2026-10-06",
        "description": "Whether the one-click \"smaltimento rifiuti\" charge can be applied to\na worksheet or quote, and what it would add.\n",
        "required": [
          "object",
          "applicable",
          "reason",
          "rate",
          "base",
          "description",
          "amount"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"waste_disposal_preview\""
          },
          "applicable": {
            "type": "boolean"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "disabled",
              "frozen",
              "fully_paid",
              "already_applied",
              "nothing_to_charge",
              null
            ],
            "description": "Null when applicable. `frozen` (POST answers `409 conflict`) and `fully_paid` only on worksheets."
          },
          "rate": {
            "type": "number",
            "description": "Percentage, 1.2 = 1,2%."
          },
          "base": {
            "type": "string",
            "enum": [
              "articles",
              "total"
            ]
          },
          "description": {
            "type": "string",
            "description": "The line description that would be added."
          },
          "amount": {
            "type": "number",
            "description": "Net line amount (before VAT) the charge would add; 0 when not applicable."
          }
        }
      },
      "CompanyPatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/company`. Three-state semantics\n(omitted = leave unchanged, explicit `null` = clear, concrete\nvalue = set) on the nullable fields. The\nnon-nullable ones — `name`, `entity_type`, `checkin_frame`,\n`document_template_name`, `default_payment_method`,\n`default_payment_due_at` and the booleans — ignore an explicit\n`null` rather than clearing.\n\n`name`, `entity_type`, `tax_id` and `fiscal_code` are rejected\nwith 409 while electronic invoicing is configured\n(`fiscal_fields_locked` in the response).\n\nEverything else about the officina — logo, sezionali, users — is\nnot patchable here.\n",
        "properties": {
          "name": {
            "type": "string"
          },
          "entity_type": {
            "type": "string",
            "enum": [
              "company",
              "individual"
            ]
          },
          "company_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "car_repair, tyre_repair, …"
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Partita IVA — 11 digits with a valid check digit and office code, checked when it changes (every revision)."
          },
          "fiscal_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored as +39…; a national number is normalised."
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "province": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inferred from the CAP when left empty."
          },
          "checkin_frame": {
            "type": "string",
            "enum": [
              "auto",
              "moto"
            ],
            "description": "The outline used on the checkin damage diagram."
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "document_template_name": {
            "type": "string"
          },
          "default_payment_method": {
            "type": "string"
          },
          "default_payment_due_at": {
            "type": "string"
          },
          "bank_account_iban": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored uppercase with no spaces. On writes it must pass the IBAN checksum (mod 97), else 422 on `/bank_account_iban`."
          },
          "bank_account_holder": {
            "type": [
              "string",
              "null"
            ]
          },
          "quote_hide_line_item_details": {
            "type": "boolean"
          },
          "worksheet_hide_line_item_details": {
            "type": "boolean"
          },
          "worksheet_line_item_code_enabled": {
            "type": "boolean"
          },
          "line_items_including_vat": {
            "type": "boolean"
          },
          "worksheet_km_required": {
            "type": "boolean"
          }
        },
        "additionalProperties": false
      },
      "CompanyProfile": {
        "type": "object",
        "description": "CompanyProfile — Informazioni officina (anagrafica scope).\n",
        "required": [
          "object",
          "id",
          "name",
          "entity_type",
          "company_type",
          "tax_id",
          "fiscal_code",
          "email",
          "phone",
          "phone_national",
          "address",
          "city",
          "zip_code",
          "province",
          "fiscal_fields_locked",
          "checkin_frame",
          "primary_color",
          "document_template_name",
          "default_payment_method",
          "default_payment_due_at",
          "bank_account_iban",
          "bank_account_holder",
          "quote_hide_line_item_details",
          "worksheet_hide_line_item_details",
          "worksheet_line_item_code_enabled",
          "line_items_including_vat",
          "worksheet_km_required",
          "waste_disposal_enabled",
          "waste_disposal_rate",
          "waste_disposal_base",
          "waste_disposal_description"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"company\""
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "entity_type": {
            "type": "string",
            "description": "company | individual"
          },
          "company_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "fiscal_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone_national": {
            "type": [
              "string",
              "null"
            ],
            "description": "The phone number without the +39 country code."
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "province": {
            "type": [
              "string",
              "null"
            ]
          },
          "fiscal_fields_locked": {
            "type": "boolean",
            "description": "True when electronic-invoicing registration locks the fiscal fields."
          },
          "checkin_frame": {
            "type": "string",
            "description": "\"auto\" | \"moto\" — the outline used on the checkin damage diagram."
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "document_template_name": {
            "type": "string"
          },
          "default_payment_method": {
            "type": "string"
          },
          "default_payment_due_at": {
            "type": "string"
          },
          "bank_account_iban": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored uppercase with no spaces. On writes it must pass the IBAN checksum (mod 97), else 422 on `/bank_account_iban`."
          },
          "bank_account_holder": {
            "type": [
              "string",
              "null"
            ]
          },
          "quote_hide_line_item_details": {
            "type": "boolean"
          },
          "worksheet_hide_line_item_details": {
            "type": "boolean"
          },
          "worksheet_line_item_code_enabled": {
            "type": "boolean"
          },
          "line_items_including_vat": {
            "type": "boolean"
          },
          "worksheet_km_required": {
            "type": "boolean"
          },
          "waste_disposal_enabled": {
            "type": "boolean",
            "x-since": "2026-10-06",
            "description": "Whether the one-click \"smaltimento rifiuti\" charge is offered (default false)."
          },
          "waste_disposal_rate": {
            "type": "number",
            "x-since": "2026-10-06",
            "description": "Percentage applied, 1.2 = 1,2%. 0 when not set."
          },
          "waste_disposal_base": {
            "type": "string",
            "enum": [
              "articles",
              "total"
            ],
            "x-since": "2026-10-06",
            "description": "`articles` = net of the goods lines only (ricambi), `total` = net of every line."
          },
          "waste_disposal_description": {
            "type": "string",
            "x-since": "2026-10-06",
            "description": "The line description the customer reads (default \"Smaltimento rifiuti\")."
          }
        }
      },
      "CustomerMessageEntity": {
        "type": "object",
        "description": "CustomerMessageEntity — one eligible target of the Invia messaggio\nflow (web customer-{channel}-entity-list parity).\n",
        "required": [
          "object",
          "id",
          "seq_number",
          "title",
          "vehicle_name",
          "vehicle_license",
          "created_at",
          "total"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"customer_message_entity\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "title": {
            "type": "string",
            "description": "calendar entries use the title"
          },
          "vehicle_name": {
            "type": "string"
          },
          "vehicle_license": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "total": {
            "type": "number"
          }
        }
      },
      "CustomerMessageSendRequest": {
        "type": "object",
        "description": "CustomerMessageSendRequest — POST /v1/customers/{id}/messages.\n",
        "required": [
          "type",
          "entity_id",
          "channel",
          "recipient",
          "subject",
          "message",
          "sender_method"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "worksheet | invoice | quote | calendar | revisione | service | freetext"
          },
          "entity_id": {
            "type": "string"
          },
          "channel": {
            "type": "string",
            "description": "email | sms | whatsapp"
          },
          "recipient": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "sender_method": {
            "type": "string"
          }
        }
      },
      "DeliveryNote": {
        "type": "object",
        "description": "DeliveryNote is one \"Documenti di trasporto\" (DDT) row. The list\n(GET /v1/delivery-notes) mirrors the web delivery-note-list: the\nsupplier (or the causale/description when no supplier), the\nnumber, the delivery date and the amount. status is \"draft\"\n(Bozza) or \"issued\" (Emesso).\n",
        "required": [
          "object",
          "id",
          "seq_number",
          "supplier_id",
          "supplier_name",
          "description",
          "status",
          "delivery_date",
          "total_before_tax",
          "total_tax",
          "total_after_tax",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"delivery_note\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "supplier_id": {
            "type": "string"
          },
          "supplier_name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "delivery_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "total_after_tax": {
            "type": "number"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DeliveryNoteStatusCounts": {
        "type": "object",
        "description": "DeliveryNoteStatusCounts backs the DDT status tabs (Bozza /\nEmessi). Counts are scoped to the applied date range (echoed in\ndate_filter), the free-text search and the selected supplier — web\nparity.\n",
        "required": [
          "object",
          "draft",
          "issued",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"delivery_note_status_counts\""
          },
          "draft": {
            "type": "integer"
          },
          "issued": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "ElectronicInvoice": {
        "type": "object",
        "description": "ElectronicInvoice — one outgoing (attiva) SDI transmission row.\nThe list is a UNION of fatture (TD01), note di credito (TD04) and\nscontrini, so the client routes each row by document_type (see the\nmobile trasmissioni-sdi list): TD01 → the e-invoice detail (this\nID), TD04 → the credit-note detail (CreditNoteID), scontrino → the\nreceipt detail (this ID = receipt id).\n",
        "required": [
          "object",
          "id",
          "status",
          "document_type",
          "invoice_id",
          "credit_note_id",
          "seq_number",
          "customer_name",
          "vehicle_name",
          "total_after_discount",
          "date",
          "sent_at",
          "delivered_at",
          "rejected_at",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"electronic_invoice\""
          },
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "draft | sent | delivered | rejected | cancelled"
          },
          "document_type": {
            "type": "string",
            "description": "TD01 (fattura) | TD04 (nota di credito) | scontrino"
          },
          "invoice_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "credit_note_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "TD04 rows only: the credit-note detail id"
          },
          "receipt_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "scontrino rows: AdE document number, used as title when no customer"
          },
          "seq_number": {
            "type": "integer"
          },
          "customer_name": {
            "type": "string"
          },
          "vehicle_name": {
            "type": "string"
          },
          "total_after_discount": {
            "type": "number"
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "rejected_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ElectronicInvoiceIncoming": {
        "type": "object",
        "description": "ElectronicInvoiceIncoming — one passive supplier invoice from\nACube.\n",
        "required": [
          "object",
          "id",
          "supplier_id",
          "business_name",
          "invoice_number",
          "invoice_date",
          "total_amount",
          "status",
          "document_type",
          "expense_id",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"electronic_invoice_incoming\""
          },
          "id": {
            "type": "string"
          },
          "supplier_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "business_name": {
            "type": "string"
          },
          "invoice_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "invoice_date": {
            "type": "string",
            "format": "date-time"
          },
          "total_amount": {
            "type": "number"
          },
          "status": {
            "type": "string",
            "description": "received | quarantena"
          },
          "document_type": {
            "type": "string"
          },
          "expense_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ElectronicInvoiceIncomingStatusCounts": {
        "type": "object",
        "description": "ElectronicInvoiceIncomingStatusCounts — Passive tab counters.\nEchoes the applied date filter (see\nElectronicInvoiceStatusCounts).\n",
        "required": [
          "object",
          "received",
          "quarantena",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"electronic_invoice_incoming_status_counts\""
          },
          "received": {
            "type": "integer"
          },
          "quarantena": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "ElectronicInvoiceStatusCounts": {
        "type": "object",
        "description": "ElectronicInvoiceStatusCounts — Attive tab counters. Echoes the\napplied date filter so the client can show the period without a\nprobe.\n",
        "required": [
          "object",
          "draft",
          "sent",
          "delivered",
          "rejected",
          "cancelled",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"electronic_invoice_status_counts\""
          },
          "draft": {
            "type": "integer"
          },
          "sent": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "cancelled": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "EmailSender": {
        "type": "object",
        "description": "EmailSender: one configured sending identity — the same options\nthe web \"Mittente\" dropdown offers. \"internal\" is always present;\n\"smtp\" and \"gmail\" appear only when configured (in the web\nsettings — configuration itself is not exposed on this API).\n",
        "required": [
          "key",
          "label",
          "default"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "internal | smtp | gmail"
          },
          "label": {
            "type": "string",
            "description": "e.g. \"Account Gmail (x@gmail.com)\""
          },
          "default": {
            "type": "boolean"
          }
        }
      },
      "Expense": {
        "type": "object",
        "description": "Expense (spesa) — passive invoices from suppliers.\n",
        "required": [
          "object",
          "id",
          "seq_number",
          "supplier_id",
          "supplier_name",
          "expense_category_id",
          "category_name",
          "description",
          "invoice_number",
          "comments",
          "status",
          "expired",
          "payment_method",
          "payment_due_at",
          "total_before_tax",
          "total_tax",
          "total_after_tax",
          "total_discount",
          "created_by_full_name",
          "created_at",
          "updated_at",
          "deleted_at"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "description": "always \"expense\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "supplier_id": {
            "type": "string"
          },
          "supplier_name": {
            "type": "string"
          },
          "expense_category_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "category_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "invoice_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"to_pay\" | \"paid\"; expired = to_pay past the due date."
          },
          "status": {
            "type": "string"
          },
          "expired": {
            "type": "boolean"
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "total_after_tax": {
            "type": "number"
          },
          "total_discount": {
            "type": "number"
          },
          "created_by_full_name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Detail responses only."
          },
          "line_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExpenseLineItem"
            }
          },
          "installments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExpenseInstallment"
            },
            "description": "Detail responses only — web expense-detail parity. TotalPayments /"
          },
          "total_payments": {
            "type": [
              "number",
              "null"
            ]
          },
          "outstanding_payments": {
            "type": [
              "number",
              "null"
            ]
          },
          "is_stornata": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "schedule": {
            "$ref": "#/components/schemas/ExpenseScheduleInfo"
          },
          "recurring_expenses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExpenseRecurringRow"
            }
          },
          "incoming_sdi": {
            "$ref": "#/components/schemas/ExpenseIncomingSDIInfo"
          }
        }
      },
      "ExpenseCategory": {
        "type": "object",
        "description": "ExpenseCategory is one spese category option.\n",
        "required": [
          "object",
          "id",
          "name"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"expense_category\""
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "ExpenseCreateRequest": {
        "type": "object",
        "description": "ExpenseCreateRequest — the simplified mobile create: totals\nentered directly (line items are a web-side refinement).\n",
        "required": [
          "supplier_id",
          "description",
          "invoice_number",
          "expense_category_id",
          "payment_method",
          "payment_due_at",
          "total_before_tax",
          "total_tax",
          "comments"
        ],
        "properties": {
          "supplier_id": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "invoice_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "expense_category_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_due_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ExpenseIncomingSDIInfo": {
        "type": "object",
        "description": "ExpenseIncomingSDIInfo — the ricezione SDI this expense was\ncreated from.\n",
        "required": [
          "object",
          "id",
          "invoice_number"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"electronic_invoice_incoming_ref\""
          },
          "id": {
            "type": "string"
          },
          "invoice_number": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ExpenseInstallment": {
        "type": "object",
        "description": "ExpenseInstallment is one planned scadenza.\n",
        "required": [
          "object",
          "id",
          "due_date",
          "amount",
          "paid"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"expense_installment\""
          },
          "id": {
            "type": "string"
          },
          "due_date": {
            "type": "string",
            "format": "date-time"
          },
          "amount": {
            "type": "number"
          },
          "paid": {
            "type": "boolean"
          }
        }
      },
      "ExpenseLineItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "description",
          "quantity",
          "unit_type",
          "unit_price",
          "unit_discount_rate",
          "tax_rate",
          "total_price"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"expense_line_item\""
          },
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "unit_type": {
            "type": "string"
          },
          "unit_price": {
            "type": "number"
          },
          "unit_discount_rate": {
            "type": "number"
          },
          "tax_rate": {
            "type": "number"
          },
          "total_price": {
            "type": "number"
          }
        }
      },
      "ExpensePatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/expenses/{id}`. Three-state semantics\n(omitted = leave unchanged, explicit `null` = clear, concrete\nvalue = set). `total_before_tax` and\n`total_tax` are not nullable and ignore an explicit `null`.\n\n`supplier_id`, `seq_number`, `status`, computed totals and the\ntimestamps are NOT patchable; line items are managed on their\nown sub-resource.\n",
        "properties": {
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "invoice_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "expense_category_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_due_at": {
            "$ref": "#/components/schemas/WritableTimestampNullable"
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "ExpensePayment": {
        "type": "object",
        "description": "ExpensePayment — one row of the expense payments ledger.\n",
        "required": [
          "object",
          "id",
          "amount",
          "payment_method",
          "payment_date",
          "financial_account_id",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"expense_payment\""
          },
          "id": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "payment_method": {
            "type": "string"
          },
          "payment_date": {
            "type": "string",
            "format": "date-time"
          },
          "financial_account_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExpensePaymentCreateRequest": {
        "type": "object",
        "description": "ExpensePaymentCreateRequest — POST /v1/expenses/{id}/payments.\n",
        "required": [
          "amount",
          "payment_method",
          "payment_date",
          "financial_account_id"
        ],
        "properties": {
          "amount": {
            "type": "number"
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "YYYY-MM-DD between 1900-01-01 and 2100-12-31 (same window as\n`WritableTimestamp`); default today. Out of range → 422\n`parameter_invalid`.\n"
          },
          "financial_account_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ExpenseRecurringRow": {
        "type": "object",
        "description": "ExpenseRecurringRow — sibling expense generated by the same\nschedule.\n",
        "required": [
          "object",
          "id",
          "seq_number",
          "created_at",
          "status",
          "total_after_tax"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"expense_recurring_row\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string"
          },
          "total_after_tax": {
            "type": "number"
          }
        }
      },
      "ExpenseScheduleInfo": {
        "type": "object",
        "description": "ExpenseScheduleInfo — the recurring schedule this expense belongs\nto.\n",
        "required": [
          "object",
          "id",
          "frequency_months"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"expense_schedule\""
          },
          "id": {
            "type": "string"
          },
          "frequency_months": {
            "type": "integer"
          }
        }
      },
      "ExpenseStatusCounts": {
        "type": "object",
        "description": "ExpenseStatusCounts backs the list tabs (GET\n/v1/expenses/status-counts).\n",
        "required": [
          "object",
          "to_pay",
          "expired",
          "paid",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"expense_status_counts\""
          },
          "to_pay": {
            "type": "integer"
          },
          "expired": {
            "type": "integer"
          },
          "paid": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "FinancialAccount": {
        "type": "object",
        "description": "FinancialAccount: one of the company's conti (Banca, Cassa, …) —\nthe options of the prima nota Conto filter, mirroring the web\ncashbook's account dropdown. Read-only on this API.\n",
        "required": [
          "object",
          "id",
          "type",
          "name"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"financial_account\""
          },
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "e.g. \"bank\" | \"cash\""
          },
          "name": {
            "type": "string"
          }
        }
      },
      "LogoutRequest": {
        "type": "object",
        "description": "LogoutRequest: POST /v1/auth/logout body. The refresh token\nidentifies the session to revoke; sending it (rather than relying\non the bearer) lets sign-out work even after the short-lived\naccess token has expired.\n",
        "required": [
          "refresh_token"
        ],
        "properties": {
          "refresh_token": {
            "type": "string"
          }
        }
      },
      "ManufacturerListEnvelope": {
        "type": "object",
        "description": "ManufacturerListEnvelope: success body for GET /v1/manufacturers.\nFollows the standard list envelope; the collection is unpaginated\n(small, rarely-changing catalog) so HasMore is always false.\n",
        "required": [
          "object",
          "data",
          "has_more",
          "url"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"list\""
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Manufacturer"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "Order": {
        "type": "object",
        "description": "Order is one \"Arrivi Merce\" row — a supplier order (ordine\nfornitore). List responses carry the header fields only; line\nitems live on the (not-yet-built) detail endpoint. status is one\nof the web lifecycle values: \"pending\" (In attesa) / \"delivery\"\n(In consegna) / \"received\" (Ricevuto).\n",
        "required": [
          "object",
          "id",
          "seq_number",
          "supplier_id",
          "supplier_name",
          "supplier_invoice_reference",
          "status",
          "total_before_tax",
          "total_tax",
          "total_after_tax",
          "total_after_discount",
          "payment_method",
          "comments",
          "invoice_date",
          "confirmed_at",
          "received_at",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"order\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "supplier_id": {
            "type": "string"
          },
          "supplier_name": {
            "type": "string"
          },
          "supplier_invoice_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "total_after_tax": {
            "type": "number"
          },
          "total_after_discount": {
            "type": "number"
          },
          "payment_method": {
            "type": "string"
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "invoice_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "received_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OrderStatusCounts": {
        "type": "object",
        "description": "OrderStatusCounts backs the \"Arrivi Merce\" status tabs. Counts are\nscoped to the applied date range (echoed back in date_filter) and\nthe free-text search, mirroring the web list's per-status tab\ncounters.\n",
        "required": [
          "object",
          "pending",
          "delivery",
          "received",
          "date_filter"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"order_status_counts\""
          },
          "pending": {
            "type": "integer"
          },
          "delivery": {
            "type": "integer"
          },
          "received": {
            "type": "integer"
          },
          "date_filter": {
            "$ref": "#/components/schemas/DateFilter"
          }
        }
      },
      "PaymentListEnvelope": {
        "type": "object",
        "description": "PaymentListEnvelope: success body for GET\n/v1/worksheets/{id}/payments. Unpaginated — a worksheet carries a\nhandful of payments at most.\n",
        "required": [
          "object",
          "data",
          "has_more",
          "url"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"list\""
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Payment"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "ProfileModules": {
        "type": "object",
        "description": "ProfileModules: the company feature flags the app UI gates on,\nresolved server-side from the company row with the same semantics\nthe web sidebar reads them. technical_data is the composite\nCompany.CanUseTechnicalData() (module flag AND active\nsubscription); has_worksheets backs the web's \"Situazione appears\nafter the first worksheet\" rule.\n",
        "required": [
          "checkins",
          "electronic_invoices",
          "expenses",
          "dealership",
          "vehicle_rental",
          "tyre_storage",
          "service_catalog",
          "inventory",
          "technical_data",
          "license_plate_search",
          "public_profile",
          "ai_assistant",
          "has_worksheets"
        ],
        "properties": {
          "checkins": {
            "type": "boolean"
          },
          "electronic_invoices": {
            "type": "boolean"
          },
          "expenses": {
            "type": "boolean"
          },
          "dealership": {
            "type": "boolean"
          },
          "vehicle_rental": {
            "type": "boolean"
          },
          "tyre_storage": {
            "type": "boolean"
          },
          "service_catalog": {
            "type": "boolean"
          },
          "inventory": {
            "type": "boolean"
          },
          "technical_data": {
            "type": "boolean"
          },
          "license_plate_search": {
            "type": "boolean"
          },
          "public_profile": {
            "type": "boolean"
          },
          "ai_assistant": {
            "type": "boolean"
          },
          "has_worksheets": {
            "type": "boolean"
          }
        }
      },
      "ProfileUpdateRequest": {
        "type": "object",
        "description": "ProfileUpdateRequest: body of PATCH /v1/me. first_name / last_name\nrequired (web parity) phone required — Italian mobile, national\nnumber or full +39 form (NormalizeItalianMobilePhone) password\noptional — set only to change it (min 6 chars, web parity); the\nrepeat-password check is client-side, like the web form\n",
        "required": [
          "first_name",
          "last_name",
          "phone",
          "password"
        ],
        "properties": {
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "Receipt": {
        "type": "object",
        "description": "Receipt is the serialized form of entity.Receipt for the\n/v1/receipts endpoints. Same conventions as Invoice: money floats\nare display values, computed names are inlined, line_items appear\non detail responses only.\n",
        "required": [
          "object",
          "id",
          "title",
          "comments",
          "internal_comments",
          "email",
          "customer_id",
          "vehicle_id",
          "worksheet_id",
          "sezionale_id",
          "seq_number",
          "type",
          "status",
          "issued_at",
          "voided_at",
          "payment_method",
          "total_before_tax",
          "total_tax",
          "total_after_tax",
          "total_discount",
          "total_after_discount",
          "total_payments",
          "outstanding_payments",
          "customer_name",
          "vehicle_name",
          "vehicle_license",
          "created_by_full_name",
          "sezionale_code",
          "e_receipt_status",
          "e_receipt_document_number",
          "electronic_receipts_enabled",
          "created_at",
          "updated_at",
          "deleted_at"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-23",
            "description": "Detail, create and PATCH responses only (lists omit it). Send it\nback as `If-Match` on PATCH: a save from an older copy is refused\nwith 409. Required from revision 2026-10-23.\n"
          },
          "object": {
            "type": "string",
            "description": "always \"receipt\""
          },
          "id": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional — the electronic receipt PDF is delivered here."
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "worksheet_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "sezionale_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "seq_number": {
            "type": "integer",
            "description": "Progressive number. 0 until the receipt is issued."
          },
          "type": {
            "type": "string",
            "description": "Derived: \"draft\" | \"issued\" | \"voided\"."
          },
          "status": {
            "type": "string",
            "description": "\"sale\" | \"return\"."
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "voided_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_before_tax": {
            "type": "number"
          },
          "total_tax": {
            "type": "number"
          },
          "total_after_tax": {
            "type": "number"
          },
          "total_discount": {
            "type": "number"
          },
          "total_after_discount": {
            "type": "number"
          },
          "total_payments": {
            "type": "number"
          },
          "outstanding_payments": {
            "type": "number"
          },
          "customer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "vehicle_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by_full_name": {
            "type": "string"
          },
          "sezionale_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "Latest electronic-receipt transmission (empty when none / module off)."
          },
          "e_receipt_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "e_receipt_document_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "ElectronicReceiptsEnabled — the company transmits scontrini to the AdE."
          },
          "electronic_receipts_enabled": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Detail responses only."
          },
          "line_items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReceiptLineItem"
            }
          }
        }
      },
      "ReceiptCreateRequest": {
        "type": "object",
        "description": "ReceiptCreateRequest — a mobile scontrino is always created from a\nworksheet (web \"Crea scontrino\" parity).\n",
        "required": [
          "worksheet_id",
          "payment_method"
        ],
        "properties": {
          "worksheet_id": {
            "type": "string"
          },
          "payment_method": {
            "type": "string"
          }
        }
      },
      "ReceiptLineItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "line_type",
          "article_id",
          "description",
          "quantity",
          "unit_type",
          "unit_price",
          "unit_discount_rate",
          "tax_rate",
          "vat_code",
          "total_price",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"receipt_line_item\""
          },
          "id": {
            "type": "string"
          },
          "line_type": {
            "type": "string",
            "enum": [
              "item",
              "note",
              "separator"
            ],
            "x-since": "2026-10-14",
            "description": "`item` is a priced line. `note` is a description-only line and\n`separator` an empty spacer row: both print without quantity,\nprice and VAT and carry zero amounts (ignore their numeric\nfields). Lines from documents without types report `item`.\n"
          },
          "article_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "unit_type": {
            "type": "string"
          },
          "unit_price": {
            "type": "number"
          },
          "unit_discount_rate": {
            "type": "number"
          },
          "tax_rate": {
            "type": "number"
          },
          "vat_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "total_price": {
            "type": "number"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ReceiptPatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/receipts/{id}`. Three-state semantics\n(omitted = leave unchanged, explicit `null` = clear, concrete\nvalue = set).\n\n`seq_number`, `status`, `type`, the customer/vehicle/worksheet\nreferences, totals and the timestamps are NOT patchable; line\nitems are managed on their own sub-resource.\n",
        "properties": {
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "internal_comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "sezionale_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "ReceiptStatusCounts": {
        "type": "object",
        "description": "ReceiptStatusCounts — the scontrini list document-status tab\ncounters (Bozze / Emessi / Annullati), computed over the active\nSDI + search filters, web parity.\n",
        "required": [
          "object",
          "draft",
          "issued",
          "voided"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"receipt_status_counts\""
          },
          "draft": {
            "type": "integer"
          },
          "issued": {
            "type": "integer"
          },
          "voided": {
            "type": "integer"
          }
        }
      },
      "RefreshRequest": {
        "type": "object",
        "description": "RefreshRequest: POST /v1/auth/refresh body. The opaque refresh\ntoken IS the credential (this endpoint takes no bearer), exchanged\nfor a rotated token pair.\n",
        "required": [
          "refresh_token"
        ],
        "properties": {
          "refresh_token": {
            "type": "string"
          }
        }
      },
      "ReportAppointment": {
        "type": "object",
        "description": "ReportAppointment is one row of widget \"upcoming-appointments\".\n",
        "required": [
          "id",
          "title",
          "customer_name",
          "start_time"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "customer_name": {
            "type": "string"
          },
          "start_time": {
            "type": "string",
            "description": "RFC 3339"
          }
        }
      },
      "ReportChartSeries": {
        "type": "object",
        "description": "ReportChartSeries is one named series of a stacked chart, aligned\nwith the envelope's XAxis.\n",
        "required": [
          "key",
          "values"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "values": {
            "type": "array",
            "items": {
              "type": "number"
            }
          }
        }
      },
      "ReportKPI": {
        "type": "object",
        "description": "ReportKPI is one headline stat tile (widget \"kpis\" returns four).\n",
        "required": [
          "key",
          "value",
          "is_currency",
          "previous_value",
          "delta",
          "delta_percentage"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "sales | outstanding | worksheets | customers"
          },
          "value": {
            "type": "number"
          },
          "is_currency": {
            "type": "boolean"
          },
          "previous_value": {
            "type": "number"
          },
          "delta": {
            "type": "number"
          },
          "delta_percentage": {
            "type": "number"
          }
        }
      },
      "ReportOutstandingWorksheet": {
        "type": "object",
        "description": "ReportOutstandingWorksheet is one row of widget\n\"outstanding-worksheets\". BarWidth is the server-normalized bar\nlength (0-100).\n",
        "required": [
          "worksheet_id",
          "title",
          "customer_name",
          "outstanding",
          "total",
          "bar_width"
        ],
        "properties": {
          "worksheet_id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "customer_name": {
            "type": "string"
          },
          "outstanding": {
            "type": "number"
          },
          "total": {
            "type": "number"
          },
          "bar_width": {
            "type": "number"
          }
        }
      },
      "ReportRankedRow": {
        "type": "object",
        "description": "ReportRankedRow is one bar of the ranked-list widgets\n(\"top-customers\", \"top-services\", \"worksheet-margins\"). Amount\ncarries the sort metric; BarWidth is server-normalized (0-100).\n",
        "required": [
          "id",
          "label",
          "amount",
          "bar_width"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "sublabel": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "percent": {
            "type": "number"
          },
          "bar_width": {
            "type": "number"
          }
        }
      },
      "RoadmapBoard": {
        "type": "object",
        "description": "Roadmap (Novità / Funzionalità in sviluppo) — web\nsettings-roadmap.\n",
        "required": [
          "object",
          "planned",
          "progress",
          "done"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"roadmap_board\""
          },
          "planned": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RoadmapItem"
            }
          },
          "progress": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RoadmapItem"
            }
          },
          "done": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RoadmapItem"
            }
          }
        }
      },
      "RoadmapItem": {
        "type": "object",
        "required": [
          "object",
          "id",
          "title",
          "description",
          "votes",
          "has_voted"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"roadmap_item\""
          },
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "description": "Display count (baseline 1 + user votes, web DisplayVotes)."
          },
          "votes": {
            "type": "integer"
          },
          "has_voted": {
            "type": "boolean"
          }
        }
      },
      "RoadmapSuggestionRequest": {
        "type": "object",
        "required": [
          "name",
          "description"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "SendChannelEmail": {
        "type": "object",
        "description": "SendChannelEmail: defaults for the email form. Free (no credits).\n",
        "required": [
          "recipient",
          "subject",
          "body",
          "senders"
        ],
        "properties": {
          "recipient": {
            "type": "string",
            "description": "customer email (\"\" when unset)"
          },
          "subject": {
            "type": "string",
            "description": "max 50 chars on send"
          },
          "body": {
            "type": "string",
            "description": "editable, share link included"
          },
          "senders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailSender"
            },
            "description": "exactly one has default=true"
          }
        }
      },
      "SendChannelSMS": {
        "type": "object",
        "description": "SendChannelSMS: defaults for the SMS form. Body must stay within\nmax_length and the GSM-7 basic charset (no accents/emoji).\nRecipient is the customer's stored E.164 phone: messaging is\nItalian-only, so the client shows an editable +39 national-number\nfield when it's empty or +39, and a read-only field with a\ndisabled send otherwise (web message-phone-field parity).\n",
        "required": [
          "recipient",
          "body",
          "max_length",
          "credit_cost"
        ],
        "properties": {
          "recipient": {
            "type": "string",
            "description": "customer phone (\"\" when unset)"
          },
          "body": {
            "type": "string"
          },
          "max_length": {
            "type": "integer",
            "const": 160,
            "description": "The SMS body limit, in GSM-7 characters."
          },
          "credit_cost": {
            "type": "number",
            "description": "0 = included in the plan"
          }
        }
      },
      "SendChannelWhatsApp": {
        "type": "object",
        "description": "SendChannelWhatsApp: the WhatsApp Business flow sends a\npre-approved template — template_preview is display-only, never\neditable or submitted. Same Italian-only recipient rules as SMS.\n",
        "required": [
          "recipient",
          "template_preview",
          "credit_cost"
        ],
        "properties": {
          "recipient": {
            "type": "string"
          },
          "template_preview": {
            "type": "string"
          },
          "credit_cost": {
            "type": "number",
            "description": "0 = included in the plan"
          }
        }
      },
      "Session": {
        "type": "object",
        "description": "Session: one entry in the \"Sessioni attive\" list (GET\n/v1/sessions) and the object returned by DELETE /v1/sessions/{id}.\nNever exposes the token hash or raw IP — only what the user needs\nto recognise and manage a device.\n",
        "required": [
          "object",
          "id",
          "surface",
          "display_name",
          "created_at",
          "last_used_at",
          "active",
          "current"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"session\""
          },
          "id": {
            "type": "string"
          },
          "surface": {
            "type": "string",
            "description": "web | mobile"
          },
          "display_name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "description": "team member the device belongs to (owner-only company listing)"
          },
          "platform": {
            "type": "string"
          },
          "app_version": {
            "type": "string"
          },
          "os_version": {
            "type": "string"
          },
          "location": {
            "type": "string",
            "description": "\"Milano, IT\" — from Cloudflare geo"
          },
          "last_ip": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time"
          },
          "active": {
            "type": "boolean",
            "description": "true when the token is still live (not revoked/expired)"
          },
          "current": {
            "type": "boolean",
            "description": "true on the caller's own session"
          }
        }
      },
      "SessionDeletedResponse": {
        "type": "object",
        "description": "SessionDeletedResponse: body for DELETE /v1/sessions/{id} and POST\n/v1/auth/logout.\n",
        "required": [
          "object",
          "id",
          "deleted"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"session\""
          },
          "id": {
            "type": "string"
          },
          "deleted": {
            "type": "boolean"
          }
        }
      },
      "SessionRevocationResponse": {
        "type": "object",
        "description": "SessionRevocationResponse: body for POST\n/v1/sessions/revoke-others.\n",
        "required": [
          "object",
          "revoked_count"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"session_revocation\""
          },
          "revoked_count": {
            "type": "integer"
          }
        }
      },
      "Supplier": {
        "type": "object",
        "description": "Supplier (fornitore).\n",
        "required": [
          "object",
          "id",
          "name",
          "email",
          "phone",
          "phone_national",
          "address",
          "city",
          "zip_code",
          "tax_id",
          "comments",
          "bank_account_iban",
          "website",
          "website_display",
          "supplier_type",
          "created_at",
          "updated_at",
          "deleted_at"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "description": "always \"supplier\""
          },
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone_national": {
            "type": [
              "string",
              "null"
            ],
            "description": "The phone number without the +39 country code."
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "bank_account_iban": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored uppercase with no spaces. On writes it must pass the IBAN checksum (mod 97), else 422 on `/bank_account_iban`."
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "website_display": {
            "type": [
              "string",
              "null"
            ],
            "description": "The website without the scheme, as shown in the app."
          },
          "supplier_type": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Detail-only compact rows for the web supplier-detail tabs"
          },
          "orders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupplierOrderRow"
            }
          },
          "articles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupplierArticleRow"
            }
          },
          "expenses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SupplierExpenseRow"
            }
          }
        }
      },
      "SupplierArticleRow": {
        "type": "object",
        "required": [
          "object",
          "id",
          "code",
          "seq_number",
          "name",
          "stock",
          "sale_price",
          "created_at"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"supplier_article\""
          },
          "id": {
            "type": "string"
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "seq_number": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "stock": {
            "type": "number"
          },
          "sale_price": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SupplierCreateRequest": {
        "type": "object",
        "required": [
          "name",
          "email",
          "phone",
          "address",
          "city",
          "zip_code",
          "tax_id",
          "comments",
          "bank_account_iban",
          "website"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "bank_account_iban": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored uppercase with no spaces. On writes it must pass the IBAN checksum (mod 97), else 422 on `/bank_account_iban`."
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "SupplierExpenseRow": {
        "type": "object",
        "required": [
          "object",
          "id",
          "seq_number",
          "created_at",
          "invoice_number",
          "payment_due_at",
          "total_after_tax",
          "status"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"supplier_expense\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "invoice_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "total_after_tax": {
            "type": "number"
          },
          "status": {
            "type": "string",
            "description": "to_pay | paid"
          }
        }
      },
      "SupplierOrderRow": {
        "type": "object",
        "required": [
          "object",
          "id",
          "seq_number",
          "created_at",
          "status",
          "total_after_tax"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"supplier_order\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "description": "pending | delivery | received"
          },
          "total_after_tax": {
            "type": "number"
          }
        }
      },
      "SupplierPatchRequest": {
        "type": "object",
        "description": "Body of `PATCH /v1/suppliers/{id}`. Three-state semantics\n(omitted = leave unchanged, explicit `null` = clear, concrete\nvalue = set) on every field but `name`,\nwhich is not nullable and ignores an explicit `null`.\n\n`supplier_type`, `phone_national`, `website_display` and the\ntimestamps are NOT patchable: the two display fields are derived\nfrom `phone` and `website`.\n",
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored as +39…; a national number is normalised."
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "zip_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "tax_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "bank_account_iban": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored uppercase with no spaces. On writes it must pass the IBAN checksum (mod 97), else 422 on `/bank_account_iban`."
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored with the scheme; `https://` is added when missing."
          }
        },
        "additionalProperties": false
      },
      "TyreSet": {
        "type": "object",
        "description": "TyreSet (treno gomme) — deposito gomme.\n",
        "required": [
          "object",
          "id",
          "seq_number",
          "description",
          "status",
          "quantity",
          "rim_type",
          "season_type",
          "condition",
          "residual_mm",
          "location",
          "comments",
          "customer_id",
          "vehicle_id",
          "customer_name",
          "vehicle_name",
          "vehicle_license",
          "deposited_at",
          "mounted_at",
          "collected_at",
          "disposed_at",
          "inventory_check_at",
          "created_at",
          "updated_at",
          "deleted_at"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "x-since": "2026-10-22",
            "description": "Detail, create and write responses only (lists omit it). Send it\nback as `If-Match` on PATCH (and on the line-items batch): a save\nfrom an older copy is refused with 409. Required from revision\n2026-10-22.\n"
          },
          "object": {
            "type": "string",
            "description": "always \"tyre_set\""
          },
          "id": {
            "type": "string"
          },
          "seq_number": {
            "type": "integer"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "deposited | mounted | disposed | collected"
          },
          "quantity": {
            "type": "integer"
          },
          "rim_type": {
            "type": "string",
            "description": "steel | alloy"
          },
          "season_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "condition": {
            "type": [
              "string",
              "null"
            ]
          },
          "residual_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": "string"
          },
          "customer_name": {
            "type": "string"
          },
          "vehicle_name": {
            "type": "string"
          },
          "vehicle_license": {
            "type": [
              "string",
              "null"
            ]
          },
          "deposited_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "mounted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "collected_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "disposed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "inventory_check_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "TyreSetCreateRequest": {
        "type": "object",
        "description": "TyreSetCreateRequest doubles as the full-form PATCH body (customer\nand vehicle are ignored on update).\n",
        "required": [
          "customer_id",
          "vehicle_id",
          "description",
          "quantity",
          "rim_type",
          "season_type",
          "condition",
          "residual_mm",
          "location",
          "comments"
        ],
        "properties": {
          "customer_id": {
            "type": "string"
          },
          "vehicle_id": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "rim_type": {
            "type": "string"
          },
          "season_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "condition": {
            "type": [
              "string",
              "null"
            ]
          },
          "residual_mm": {
            "type": [
              "integer",
              "null"
            ]
          },
          "location": {
            "type": [
              "string",
              "null"
            ]
          },
          "comments": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "TyreSetStatusCounts": {
        "type": "object",
        "required": [
          "object",
          "deposited",
          "mounted",
          "disposed",
          "collected",
          "locations"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"tyre_set_status_counts\""
          },
          "deposited": {
            "type": "integer"
          },
          "mounted": {
            "type": "integer"
          },
          "disposed": {
            "type": "integer"
          },
          "collected": {
            "type": "integer",
            "description": "Distinct locations in use — drives the Ubicazione filter, which the"
          },
          "locations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "VatRateCatalogEntry": {
        "type": "object",
        "description": "VatRateCatalogEntry — one global VAT rate with the company's\nenabled mark (GET /v1/vat-rates/catalog).\n",
        "required": [
          "object",
          "code",
          "rate",
          "description",
          "enabled"
        ],
        "properties": {
          "object": {
            "type": "string",
            "description": "always \"vat_rate_catalog_entry\""
          },
          "code": {
            "type": "string"
          },
          "rate": {
            "type": "number"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "VehicleCustomField": {
        "type": "object",
        "description": "VehicleCustomField: one company-configured custom field resolved\nfor display and editing — id from the definition, its display\nname, its input type (string | integer | datetime | list), the\nchoices of a list field (`options`) and this vehicle's value\n(\"\" when unset).\n",
        "required": [
          "id",
          "name",
          "type",
          "value"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "string",
              "integer",
              "datetime",
              "list"
            ]
          },
          "options": {
            "type": "array",
            "x-since": "2026-10-23",
            "items": {
              "type": "string"
            },
            "description": "`list` fields only (omitted on the other types): the dropdown\nchoices in order. `value` is one of them, or \"\" when unset.\n"
          },
          "value": {
            "type": "string"
          }
        }
      },
      "VehicleFieldVisibility": {
        "type": "object",
        "description": "VehicleFieldVisibility mirrors the company's optional-field\ntoggles (entity.VehicleFieldVisibility) so the edit form hides\nexactly what the web form hides.\n",
        "required": [
          "km",
          "numero_telaio",
          "transmission",
          "engine_category",
          "engine_power_kw",
          "engine_displacement_ccm",
          "engine_code",
          "vehicle_weight_kg",
          "registration_date",
          "last_inspection_date",
          "next_inspection_date"
        ],
        "properties": {
          "km": {
            "type": "boolean"
          },
          "numero_telaio": {
            "type": "boolean"
          },
          "transmission": {
            "type": "boolean"
          },
          "engine_category": {
            "type": "boolean"
          },
          "engine_power_kw": {
            "type": "boolean"
          },
          "engine_displacement_ccm": {
            "type": "boolean"
          },
          "engine_code": {
            "type": "boolean",
            "x-since": "2026-10-05"
          },
          "vehicle_weight_kg": {
            "type": "boolean"
          },
          "registration_date": {
            "type": "boolean"
          },
          "last_inspection_date": {
            "type": "boolean"
          },
          "next_inspection_date": {
            "type": "boolean"
          }
        }
      }
    },
    "responses": {
      "EditConflict": {
        "x-description-it": "Il documento è stato salvato nel frattempo (la `version` di `If-Match` è superata): ricaricalo e riprova. Nulla è stato salvato.",
        "description": "The record was saved since the client read it: the `If-Match`\nversion is superseded, or another save held it. Nothing was\nwritten; read it again and retry. Other state conflicts (e.g. an\nissued document) answer 409 too, with their own message.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "status": 409,
                "code": "conflict",
                "message": "Il documento è stato modificato nel frattempo: ricaricalo e riprova."
              }
            }
          }
        }
      },
      "PreconditionRequired": {
        "x-description-it": "Manca l'header `If-Match` (obbligatorio dalla revisione 2026-10-22).",
        "description": "The write did not send `If-Match`, required from revision\n2026-10-22 on writes to versioned records.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "status": 428,
                "code": "precondition_required",
                "param": "If-Match",
                "message": "Manca l'header If-Match: invia il campo version letto nel documento (oppure * per scrivere comunque)."
              }
            }
          }
        }
      },
      "Unauthorized": {
        "x-description-it": "Chiave API mancante, non valida, revocata o scaduta.",
        "description": "Missing, malformed, expired, or wrong-audience bearer token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/api_key_invalid",
                "status": 401,
                "code": "api_key_invalid",
                "message": "Chiave API mancante, non valida, revocata o scaduta.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "PaymentRequired": {
        "x-description-it": "Il piano non include l'accesso API, oppure non è attivo un abbonamento.",
        "description": "The company has no active subscription, or its plan does not\ninclude API access. API access is included in the Business and\nIllimitato plans.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/plan_upgrade_required",
                "status": 402,
                "code": "plan_upgrade_required",
                "message": "Il piano attivo non include l'accesso API.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "Forbidden": {
        "x-description-it": "La chiave non ha il permesso richiesto per questa operazione.",
        "description": "The authenticated user lacks the required permission.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/insufficient_scope",
                "status": 403,
                "code": "insufficient_scope",
                "message": "La chiave non ha il permesso reports.read.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "NotFound": {
        "x-description-it": "La risorsa non esiste, è stata eliminata o non appartiene alla tua officina.",
        "description": "Resource does not exist OR belongs to a different company.\nThe two cases are intentionally indistinguishable.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/not_found",
                "status": 404,
                "code": "not_found",
                "message": "Customer not found.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "TimeTrackingForbidden": {
        "x-description-it": "Il modulo Marcatempo non è attivo (Impostazioni → Moduli), oppure la chiave non ha il permesso `worksheets.read`.",
        "description": "The Marcatempo module is off for the workshop (`forbidden`), or the\nkey lacks the `worksheets.read` scope (`insufficient_scope`).\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/forbidden",
                "status": 403,
                "code": "forbidden",
                "message": "Il marcatempo non è attivo per la tua officina o per il tuo utente.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "InvalidCredentials": {
        "x-description-it": "Credenziali non corrette.",
        "description": "Email or password incorrect.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/invalid_credentials",
                "status": 401,
                "code": "invalid_credentials",
                "message": "The email or password you provided is incorrect.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "SignOnTokenInvalid": {
        "x-description-it": "Codice di accesso non valido o scaduto.",
        "description": "Sign-on token invalid or expired.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/sign_on_token_invalid",
                "status": 401,
                "code": "sign_on_token_invalid",
                "message": "The sign-on token is invalid or has expired.",
                "param": "/sign_on_token",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "ValidationError": {
        "x-description-it": "Un campo della richiesta manca o non è valido: «param» indica quale.",
        "description": "A request field is missing or invalid (fast-fail).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/parameter_invalid",
                "status": 422,
                "code": "parameter_invalid",
                "message": "Email must be a valid email address.",
                "param": "/email",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "MalformedRequest": {
        "x-description-it": "Corpo della richiesta non leggibile o JSON non valido.",
        "description": "Request body is unparseable or X-API-Version is missing.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/malformed_request",
                "status": 400,
                "code": "malformed_request",
                "message": "Request body is not valid JSON.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "x-description-it": "Il Content-Type deve essere application/json e Accept deve accettare JSON.",
        "description": "Content-Type is not `application/json`, or Accept rejects JSON.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/unsupported_media_type",
                "status": 415,
                "code": "unsupported_media_type",
                "message": "Content-Type must be application/json.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "Conflict": {
        "x-description-it": "L'operazione è in conflitto con lo stato attuale della risorsa.",
        "description": "The request conflicts with the current state of the\nresource. Examples: issuing an invoice that has already\nbeen issued; back-dating an issuance that would violate the\nmonotonic per-company sequence; deleting a credit note\nwhose SDI electronic counterpart has already been\ntransmitted and cannot be retracted; mutating a worksheet\nwhose linked invoice has been issued (the worksheet is\nfrozen — its totals are now part of the issued invoice's\naccounting record and must not desync).\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/conflict",
                "status": 409,
                "code": "conflict",
                "message": "Worksheet is frozen: its linked invoice has been issued. Cancel the invoice with a credit note before editing.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      },
      "AmbiguousNumber": {
        "x-description-it": "Il numero corrisponde a più documenti: scegli quello giusto tra i `candidates` e recuperalo per id.",
        "description": "The number matched more than one document — a number repeated\nin historical data. `candidates` lists their ids; retrieve the\nright one by id. For sezionale-numbered documents, passing\n`sezionale_id` narrows the search to one series.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/ambiguous_number",
                "status": 409,
                "code": "ambiguous_number",
                "message": "Più schede lavoro hanno il numero 12/2025: recupera quella giusta per id tra i candidates.",
                "candidates": [
                  "a5d21e64-8f37-4b90-ae12-3c7d6b048f95",
                  "0c3e9b7a-4f12-4d8e-9a61-7b2f5c8d1e43"
                ]
              }
            }
          }
        }
      },
      "ArticleCodeTaken": {
        "x-description-it": "Un altro articolo dello stesso tipo (magazzino o manodopera) usa già questo codice, senza distinguere maiuscole e spazi iniziali o finali. Il suo id è in `candidates`. Vale per tutte le revisioni supportate.",
        "description": "Another live article of the same kind (Magazzino or Listino\nmanodopera) already uses this code, ignoring case and surrounding\nspaces. `candidates` holds its id. Applies to every supported\nrevision: request and response schemas are unchanged.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/article_code_taken",
                "status": 409,
                "code": "article_code_taken",
                "message": "Il codice \"FO-2231\" è già usato dall'articolo \"Filtro olio\" in magazzino.",
                "param": "/code",
                "candidates": [
                  "f18c4b62-9d07-4e53-a294-6b1f8d3c5e70"
                ]
              }
            }
          }
        }
      },
      "LicensePlateTaken": {
        "x-description-it": "Un altro veicolo attivo dell'officina ha già questa targa, senza distinguere maiuscole, spazi e trattini. Il suo id è in `candidates`. Dalla revisione 2026-10-22; `allow_duplicate_license: true` nel corpo salva comunque.",
        "description": "Another live vehicle of the workshop already carries this plate,\nignoring case, spaces and dashes. `candidates` holds its id. From\nrevision 2026-10-22; `allow_duplicate_license: true` in the body\nsaves anyway (the user saw the existing vehicle and chose to go\non). Rental vehicles and deleted vehicles never count.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/license_plate_taken",
                "status": 409,
                "code": "license_plate_taken",
                "message": "La targa \"AB123CD\" è già registrata sul veicolo Fiat Panda di Mario Rossi. Se è lo stesso veicolo, usa quello.",
                "param": "/license",
                "candidates": [
                  "0871ea26-9502-466a-9132-26e2589fdd58"
                ]
              }
            }
          }
        }
      },
      "VehiclePatchConflict": {
        "x-description-it": "409 su `PATCH /v1/vehicles/{id}`: `conflict` se la `version` in `If-Match` è superata (nulla viene salvato), oppure `license_plate_taken` se la targa è già di un altro veicolo attivo (vedi `LicensePlateTaken`).",
        "description": "Two 409 codes on `PATCH /v1/vehicles/{id}`: `conflict` when the\n`version` in `If-Match` is superseded (nothing is written), or\n`license_plate_taken` when the plate is changed to one that already\nbelongs to another live vehicle (re-sending the unchanged plate is never refused) (see `LicensePlateTaken`; `candidates` holds\nits id, `allow_duplicate_license: true` saves anyway).\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/license_plate_taken",
                "status": 409,
                "code": "license_plate_taken",
                "message": "La targa \"AB123CD\" è già registrata sul veicolo Fiat Panda di Mario Rossi. Se è lo stesso veicolo, usa quello.",
                "param": "/license",
                "candidates": [
                  "0871ea26-9502-466a-9132-26e2589fdd58"
                ]
              }
            }
          }
        }
      },
      "InternalError": {
        "x-description-it": "Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.",
        "description": "Unhandled server error. Detail is logged, not exposed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "error": {
                "type": "https://api.officina.it/errors/internal_error",
                "status": 500,
                "code": "internal_error",
                "message": "An unexpected error occurred. Please retry or contact support if it persists.",
                "trace_id": "abc-123"
              }
            }
          }
        }
      }
    }
  }
}
