Preventivi

Preventivi, righe e conversione in scheda lavoro.

9 endpoint Versione v1 · revisione 2026-07-20

Gli endpoint sono relativi a https://api.officina.it e richiedono l'header Authorization: Bearer con la chiave API e X-API-Version: 2026-07-20. L'header della revisione è obbligatorio: differenza fra v1 e revisione.

GET /v1/quotes
Permesso richiesto quotes.read

Elenca i preventivi

I preventivi dell'officina, dal più recente. Filtra per stato con status, per cliente con customer_id, per veicolo con vehicle_id e per periodo con from / to. La paginazione usa un cursore opaco: passa next_starting_after invariato in starting_after e fermati quando has_more è false.
Risposte
200
Una pagina di preventivi.
401
Chiave API mancante, non valida, revocata o scaduta.
415
Il Content-Type deve essere application/json e Accept deve accettare JSON.
422
Un campo della richiesta manca o non è valido: «param» indica quale.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl https://api.officina.it/v1/quotes \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20"
Risposta
{
  "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,
      "km": 61240,
      "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"
  }
}
POST /v1/quotes
Permesso richiesto quotes.write

Crea un preventivo

Crea un preventivo in stato waiting. Il numero progressivo (seq_number) è assegnato dal server. Le righe non si passano qui: crea il preventivo, poi aggiungile con POST /v1/quotes/{id}/line-items/batch.
Risposte
201
Il preventivo creato.
400
Corpo della richiesta non leggibile o JSON non valido.
401
Chiave API mancante, non valida, revocata o scaduta.
415
Il Content-Type deve essere application/json e Accept deve accettare JSON.
422
Un campo della richiesta manca o non è valido: «param» indica quale.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl -X POST https://api.officina.it/v1/quotes \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20" \
  -H "Content-Type: application/json" \
  -d '{
  "customer_id": "4c911f08-7713-4e83-bdf6-a0692a1b43fd",
  "vehicle_id": "8821f0ac-5d2b-41e7-9a10-6c3f2b7d4e55",
  "title": "Preventivo tagliando",
  "km": 61240
}'
Risposta
{
  "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,
  "km": 61240,
  "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"
}
GET /v1/quotes/status-counts
Permesso richiesto quotes.read

Conta i preventivi per stato

Il numero di preventivi in ciascuno stato, con gli stessi filtri dell'elenco. Serve a popolare i contatori senza scaricare i preventivi.
Risposte
200
Il numero di preventivi per stato.
401
Chiave API mancante, non valida, revocata o scaduta.
422
Un campo della richiesta manca o non è valido: «param» indica quale.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl https://api.officina.it/v1/quotes/status-counts \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20"
Risposta
{
  "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"
  }
}
GET /v1/quotes/{id}
Permesso richiesto quotes.read

Recupera un preventivo

Restituisce il preventivo con le righe (line_items) e i totali calcolati. Se il preventivo è già stato convertito, worksheet_id punta alla scheda lavoro generata.
Risposte
200
Il preventivo richiesto.
401
Chiave API mancante, non valida, revocata o scaduta.
404
La risorsa non esiste, è stata eliminata o non appartiene alla tua officina.
415
Il Content-Type deve essere application/json e Accept deve accettare JSON.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl https://api.officina.it/v1/quotes/{id} \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20"
Risposta
{
  "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,
  "km": 61240,
  "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"
    }
  ]
}
PATCH /v1/quotes/{id}
Permesso richiesto quotes.write

Aggiorna un preventivo

Aggiorna solo i campi presenti nel corpo: gli altri restano invariati, e null svuota il campo. Per cambiare stato usa POST /v1/quotes/{id}/transitions, non questo endpoint.
Risposte
200
Il preventivo aggiornato.
400
Corpo della richiesta non leggibile o JSON non valido.
401
Chiave API mancante, non valida, revocata o scaduta.
404
La risorsa non esiste, è stata eliminata o non appartiene alla tua officina.
415
Il Content-Type deve essere application/json e Accept deve accettare JSON.
422
Un campo della richiesta manca o non è valido: «param» indica quale.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl -X PATCH https://api.officina.it/v1/quotes/{id} \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20" \
  -H "Content-Type: application/json" \
  -d '{
  "title": "Preventivo aggiornato",
  "comments": null
}'
Risposta
{
  "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,
  "km": 61240,
  "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"
    }
  ]
}
DELETE /v1/quotes/{id}
Permesso richiesto quotes.write

