Vai al contenuto

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.

  1. 1Offerte · Leggi gli ID attivi con GET /offers
  2. 2Job · Dichiara pratiche e file con POST /simulation-jobs
  3. 3Upload · Carica ogni file sul suo URL firmato
  4. 4Submit · Metti il job in coda
  5. 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.

Verifica accesso
curl 'https://api.getwattson.it/v1/offers' \
  -H 'Authorization: Bearer wattson_sk_...'

Inizia

Flusso asincrono

PassoChiamataCosa conservare
1 · ScopriGET /offersID offerta per luce/gas
2 · CreaPOST /simulation-jobsjob.id e upload_url
3 · CaricaPUT upload_urlesito HTTP di ogni file
4 · AvviaPOST /simulation-jobs/{id}/submitstatus del job
5 · Riceviwebhook 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

GET/offers

Elenca 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.

Richiesta
curl 'https://api.getwattson.it/v1/offers' \
  -H 'Authorization: Bearer wattson_sk_...'

Simulation API

Crea job

POST/simulation-jobs

Crea 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

POST/simulation-jobs/{job_id}/submit

Esegui 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

GET/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

LimiteValore
Pratiche per job10
File per pratica4
Dimensione per file15 MiB
Dimensione per pratica30 MiB
Pagine per pratica35
Job in attesa di upload per integrazione10
Retention dei risultati90 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 envelope
{
  "error": {
    "code": "INPUT_INVALID",
    "message": "Il job deve contenere da 1 a 10 pratiche.",
    "request_id": "617c52a8-cac0-491d-8765-e138287fffc9",
    "details": { "field": "bills" }
  }
}