Come iniziare: genera una chiave API
Tre passaggi. Richiede un piano Business o Illimitato.
Crea una chiave
Dal gestionale, in Impostazioni › Accesso API, seleziona «Nuova chiave API» e assegna i soli permessi necessari. La chiave viene mostrata una sola volta al momento della creazione: conservala in un luogo sicuro.
Verifica che funzioni
Ogni richiesta deve riportare la chiave nell'header Authorization e la revisione dell'API.
# chi sono, cosa può fare questa chiave curl https://api.officina.it/v1/whoami \ -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \ -H "X-API-Version: 2026-10-31"
Leggi e scrivi i tuoi dati
Le liste sono paginate tramite un cursore opaco: va restituito invariato in starting_after per ottenere la pagina successiva.
curl "https://api.officina.it/v1/customers?limit=50" \ -H "Authorization: Bearer ofk_live_LA_TUA_CHIAVE" \ -H "X-API-Version: 2026-10-31" { "object": "list", "data": [ … ], "has_more": true, "next_starting_after": "eyJvIjoyMH0" }
Permessi
A ogni chiave sono associati permessi per area, distinti fra lettura e scrittura. Il permesso di scrittura comprende sempre quello di lettura. Una chiamata priva del permesso richiesto risponde 403 insufficient_scope.
| Area | Permesso | Cosa comprende |
|---|---|---|
| Clienti | customers.read customers.write |
Anagrafiche clienti e relativi documenti |
| Veicoli | vehicles.read vehicles.write |
Veicoli, marche e documenti allegati |
| Schede lavoro | worksheets.read worksheets.write |
Schede lavoro, righe, pagamenti e documenti |
| Preventivi | quotes.read quotes.write |
Preventivi e relative righe |
| Fatture | invoices.read invoices.write |
Fatture, note di credito e pagamenti |
| Scontrini | receipts.read receipts.write |
Scontrini e relativi pagamenti |
| Accettazioni | checkins.read checkins.write |
Accettazioni e documenti allegati |
| Agenda | calendar.read calendar.write |
Calendari e appuntamenti |
| Magazzino | articles.read articles.write |
Articoli, movimenti e pneumatici in deposito |
| Fornitori | suppliers.read suppliers.write |
Fornitori, ordini e documenti di trasporto |
| Spese | expenses.read expenses.write |
Spese, pagamenti e categorie |
| Prima nota | cashbook.read cashbook.write |
Movimenti di cassa e conti finanziari |
| Impostazioni | settings.read settings.write |
Dati azienda, aliquote IVA, listini, collaboratori |
| Report | reports.read reports.write |
Statistiche, contatori e report concessionaria; in scrittura, gli acquisti veicolo |
| Cestino | trash.read | Documenti e record eliminati, in sola lettura |
Risorse
Le aree del gestionale disponibili via API, con la stessa denominazione utilizzata nell'applicazione.
Clienti
Anagrafiche, documenti allegati e storico.
Veicoli
Veicoli, marche e documenti allegati.
Schede lavoro
Schede, righe, pagamenti, stati di lavorazione e marcatempo.
Preventivi
Preventivi, righe e conversione in scheda lavoro.
Fatture
Fatture, note di credito, pagamenti ed emissione.
Scontrini
Scontrini e relativi pagamenti.
Accettazioni
Accettazioni, conversioni e documenti.
Agenda
Calendari e appuntamenti.
Magazzino
Articoli, listino manodopera e movimenti.
Deposito gomme
Pneumatici in deposito e passaggi di stato.
Fornitori
Fornitori, ordini e documenti di trasporto.
Spese
Spese, pagamenti e categorie.
Prima nota
Movimenti di cassa e conti finanziari.
Impostazioni
Dati azienda, aliquote IVA, listini, collaboratori, cestino.
Fatturazione elettronica
Fatture elettroniche emesse e ricevute, in sola lettura.
Concessionaria
Stock veicoli, vendite e report.
Report
Statistiche e contatori della tua officina.
Autenticazione
Verifica della chiave e dei permessi.
Date e orari
Date e timestamp adottano il formato RFC 3339 in UTC, ad esempio 2026-06-12T09:30:00Z.
Le date scritte dal client devono cadere fra il 1° gennaio 1900 e il 31 dicembre 2100. Un anno esterno a tale intervallo costituisce pressoché sempre un refuso di digitazione (2025 digitato «0025») e viene rifiutato con 422 parameter_invalid, dove param indica il campo interessato. È il medesimo intervallo accettato dai selettori di data del gestionale: un record creato via API resta pertanto modificabile anche dall'interfaccia web. I timestamp generati dal server (created_at, updated_at) non sono soggetti a tale vincolo.
{
"error": {
"status": 422,
"code": "parameter_invalid",
"param": "/registered_at",
"message": "La data deve essere compresa tra il 1° gennaio 1900 e il 31 dicembre 2100."
}
}
Errori
Gli errori adottano sempre la stessa struttura: un code stabile su cui il client può discriminare e un trace_id da riportare all'assistenza.
{
"error": {
"type": "https://api.officina.it/errors/insufficient_scope",
"status": 403,
"code": "insufficient_scope",
"message": "Questa chiave API non ha il permesso «invoices.write».",
"trace_id": "21fef3bbb28da12cff21e19cadf9c99d"
}
}
| Codice | Quando |
|---|---|
| api_key_invalid | Chiave assente o sconosciuta. |
| api_key_revoked | La chiave è stata revocata dal gestionale. |
| api_key_expired | La chiave ha superato la data di scadenza. |
| insufficient_scope | La chiave non ha il permesso richiesto. |
| plan_upgrade_required | Il piano non include l'accesso API. |
| parameter_invalid | Un campo della richiesta non è valido. param indica quale. |
| not_found | La risorsa non esiste o non appartiene alla tua officina. |
| waste_disposal_not_applicable | Lo smaltimento rifiuti non si può applicare al documento. reason dice perché: disabled, fully_paid, already_applied o nothing_to_charge. |
| endpoint_retired | L’endpoint è ritirato per la revisione dichiarata in X-API-Version. Il messaggio e l’header Link indicano quello che lo sostituisce. |
| ambiguous_number | Il numero corrisponde a più documenti (numeri ripetuti nello storico). candidates elenca i loro id: recupera quello giusto per id. |
Versionamento
L'API adotta due livelli di versionamento con finalità distinte: il percorso identifica la versione maggiore del contratto, l'header ne identifica la revisione.
| Elemento | Significato | Criterio di aggiornamento |
|---|---|---|
| /v1/ | Versione maggiore, indicata nel percorso della richiesta. | Viene incrementata esclusivamente in caso di modifiche non retrocompatibili: rimozione o ridenominazione di un campo, variazione della semantica di un endpoint. In tale evenienza viene pubblicato un nuovo percorso /v2 e /v1 resta operativo. |
| X-API-Version | Revisione datata all'interno della versione maggiore. | Viene aggiornata a ogni estensione retrocompatibile: nuovi endpoint, nuovi campi nelle risposte. Dichiarando una revisione, l'integrazione conserva il comportamento previsto a quella data anche in seguito all'evoluzione dell'API. |
Revisioni supportate, deprecate e non supportate
La revisione più recente è quella stabile e consigliata. Ogni revisione precedente è deprecata: continua a funzionare, ma va aggiornata. Una revisione diventa non supportata solo dopo un preavviso di almeno 90 giorni.
| Stato | Revisioni | Cosa succede |
|---|---|---|
| Attuale | 2026-10-31 | Comportamento completo, nessun avviso. È la revisione da dichiarare. |
| Deprecata | da 2026-07-15 a quella precedente l'attuale | Le chiamate funzionano come prima. Ogni risposta riporta l'header Deprecation (RFC 9745, con la data dell'ultima revisione) e Link: <…>; rel="deprecation" verso questa pagina. Quando viene fissata la data di ritiro, si aggiunge l'header Sunset (RFC 8594) con quella data. |
| Non supportata | prima di 2026-07-15 | Le chiamate vengono rifiutate con 400 api_version_retired. Si arriva qui solo dopo il periodo di preavviso indicato da Sunset. |
Revisioni
Le revisioni pubblicate di /v1, dalla più recente. Ogni revisione è retrocompatibile: aggiunge campi ed endpoint senza cambiare il significato di quelli esistenti, quindi un'integrazione che dichiara una revisione precedente continua a funzionare. I campi aggiunti compaiono nelle risposte qualunque revisione venga dichiarata: ignorali finché non ti servono. Gli endpoint toccati da una revisione lo segnalano nella documentazione con «dalla revisione».
| Revisione | Novità |
|---|---|
| 2026-10-31 attuale |
Numeri: ogni numero inviato in scrittura segue le regole dei moduli web, con lo stesso messaggio (422 parameter_invalid sul campo). Righe di schede e preventivi: quantity e unit_price accettano anche valori negativi (una riga negativa è uno sconto o un reso); unit_discount_rate è una percentuale intera 0–100 (12.5 veniva salvato come 12). Gli importi si arrotondano al centesimo come la colonna; si rifiutano negativi, zero e decimali dove il campo non li ammette: prezzi, scorte e sconto degli articoli, imponibile e IVA delle spese, importi dei pagamenti, gomme (quantity > 0, residual_mm ≥ 0), dati motore dei veicoli (≥ 0), fuel_level (0–100), costi interni della scheda, costi e prezzo dell'acquisto veicolo, bollo_virtuale (> 0). Spese con righe: PATCH /v1/expenses/{id} con total_before_tax o total_tax diversi da quelli salvati risponde 422 (gli importi si calcolano dalle righe) invece di 200 ignorandoli. Vale per tutte le revisioni. |
| 2026-10-30 | IBAN: bank_account_iban di fornitori e officina si salva maiuscolo e senza spazi, e in scrittura (POST/PATCH /v1/suppliers, PATCH /v1/company) deve superare il controllo IBAN (mod 97), altrimenti 422 parameter_invalid su /bank_account_iban. Conti: DELETE /v1/financial-accounts/{id} risponde 409 conflict per un conto con movimenti registrati e per l'ultimo conto Banca o Cassa, invece di 500 o di eliminarlo. Vale per tutte le revisioni. |
| 2026-10-29 | Fatture: nuovo POST /v1/invoices/{id}/unissue (Annulla emissione). Riporta in draft l'ultima fattura emessa del suo sezionale nell'anno, se non ha fattura elettronica (anche in bozza) né nota di credito: seq_number e issued_at si azzerano e il numero torna libero per la prossima emissione. Negli altri casi 409 conflict. L'endpoint risponde a qualsiasi revisione. |
| 2026-10-28 | Preventivi: nuovo stato draft (bozza). Ogni preventivo nuovo nasce in bozza, interna all'officina, e passa a waiting quando lo si segna come inviato (POST /v1/quotes/{id}/transitions con waiting) o quando parte un messaggio al cliente (email, SMS o WhatsApp); una bozza può anche passare direttamente ad accepted o rejected. Filtro status=draft in GET /v1/quotes e contatore draft in GET /v1/quotes/status-counts. POST /v1/quotes/{id}/convert accetta il corpo facoltativo {"mark_accepted": false} per creare la scheda senza segnare il preventivo come accettato. Le revisioni precedenti vedono le bozze come waiting (dettaglio, elenco, filtro e contatori) e creano preventivi waiting, come prima. |
| 2026-10-27 | Preventivi: nuovo campo expires_at, la data scadenza (YYYY-MM-DD, null = nessuna scadenza), nelle risposte e in POST /v1/quotes e PATCH /v1/quotes/{id} (null la rimuove). Non può precedere la data del preventivo: 422 parameter_invalid su /expires_at. Il campo è disponibile anche per le revisioni precedenti, che possono ignorarlo. |
| 2026-10-26 | Visura targa nell'app mobile (POST /v1/vehicles/license-plate-lookup, non esposta nell'API pubblica): da questa revisione il server attende HaynesPro fino a 60 secondi, come la ricerca sul web, invece di 17; 504 provider_timeout arriva quindi dopo 60 secondi. Le revisioni precedenti restano a 17 secondi. Nessun endpoint pubblico cambia. |
| 2026-10-25 | Marcatempo: le ore lavorate sulle schede lavoro, in sola lettura nell'API pubblica. GET /v1/time-types elenca i tipi di tempo dell'officina (is_wait: Attesa, il tempo della scheda che non conta come lavorato); GET /v1/time-entries elenca i tempi di un periodo, dal più recente, con i filtri from e to (giorni, ora di Roma, inclusi), worksheet_id e user_id e la paginazione limit / starting_after; GET /v1/worksheets/{id}/time riporta i tempi di una scheda con il riepilogo: ore lavorate, ore fatturate in manodopera, differenza, resa, ricavo per ora lavorata, le 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. Nell'app mobile anche le azioni: avvio, stop, annulla, «sto ancora lavorando», tempi a mano e correzioni, il timer in corso (GET /v1/running-timer), i flag modules.time_tracking e modules.time_tracking_plan in /v1/me e time_tracking_enabled in /v1/modules. |
| 2026-10-24 | Visura targa nell'app mobile (POST /v1/vehicles/license-plate-lookup, non esposta nell'API pubblica): quando il servizio targhe di HaynesPro non risponde correttamente la risposta usa gli stati da gateway, 502 provider_unavailable per una risposta di errore e 504 provider_timeout se non risponde in tempo. Le revisioni precedenti ricevono 503 service_unavailable per entrambi. Chi sta modificando un documento, solo nell'app mobile (GET/PUT/DELETE /v1/edit-presence/{entity_type}/{entity_id}): prima di aprire una modifica l'app chiede chi ha già aperto lo stesso documento negli ultimi 2 minuti, sul web o nell'app, e avvisa; la schermata di modifica lo segnala mentre è aperta. Nessun blocco: If-Match resta ciò che rifiuta il secondo salvataggio. Nessun endpoint pubblico cambia. |
| 2026-10-23 | Scontrini come gli altri documenti modificabili: 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) per clienti e veicoli: la definizione in /v1/{customer,vehicle}-field-settings porta options (da 1 a 20 voci, nell'ordine scelto), gli elementi di custom_field_values le riportano e il valore salvato è una delle opzioni. |
| 2026-10-22 | Modifiche concorrenti senza perdite: schede lavoro, preventivi, accettazioni, spese, fatture, note di credito, appuntamenti, fornitori e treni gomme riportano version nel dettaglio (come già clienti, veicoli e articoli). Ogni salvataggio che cambia il documento la incrementa. Da questa revisione PATCH su questi documenti, su clienti, veicoli e articoli e il batch delle righe (POST /v1/{worksheets,quotes,checkins}/{id}/line-items/batch) richiedono l'header If-Match con la version letta: senza rispondono 428 precondition_required; se nel frattempo il documento è stato salvato da un'altra parte rispondono 409 conflict e non salvano nulla, così una copia vecchia non sovrascrive mai le modifiche di un collega. If-Match: * scrive comunque. Le revisioni precedenti accettano If-Match facoltativo. Cestino: GET /v1/trash elenca i documenti e i record eliminati dell'officina (fatture, scontrini, note di credito, schede lavoro, preventivi, clienti, veicoli, articoli, appuntamenti e gli altri), dal più recente, con chi li ha eliminati e quando, l'importo derivato dal documento di origine e lo stato di emissione. Filtri type, q e period (c30, c60, c180, c365, all); ogni pagina riporta i conteggi per tipo, il periodo applicato e i periodi con il flag allowed. Lo storico dipende dal piano: 30 giorni, 180 con Pro, tutto con Business e Illimitato; un periodo oltre il piano risponde 402 plan_upgrade_required su /period. Sola lettura, con il nuovo permesso dedicato trash.read (da assegnare alla chiave); nessun ripristino. Targa duplicata: POST /v1/vehicles e PATCH /v1/vehicles/{id} rifiutano con 409 license_plate_taken una targa che un altro veicolo attivo dell'officina ha già (senza distinguere maiuscole, spazi e trattini), con l'id di quel veicolo in candidates; allow_duplicate_license: true nel corpo salva comunque. Le revisioni precedenti non controllano. |
| 2026-10-19 | Report v2: le card del report con gli stessi dati della pagina Report del gestionale. GET /v1/reports/{widget} restituisce una card delle schede Officina ed Economico (lavorato, schede chiuse, preventivi, clienti, categorie, durata, tempi del marcatempo, fatturato per data documento, incassato, margine, da fatturare, da incassare per anzianità, metodi di pagamento, spese, composizione, clienti principali); una card senza niente da mostrare risponde visible: false. Il report Fiscale: GET /v1/reports/fiscal (profilo fiscale e periodi di liquidazione), PUT /v1/reports/fiscal/profile, GET /v1/reports/fiscal/{widget} (liquidazione IVA, aliquote, storico, acconto, scadenze F24) e GET /v1/reports/fiscal/export.zip (i CSV per il commercialista). GET /v1/report/{widget} è deprecato: dichiarando questa revisione risponde 410 endpoint_retired; le revisioni precedenti lo ricevono ancora, con gli header Deprecation e Link verso il sostituto. |
| 2026-10-15 | Costi di lavorazione delle schede lavoro. Ogni riga ha unit_cost, il costo interno unitario IVA esclusa: si fissa quando la riga viene scritta (il prezzo d'acquisto dell'articolo in quel momento, o il valore inviato) e non cambia se poi cambia l'articolo. total_work_cost e total_article_cost sono la somma delle righe (manodopera = unità h, gg, lav, min; il resto ricambi) oppure il valore inserito a mano, indicato da work_cost_manual / article_cost_manual. PATCH /v1/worksheets/{id} accetta total_work_cost e total_article_cost: un numero li fissa a mano, null torna al calcolo automatico. Il batch delle righe accetta unit_cost in add e update; in update senza unit_cost la riga tiene il suo costo se l'articolo non cambia. Anche le righe dei preventivi hanno unit_cost (stessa regola), e la scheda creata da un preventivo eredita i costi delle sue righe. Vale per tutte le revisioni: i costi letti sono ora sempre quelli corretti, anche nell'elenco (prima valevano 0). |
| 2026-10-14 | Tipi di riga: le righe di schede lavoro, preventivi, fatture e scontrini hanno line_type (item riga con importo, note solo testo, separator riga vuota). Note e separatori si stampano senza quantità, prezzo e IVA e hanno importi a zero. POST /v1/worksheets/{id}/line-items/batch e /v1/quotes/{id}/line-items/batch accettano line_type in add e update (predefinito item; in update, se assente, il tipo resta quello della riga). Per una nota serve solo description, per un separatore niente. Le revisioni precedenti, che non inviano line_type, mantengono la regola di prima (riga a prezzo 0, quantità fino a 1 e senza articolo = solo testo); in update un prezzo o un articolo rendono sempre la riga una riga con importo. |
| 2026-10-13 | Paginazione vera per gli elenchi che tornavano interi: GET /v1/calendar-entries (pagine di limit voci, predefinito 20, massimo 100, ordinate per inizio, per intervallo, cliente e veicolo), GET /v1/receipts (filtri e conteggi delle schede calcolati sul database), GET /v1/articles/{id}/movements e GET /v1/vehicles/{id}/worksheets, /quotes e /checkins (dal più recente). next_starting_after porta alla pagina successiva. Le revisioni precedenti restano invariate: questi elenchi tornano interi (l'intervallo degli appuntamenti al massimo 2000 voci, poi has_more; il filtro per cliente o veicolo degli appuntamenti si ferma a limit). In ogni revisione from/to degli appuntamenti è un intervallo esatto [from, to), non più esteso a giorni interi. |
| 2026-10-06 | Smaltimento rifiuti: GET /v1/worksheets/{id}/waste-disposal e GET /v1/quotes/{id}/waste-disposal dicono se l'addebito si può applicare e quanto vale; POST sullo stesso percorso aggiunge la riga (una volta sola) e restituisce il documento aggiornato. Se non è applicabile la risposta è 409 waste_disposal_not_applicable con il motivo nel nuovo campo error.reason (anche fully_paid per le schede già interamente pagate). Le impostazioni sono in GET /v1/company (waste_disposal_enabled, waste_disposal_rate, waste_disposal_base, waste_disposal_description). |
| 2026-10-05 | Codice motore dei veicoli: engine_code in ogni risposta dei veicoli e in creazione e modifica (salvato senza spazi iniziali e finali e in maiuscolo, massimo 64 caratteri; stringa vuota o null lo cancella; assente in modifica = invariato). Nuova opzione engine_code in field_visibility (GET/PUT /v1/vehicle-field-settings e dettaglio veicolo), attiva in modo predefinito. |
| 2026-10-04 | Recupero per numero: GET /v1/{risorsa}/by-number/{year}/{number} restituisce schede lavoro, preventivi, accettazioni, fatture, note di credito, scontrini, spese, depositi gomme, veicoli in stock e vendite dal numero mostrato nell'app (per esempio 12/2026), con la stessa risposta del recupero per id. Fatture, note di credito e scontrini accettano sezionale_id (oppure default per la numerazione senza sezionale). Un numero che corrisponde a più documenti restituisce 409 ambiguous_number con gli id in candidates. POST /v1/invoices/{id}/issue accetta issued_at solo tra oggi e 12 giorni fa (fattura immediata), altrimenti 422. Gli articoli (magazzino e listino manodopera) riportano le categorie per cui si usano: work_category_ids e work_categories in ogni risposta, work_category_ids in creazione e modifica (assente = invariato, [] o null = nessuno) e il filtro work_category_id su GET /v1/articles (ripetibile, almeno uno). |
| 2026-10-03 | Più categorie per documento: schede lavoro, preventivi, accettazioni e appuntamenti espongono work_category_ids e work_categories (tutte le categorie, nell'ordine scelto) e accettano work_category_ids in creazione e modifica; le fatture espongono work_category_names. work_category_id resta valido in ogni revisione per i client che gestiscono una sola categoria e riporta il primo dell'elenco. I nomi delle categorie sono unici senza distinguere maiuscole e minuscole: creare un nome già esistente restituisce 200 con quello esistente, rinominare su un nome già usato restituisce 409. |
| 2026-09-28 | Campi personalizzati dei clienti: GET/PUT /v1/customer-field-settings (visibilità di compleanno e note, fino a 10 campi personalizzati), custom_fields in creazione e modifica dei clienti, custom_field_values e field_visibility nel dettaglio cliente. |
| 2026-09-05 | Concessionaria: dettaglio e creazione degli stock (GET /v1/vehicle-stocks/{id}, POST /v1/vehicle-stocks), download degli allegati di stock e vendite, dettaglio vendita arricchito (righe di ricavo e costo, margine, giorni in stock, documenti, veicolo e fattura collegata). |
| 2026-07-20 | GET /v1/checkins/status-counts (contatori delle accettazioni per stato) e ordinamento sort/direction su GET /v1/checkins. |
| 2026-07-19 | Il dettaglio della scheda lavoro espone frozen (vero quando una fattura collegata ha una fattura elettronica trasmessa). Una fattura emessa da sola non blocca più le modifiche alla scheda: le blocca solo una fattura elettronica trasmessa. |
| 2026-07-15 | Revisione di lancio, la minima supportata: anagrafiche, veicoli, schede lavoro, preventivi, accettazioni, fatture, scontrini, appuntamenti e cataloghi in lettura e scrittura. |
Specifica OpenAPI
Il contratto completo in formato OpenAPI 3.1, con tutti gli endpoint, i campi e gli errori, utilizzabile con qualsiasi generatore di client. Disponibile in YAML e in JSON: stesso documento, due formati.
Usare officina.it da Claude e ChatGPT
Per fare domande sui dati della tua officina a un assistente AI non serve una chiave API: collega il server MCP di officina.it ed entra con il tuo login. Il collegamento è in sola lettura, è incluso nei piani Pro e superiori e in tutte le prove gratuite, e si revoca da Impostazioni › Sessioni attive › App collegate.
# indirizzo del server MCP (streamable HTTP, OAuth)
https://mcp.officina.it/mcp
Passo per passo: come collegare officina.it a Claude o ChatGPT. Descrizione per i client: server card. Come ottenere le credenziali, con OAuth o con una chiave API: auth.md.
Per agenti AI
Se usi Claude, Cursor, Codex o un altro agente di coding, incolla il prompt qui sotto (in inglese, più affidabile con gli agenti): scarica da solo le istruzioni e l'OpenAPI. Non serve installare nulla in locale.