Elimina un preventivo

Eliminazione logica: il preventivo sparisce dagli elenchi. La risposta contiene il preventivo con deleted_at valorizzato.
Risposte
200
Il preventivo eliminato.
401
Chiave API mancante, non valida, revocata o scaduta.
404
La risorsa non esiste, è stata eliminata o non appartiene alla tua officina.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl -X DELETE https://api.officina.it/v1/quotes/{id} \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20"
Risposta
{
  "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,
  "km": 61240,
  "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"
}
POST /v1/quotes/{id}/convert
Permesso richiesto quotes.write

Converti in scheda lavoro

Genera una scheda lavoro dal preventivo, copiandone righe e totali, e collega i due documenti (worksheet_id sul preventivo). Attenzione: la risposta è la scheda lavoro creata, non il preventivo — l'oggetto restituito ha object: worksheet. La conversione è una sola volta: un preventivo già convertito risponde 409.
Risposte
201
La scheda lavoro generata dal preventivo.
401
Chiave API mancante, non valida, revocata o scaduta.
404
La risorsa non esiste, è stata eliminata o non appartiene alla tua officina.
415
Il Content-Type deve essere application/json e Accept deve accettare JSON.
422
Un campo della richiesta manca o non è valido: «param» indica quale.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl -X POST https://api.officina.it/v1/quotes/{id}/convert \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20"
Risposta
{
  "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"
}
POST /v1/quotes/{id}/line-items/batch
Permesso richiesto quotes.write

Modifica le righe in blocco

Aggiunge, aggiorna, rimuove e riordina le righe in una sola richiesta, applicata come singola transazione: se una operazione fallisce non viene applicata nessuna. I totali del preventivo vengono ricalcolati e restituiti.
Risposte
200
Il preventivo con le righe aggiornate e i totali ricalcolati.
400
Corpo della richiesta non leggibile o JSON non valido.
401
Chiave API mancante, non valida, revocata o scaduta.
404
La risorsa non esiste, è stata eliminata o non appartiene alla tua officina.
415
Il Content-Type deve essere application/json e Accept deve accettare JSON.
422
Un campo della richiesta manca o non è valido: «param» indica quale.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl -X POST https://api.officina.it/v1/quotes/{id}/line-items/batch \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20" \
  -H "Content-Type: application/json" \
  -d '{
  "add": [
    {
      "description": "Estimated labour",
      "quantity": 2,
      "unit_type": "pz",
      "unit_price": 35,
      "unit_discount_rate": 0,
      "vat_code": "22"
    }
  ]
}'
Risposta
{
  "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,
  "km": 61240,
  "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"
    }
  ]
}
POST /v1/quotes/{id}/transitions
Permesso richiesto quotes.write

Cambia stato al preventivo

Porta il preventivo in waiting, accepted o rejected. È il modo corretto di registrare l'esito: non impostare status via PATCH. Accettare un preventivo non crea da solo la scheda lavoro — per quello serve POST /v1/quotes/{id}/convert.
Risposte
200
Il preventivo dopo la transizione.
400
Corpo della richiesta non leggibile o JSON non valido.
401
Chiave API mancante, non valida, revocata o scaduta.
404
La risorsa non esiste, è stata eliminata o non appartiene alla tua officina.
415
Il Content-Type deve essere application/json e Accept deve accettare JSON.
422
Un campo della richiesta manca o non è valido: «param» indica quale.
500
Errore imprevisto del server. Il dettaglio finisce nei log, non nella risposta.
Richiesta
curl -X POST https://api.officina.it/v1/quotes/{id}/transitions \
  -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \
  -H "X-API-Version: 2026-07-20" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "accepted"
}'
Risposta
{
  "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,
  "km": 61240,
  "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"
    }
  ]
}