OpenTabacchi

Riferimento API

Base: https://opentabacchi.org/v1. Tutte le risposte sono JSON con codifica UTF-8, salvo i file del dataset che sono gzip. Tutti gli esempi usano dati di fantasia.

Autenticazione

Ogni richiesta porta una chiave API (come ottenerla) in una di queste intestazioni:

Authorization: Bearer otb_la_tua_chiave
X-Api-Key: otb_la_tua_chiave

Senza chiave valida: 401, intestazione WWW-Authenticate: Bearer e corpo { "error": "invalid_api_key", "message": "Chiave API mancante o non valida." }.

Limiti e intestazioni

Le chiamate GET consumano una lettura ciascuna (10 al giorno per account, giorno UTC, anche quando rispondono 304). POST /v1/barcodes consuma invece il contatore degli invii (100 elementi all'ora, valore predefinito) e non tocca le letture. Ogni risposta riporta il contatore usato:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 8
X-RateLimit-Reset: 1792454400

Spiegazione completa e strategia di cache in Limiti e uso corretto.

Codici di stato ed errori

StatoQuandoCorpo
200Richiesta eseguita.JSON o file gzip.
304Download con If-None-Match uguale all'ETag corrente.Vuoto. Conta comunque come lettura.
400Batch di barcode vuoto o troppo grande.{ "error": "empty_batch" } oppure { "error": "batch_too_large", "max": 500 }
401Chiave mancante, non valida, revocata o account bloccato.{ "error": "invalid_api_key" }
404Nessuna release ancora pubblicata (solo /version e i file).{ "error": "no_release", "message": "Nessuna release ancora pubblicata." }
429Limite superato; intestazione Retry-After in secondi.{ "error": "rate_limited", "message": "…", "retryAfterSeconds": 3600 }; per gli invii anche limit e remaining.

Gli errori hanno sempre un campo error con un codice stabile e, quando serve, un message in italiano destinato ai log, non all'utente finale.

GET/v1/version1 lettura

Il manifest della release corrente. È la chiamata da fare una volta al giorno: se version coincide con quella già importata, non c'è altro da scaricare.

curl
curl -H "Authorization: Bearer otb_la_tua_chiave" https://opentabacchi.org/v1/version
200
{
  "version": "20261014.2",
  "tariffDate": "2026-10-14",
  "generatedAt": "2026-10-14T09:00:00+00:00",
  "source": "Agenzia delle Dogane e dei Monopoli (ADM)",
  "schemaVersion": 2,
  "counts": { "products": 4343, "barcodes": 120 },
  "files": {
    "json":   { "url": "https://opentabacchi.org/v1/dataset.json.gz",   "sha256": "9f2c…", "size": 83912 },
    "sqlite": { "url": "https://opentabacchi.org/v1/dataset.sqlite.gz", "sha256": "4b71…", "size": 161280 },
    "blite":  { "url": "https://opentabacchi.org/v1/dataset.blite.gz",  "sha256": "c0de…", "size": 346112,
               "bliteVersion": "5.2.3", "schemaVersion": 2 }
  }
}
CampoTipoSignificato
versionstringaAAAAMMGG.n: data della tariffa in vigore e progressivo. Cambia a ogni modifica dei dati pubblicati.
tariffDatedata ISOData di decorrenza della tariffa in vigore.
generatedAtdata e ora ISO 8601Quando la release è stata costruita.
sourcestringaSempre «Agenzia delle Dogane e dei Monopoli (ADM)».
schemaVersioninteroVersione della forma dei dati, uguale nei tre formati. Oggi 2.
counts.products, counts.barcodesinteriRighe presenti nei file.
files.*.url, sha256, sizestringa, esadecimale, byteURL assoluto, impronta e dimensione del file compresso.
files.blite.bliteVersionstringaVersione del pacchetto BLite con cui è scritto il file: va aperto con la stessa. Vedi BLite.

Se non è ancora stata pubblicata alcuna release: 404 con error: "no_release".

GET/v1/dataset.json.gz1 lettura
GET/v1/dataset.sqlite.gz1 lettura
GET/v1/dataset.blite.gz1 lettura

Il dataset completo nel formato scelto, compresso con gzip (Content-Type: application/gzip, Content-Disposition: attachment; filename=opentabacchi-<versione>.<formato>.gz). I tre file hanno lo stesso contenuto; lo schema è in Formati e schema.

La risposta porta ETag: "<sha256>", cioè l'impronta del file tra virgolette. Inviando If-None-Match con quel valore si riceve 304 senza corpo quando il file è ancora lo stesso; la lettura viene consumata ugualmente.

curl
curl -H "Authorization: Bearer otb_la_tua_chiave" \
     -H 'If-None-Match: "9f2c…"' \
     -o opentabacchi.sqlite.gz -w "%{http_code}\n" \
     https://opentabacchi.org/v1/dataset.sqlite.gz

Verifica sempre lo SHA-256 del file ricevuto contro files.*.sha256 del manifest prima di importarlo.

POST/v1/barcodes1 invio per elemento

Propone abbinamenti tra codice a barre e codice AAMS. Fino a 500 elementi per chiamata, entro il contatore orario degli invii. Un batch che supera i residui viene rifiutato per intero con 429. Le regole di approvazione sono in Codici a barre.

richiesta
POST /v1/barcodes
Authorization: Bearer otb_la_tua_chiave
Content-Type: application/json

{
  "items": [
    { "barcode": "4006381333931", "admCode": 12345 },
    { "barcode": "4006381333948", "admCode": 12345, "kind": "carton", "units": 10 }
  ]
}
CampoObbligatorioValori
barcodesì8, 12, 13 o 14 cifre con cifra di controllo GS1 valida.
admCodesìCodice AAMS intero, presente nella tariffa.
kindnopack (predefinito) o carton.
unitsnoPezzi per codice: 1 per pack (predefinito), da 2 a 1000 per carton.
contributorRefnoSolo per gli account di tipo integrazione: identificativo opaco del punto vendita (1–64 caratteri tra lettere, cifre, . _ : -).
200
{
  "accepted": 1, "unchanged": 0, "rejected": 1,
  "results": [
    { "index": 0, "barcode": "4006381333931", "outcome": "accepted", "reason": null, "status": "to_confirm" },
    { "index": 1, "barcode": "4006381333948", "outcome": "rejected", "reason": "invalid_checksum", "status": null }
  ]
}

Per ogni elemento, nell'ordine inviato: outcome è accepted (segnalazione registrata o aggiornata), unchanged (avevi già inviato lo stesso abbinamento) o rejected; status è lo stato del codice a barre dopo l'elaborazione (to_confirm, approved, conflict, revoked) e manca per i rifiuti. Ogni account ha un solo voto per codice a barre: un nuovo invio sostituisce il precedente.

Motivo del rifiutoSignificato
invalid_barcodeLunghezza o caratteri non ammessi (servono 8, 12, 13 o 14 cifre).
invalid_checksumLa cifra di controllo non torna.
unsupported_barcodePrefisso EAN-13 di un altro tipo di codice: editoria (977, 978, 979), uso interno o peso variabile (2), buoni e rimborsi (98, 99).
unknown_adm_codeIl codice AAMS non esiste nella tariffa.
invalid_kindkind diverso da pack o carton.
invalid_unitsunits diverso da 1 per pack o fuori da 2–1000 per carton.
barcode_revokedL'amministratore ha revocato questo codice: non accetta più segnalazioni.
contributor_ref_not_allowedcontributorRef inviato da un account che non è un'integrazione.
invalid_contributor_refFormato di contributorRef non ammesso.
contributor_blockedIl contributore indicato è stato bloccato dall'amministratore.
429, batch oltre il residuo orario
HTTP/1.1 429 Too Many Requests
Retry-After: 1800
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 30

{ "error": "rate_limited",
  "message": "Limite orario di invii raggiunto: il batch non è stato elaborato.",
  "limit": 100, "remaining": 30, "retryAfterSeconds": 1800 }
GET/v1/my-submissions1 lettura

Le segnalazioni del tuo account, dalla più recente, con lo stato attuale del codice a barre. Parametri: limit (predefinito 500, massimo 1000) e offset.

curl
curl -H "Authorization: Bearer otb_la_tua_chiave" "https://opentabacchi.org/v1/my-submissions?limit=100&offset=0"
200
{
  "items": [
    { "barcode": "4006381333931", "admCode": 12345, "kind": "pack", "units": 1,
      "contributorRef": null, "submittedAt": "2026-10-14T10:12:00+00:00", "status": "to_confirm" }
  ]
}

status vale to_confirm, approved, conflict, revoked oppure outvoted: il codice è stato approvato con un abbinamento diverso dal tuo (altro codice AAMS, tipo o numero di pezzi).

GET/v1/me1 lettura

Conferma la chiave e mostra i contatori. Costa una lettura come le altre: non usarla per «controllare prima» ogni chiamata.

200
{
  "email": "sviluppo@esempio.test",
  "type": "User",
  "limits": {
    "read":   { "period": "day",  "limit": 10,  "used": 2, "remaining": 8,  "resetsAt": "2026-10-15T00:00:00+00:00" },
    "submit": { "period": "hour", "limit": 100, "used": 0, "remaining": 100, "resetsAt": "2026-10-14T11:00:00+00:00" }
  }
}

type è User, Supplier o Integration.