API reference
Wattson API
Invia una o più pratiche, scegli le offerte e ricevi estrazione e simulazione in modo asincrono.
- Base URL
- https://api.getwattson.it/v1
- Autenticazione
- Bearer API key
- Elaborazione
- Asincrona + webhook
- Formato
- JSON · OpenAPI 3
Inizia
Quickstart
Le chiamate autenticate partono dal tuo backend. In produzione salva WATTSON_API_KEY nel secret manager e usa un riferimento pratica stabile come Idempotency-Key.
- 1Offerte · Leggi gli ID attivi con GET /offers
- 2Job · Dichiara pratiche e file con POST /simulation-jobs
- 3Upload · Carica ogni file sul suo URL firmato
- 4Submit · Metti il job in coda
- 5Risultati · Ricevi il webhook oppure fai polling
import { readFile } from "node:fs/promises";
const baseUrl = "https://api.getwattson.it/v1";
const apiKey = process.env.WATTSON_API_KEY;
if (!apiKey) throw new Error("WATTSON_API_KEY mancante");
async function api(path, options = {}) {
const response = await fetch(baseUrl + path, {
...options,
headers: {
Authorization: "Bearer " + apiKey,
"Content-Type": "application/json",
...options.headers,
},
});
const body = await response.json();
if (!response.ok) throw new Error(JSON.stringify(body.error));
return body;
}
// 1. Scopri gli ID validi; non copiarli da email o CTE.
const { offers } = await api("/offers");
const luce = offers.find((offer) => offer.service === "luce");
if (!luce) throw new Error("Nessuna offerta luce disponibile");
const file = await readFile("./bolletta.pdf");
const created = await api("/simulation-jobs", {
method: "POST",
headers: { "Idempotency-Key": "crm-7841-v1" },
body: JSON.stringify({
external_reference: "crm-7841",
bills: [{
reference: "cliente-rossi",
offers: { luce: [luce.id] },
files: [{
name: "bolletta.pdf",
content_type: "application/pdf",
size_bytes: file.byteLength,
page_count: 4,
}],
}],
}),
});
const upload = created.items[0].uploads[0];
const uploaded = await fetch(upload.upload_url, {
method: upload.method,
headers: upload.headers,
body: file,
});
if (!uploaded.ok) throw new Error("Upload non riuscito");
await api("/simulation-jobs/" + created.job.id + "/submit", { method: "POST" });
let result;
do {
await new Promise((resolve) => setTimeout(resolve, 2500));
result = await api("/simulation-jobs/" + created.job.id);
} while (!["completed", "completed_with_errors", "failed", "expired"].includes(result.job.status));
console.log(result.job.items);Inizia
Autenticazione
Passa la chiave con Authorization: Bearer wattson_sk_.... Una chiave simulation_api accede soltanto a offerte e job della propria integrazione.
La Simulation API è disponibile soltanto sui piani Company ed Enterprise. Un piano non abilitato riceve 402 BILLING_NOT_ENTITLED su offerte e job.
curl 'https://api.getwattson.it/v1/offers' \
-H 'Authorization: Bearer wattson_sk_...'Inizia
Flusso asincrono
| Passo | Chiamata | Cosa conservare |
|---|---|---|
| 1 · Scopri | GET /offers | ID offerta per luce/gas |
| 2 · Crea | POST /simulation-jobs | job.id e upload_url |
| 3 · Carica | PUT upload_url | esito HTTP di ogni file |
| 4 · Avvia | POST /simulation-jobs/{id}/submit | status del job |
| 5 · Ricevi | webhook o GET /simulation-jobs/{id} | risultati finali |
Riusa la stessa idempotency key per ritentare la creazione, ripeti il submit se perdi la risposta e deduplica i webhook con X-Wattson-Event-Id.
Simulation API
Elenca offerte
/offersElenca soltanto le offerte attive associate alla tua integrazione. Filtra con ?service=luce o ?service=gas.
Usa l'id restituito dall'endpoint. I codici commerciali e ARERA aiutano a riconoscere l'offerta, ma l'UUID non va copiato da una email o fissato per sempre nel codice: una nuova versione può avere un ID diverso.
curl 'https://api.getwattson.it/v1/offers' \
-H 'Authorization: Bearer wattson_sk_...'Simulation API
Crea job
/simulation-jobsCrea il lavoro e restituisce un URL firmato per ciascun file dichiarato.
Idempotency-Keyheader- Obbligatoria; unica per il contenuto logico.
external_referencestring- Riferimento del job nel tuo gestionale.
bills[].referencestring- Riferimento della singola pratica.
bills[].offersobject- ID distinti per luce e/o gas.
bills[].filesarray- Nome, MIME type, byte e pagine di ogni file.
curl -X POST 'https://api.getwattson.it/v1/simulation-jobs' \
-H 'Authorization: Bearer wattson_sk_...' \
-H 'Idempotency-Key: onda-pratica-7841-v1' \
-H 'Content-Type: application/json' \
-d '{
"external_reference": "onda-pratica-7841",
"bills": [{
"reference": "cliente-rossi",
"offers": {
"luce": ["11111111-1111-4111-8111-111111111111"],
"gas": ["22222222-2222-4222-8222-222222222222"]
},
"files": [{
"name": "bolletta-dual.pdf",
"content_type": "application/pdf",
"size_bytes": 483021,
"page_count": 6
}]
}]
}'Simulation API
Upload e invio
/simulation-jobs/{job_id}/submitEsegui un PUT binario per ogni URL restituito. Usa esattamente metodo e header presenti in uploads[]; non aggiungere la API key. Gli URL scadono e non vanno salvati nei log.
Poi chiama il submit: controlla tutti i file e mette in coda estrazione e simulazione. Restituisce 202 ed è idempotente.
Se un file manca o differisce per MIME type o dimensione, il job non parte. Crea un nuovo job se devi sostituire il file dichiarato o se il submit restituisce 410 JOB_EXPIRED.
curl -X PUT "$UPLOAD_URL" \
-H 'Content-Type: application/pdf' \
--data-binary '@bolletta-dual.pdf'Simulation API
Leggi risultati
/simulation-jobs/{job_id}Restituisce lo stato e, quando disponibili, estrazione e confronti di ogni pratica.
Stato del job
waiting_for_upload- Attende uno o più file.
queued- File validati, lavoro in coda.
processing- Almeno una pratica è in corso.
completedterminale- Tutte le pratiche sono complete.
completed_with_errorsterminale- Risultati parziali o da rivedere.
failedterminale- Nessuna pratica utilizzabile.
expiredterminale- Risultato oltre la retention.
Stato della pratica
waiting_for_upload- Attende i file.
extracting- Lettura bolletta in corso.
simulation_queued- Estrazione pronta.
simulating- Confronto in corso.
completed- Risultato disponibile.
needs_review- Serve una verifica umana.
quota_exceeded- Credito non disponibile.
failed- Pratica non elaborabile.
Ferma il polling su completed, completed_with_errors, failed, expired. Usa alcuni secondi di intervallo e backoff progressivo.
Negli errors[] con codice OFFER_NOT_ELIGIBLE, il campo opzionale offer_id identifica l'offerta esclusa: puoi così mostrare gli altri confronti validi senza scartare l'intera pratica.
curl 'https://api.getwattson.it/v1/simulation-jobs/5755422e-cdee-4f16-ad2f-7941b553480c' \
-H 'Authorization: Bearer wattson_sk_...'Notifiche
Webhook
Quando il job raggiunge uno stato terminale, Wattson invia simulation.job.completed, simulation.job.completed_with_errors oppure simulation.job.failed all'URL HTTPS configurato. La consegna è at least once: può essere ritentata o arrivare fuori ordine.
X-Wattson-Event-Idheader- Deduplica la consegna.
X-Wattson-Timestampheader- Unix timestamp incluso nella firma.
X-Wattson-Signatureheader- v1=<hex HMAC-SHA256>
Calcola l'HMAC su ${timestamp}.${rawBody} usando il body grezzo e il webhook secret. Confronta in tempo costante e rifiuta timestamp troppo vecchi prima di decodificare il JSON.
{
"id": "838aa71d-aea5-456b-9392-48d65b16c0ac",
"type": "simulation.job.completed",
"created_at": "2026-09-04T09:18:41Z",
"data": {
"job_id": "5755422e-cdee-4f16-ad2f-7941b553480c",
"status": "completed",
"external_reference": "onda-pratica-7841"
}
}Riferimento
Limiti e crediti
| Limite | Valore |
|---|---|
| Pratiche per job | 10 |
| File per pratica | 4 |
| Dimensione per file | 15 MiB |
| Dimensione per pratica | 30 MiB |
| Pagine per pratica | 35 |
| Job in attesa di upload per integrazione | 10 |
| Retention dei risultati | 90 giorni |
1 POD o 1 PDR distinto = 1 credito. Confrontare la stessa fornitura con più offerte non moltiplica i crediti. Una pratica dual con un POD e un PDR usa due crediti.
Oltre i 10 job non scaduti in attesa di upload, la richiesta successiva restituisce 429 PENDING_JOB_LIMIT_REACHED con l'header Retry-After: completa o lascia scadere un job prima di crearne un altro.
Un job abbandonato in waiting_for_upload scade dopo 2 ore. Per i job inviati, i file grezzi seguono il cleanup breve dell'estrazione, normalmente entro circa 24 ore.
I risultati restano disponibili fino a 90 giorni. Alla scadenza payload e identificativi vengono minimizzati e resta soltanto un tombstone tecnico per idempotenza e audit; il risultato completo non è più disponibile via API. Salva nel tuo sistema solo ciò che serve al processo commerciale.
Riferimento
Errori
Ogni risposta include X-Request-Id. Negli errori lo stesso valore compare nel body: conservalo insieme a endpoint e status HTTP, mai insieme a chiavi o URL firmati.
400- Payload, header o stato non valido.
401- API key mancante, revocata o non valida.
402- Simulation API fuori dai piani Company/Enterprise o quota esaurita al submit.
403- Scope non autorizzato per l'endpoint.
404- Risorsa non trovata nel tenant.
409- Idempotency key o stato incompatibile.
410- Finestra di upload scaduta o risultato oltre la retention: crea un nuovo job.
413- Limite di file, pagine o byte superato.
422- File, offerta o bolletta non elaborabile.
429- Troppe estrazioni o troppi job in attesa di upload: rispetta Retry-After.
500- Errore interno: ritenta e conserva il request ID.
{
"error": {
"code": "INPUT_INVALID",
"message": "Il job deve contenere da 1 a 10 pratiche.",
"request_id": "617c52a8-cac0-491d-8765-e138287fffc9",
"details": { "field": "bills" }
}
}