Skip to content

Integrazione Market Scanner ​

Integra con un assistente AI
Scarica o copia il prompt di integrazione per Market Scanner e incollalo in Claude Code, Cursor o Copilot: contiene auth, esempi end-to-end e checklist.
Apri

Le API dello Scanner di Dokicasa permettono di ottenere visure, ispezioni, note, mappe catastali e ricerche da indirizzo. Sono asincrone: questa guida ti mostra come riceverne i risultati nel modo più efficiente, evitando il polling quando possibile.

🎮 Prova le API dal vivo

Apri il Playground interattivo: incolli il tuo token, lanci ogni chiamata in sandbox e vedi arrivare socket e polling in tempo reale, testi i webhook, e copi il codice pronto in cURL, Python, Node, JavaScript e PHP.

Base URLhttps://api.dokicasa.it
AuthAuthorization: Bearer <api_token>
Content-Typeapplication/json

Documentazione e autenticazione ​

La documentazione interattiva di tutti gli endpoint è su Swagger, sezione Scanner: api.dokicasa.it/api/documentation#/Scanner (ricerche, polling, recupero risultati).

Tutte le chiamate vanno autenticate con un Bearer token:

http
Authorization: Bearer YOUR_API_TOKEN

WARNING

Il token non deve mai finire in repo pubblici o sul frontend: trattalo come una password.

Endpoint principali ​

Tabella riassuntiva — lo schema completo è su Swagger.

MetodoPathCosa fa
POST/api/scanner/{type}Crea una richiesta scanner (visura / ispezione / mappa / list-addresses / …). Ritorna scanner_request_id.
GET/api/scanner-request/{id}Stato richiesta + risultato. Per i PDF qui trovi il link result.pdf_path (il file NON è nel JSON: si scarica dal link); per list-addresses i dati sono già nel JSON.
GET/api/scanner-request/{id}/resultsRisultati strutturati per le ricerche "normali" (es. immobili da indirizzo).
WSprivate-scanner_request.{id}Canale WebSocket privato per gli aggiornamenti realtime.
GET/api/scanner/healthStato del servizio: Sister funziona, è lento, è fermo o in manutenzione programmata, anche per tipo di scansione. Dettagli

Stati della richiesta ​

ENQUEUED  →  SCANNING  →  SUCCESS
                       ╲→  ERROR

ENQUEUED e SCANNING sono transitori; SUCCESS ed ERROR sono finali.

Tipi di interrogazione ​

Ogni interrogazione è un POST /api/scanner/{type}. Qui sotto i type disponibili (lo schema dei parametri di ognuno è sullo Swagger, sezione Scanner).

Ricerche dati — il risultato è JSON nel campo result:

typeCosa faCampo in result
search-fiscal-codeNominativi con codice fiscale da nome, cognome e provincia facoltativa (gratuita)anagrafiche
search-companySocietà (persone giuridiche) con codice fiscale da ragione sociale e provincia facoltativa (a listino)anagrafiche
tax-dataImmobili posseduti da un CF / P.IVA (provincia e comune facoltativi)immobili
catastoImmobili da foglio / particellaimmobili
intestatari-catastoProprietari di una particellaintestatari
list-addressesNormalizza un indirizzo → lista di vieaddresses
immobili-by-nameImmobili a livello nazionale da nome/cognome (2 fasi)immobili

Documenti — nessuna chiamata API restituisce il PDF: nel campo result.pdf_path trovi il link da cui scaricarlo (vedi nota sotto):

typeDocumento
visura-catasto / visura-tax-dataVisura catastale (da dati catastali / da CF-P.IVA)
ispezione-catasto / ispezione-tax-dataIspezione ipotecaria
nota-catasto / nota-tax-dataNota di trascrizione
mappa-catastoMappa catastale
elaborato-planimetricoElaborato planimetrico
elenco-immobiliElenco immobili

IMPORTANT

catasto ≠ visura catastale. catasto è la ricerca immobili da foglio/particella e restituisce l'elenco immobili (dati JSON). La visura catastale in PDF è invece visura-catasto (da dati catastali) o visura-tax-data (da CF/P.IVA). Analogamente la visura/ispezione ipotecaria è ispezione-catasto / ispezione-tax-data.

Visura attuale o storica ​

Le due visure non si comportano allo stesso modo con tipo_visura:

Chiamatatipo_visuraVisura che ricevi
visura-catasto (per immobile)ANALITICAstorica analitica
SINTETICAstorica sintetica
COMPLETAattuale
visura-tax-data (per soggetto)ANALITICA | SINTETICA | COMPLETAattuale per soggetto
visura-tax-data + "storica": trueANALITICAstorica analitica per soggetto
SINTETICAstorica sintetica per soggetto

WARNING

Per la visura per immobile COMPLETA non vuol dire "con più dati": è l'unica senza storico. Per la visura per soggetto, invece, la storica si chiede solo con "storica": true: senza, anche ANALITICA e SINTETICA restituiscono la visura attuale.

Visura per soggetto: attuale o storica ​

  • Attuale (default, senza storica): gli immobili di cui il soggetto è intestatario oggi nella provincia indicata. Se oggi in quella provincia non ha niente, Sister non produce la visura.
  • Storica ("storica": true): tutti gli immobili di cui il soggetto è stato intestatario nella provincia, anche se oggi non li possiede più, con i passaggi di intestazione. È la "Visura storica per soggetto" di Sister: serve per esempio per chi ha venduto, per un defunto o per ricostruire il patrimonio passato.
json
POST /api/scanner/visura-tax-data
{
  "tax_code": "RSSMRA80A01H501U",
  "provincia": "LT",
  "tipo_visura": "ANALITICA",
  "storica": true
}
RegolaDettaglio
Persone fisiche e giuridichetax_code può essere un codice fiscale o una partita IVA (società)
Solo ANALITICA o SINTETICAcon COMPLETA risponde 422
Una provincia per chiamatala visura copre tutta la provincia indicata in provincia; se il soggetto ha avuto immobili in più province serve una chiamata per ciascuna
Stesso prezzocosta come una visura-tax-data normale
Tempila storica passa dal codice di sicurezza di Sister, risolto in automatico: può richiedere qualche secondo in più

Visura a una data: situazione_al ​

Il modulo di Sister ha il campo "Situazione al": la visura per soggetto fotografa la situazione a quella data invece che a oggi. Con situazione_al (facoltativo, formato AAAA-MM-GG) la chiedi a una data passata. Senza il parametro la data è quella di oggi, come sempre.

json
POST /api/scanner/visura-tax-data
{
  "tax_code": "RSSMRA80A01H501U",
  "provincia": "LT",
  "tipo_visura": "ANALITICA",
  "storica": true,
  "situazione_al": "2020-06-30"
}
RegolaDettaglio
Solo visura-tax-datasulle altre chiamate il parametro non ha effetto; vale per codice fiscale e partita IVA
FormatoAAAA-MM-GG; una data nel futuro o in un altro formato risponde 422
Con storicaè il caso tipico: chi era intestatario a quella data, anche se oggi non possiede più niente
Stesso prezzocosta come una visura-tax-data normale
Tempicon una data diversa da oggi Sister chiede il codice di sicurezza, risolto in automatico: qualche secondo in più
Data troppo vecchiase a quella data negli atti informatizzati non c'è niente, la risposta è Nessun documento trovato.
Documenti già scaricatila data torna in situazione_al nell'elenco dei documenti del soggetto (null = situazione al giorno della richiesta)

Solo terreni o solo fabbricati: search_catasto_type ​

Come il menu "Catasto" della ricerca per soggetto di Sister, search_catasto_type sceglie quale catasto interrogare. Facoltativo: senza, la ricerca copre terreni e fabbricati, come sempre.

ValoreCosa restituisce
E (default)terreni e fabbricati
Tsolo i terreni
Fsolo i fabbricati
json
POST /api/scanner/tax-data
{
  "tax_code": "RSSMRA80A01H501U",
  "search_catasto_type": "F"
}
RegolaDettaglio
Dove valetax-data, visura-tax-data, immobili-by-name (in entrambe le fasi); codice fiscale e partita IVA
ValoriE, T o F; altro valore → 422
Formatole righe di immobili sono le stesse: cambia solo quali ci sono (catasto vale T o F su ogni riga)
Stesso prezzocosta come la stessa ricerca senza parametro
Documenti già scaricatinell'elenco dei documenti del soggetto la visura riporta catasto (E, T o F)

NOTE

Nelle ricerche per immobile (catasto, intestatari-catasto, visura-catasto, nota-catasto…) lo stesso parametro indica il catasto dell'immobile cercato e accetta solo F o T.

Provincia e comune (facoltativi). In tax-data si possono restringere gli immobili a una provincia e, dentro la provincia, a un comune. Senza, la ricerca resta su tutta Italia come sempre.

json
POST /api/scanner/tax-data
{
  "tax_code": "RSSMRA80A01H501U",
  "provincia": "RM",
  "comune": "Colleferro"
}
RegolaDettaglio
provinciasigla (RM); Trento e Bolzano non sono su Sister → 422
comunenome del comune, con provincia (lo stesso nome esiste in più province); in alternativa codice ISTAT o catastale (H501), e allora la provincia non serve
Confrontoaccenti, apostrofi e maiuscole non contano (Lurago d Erba = LURAGO D'ERBA)
Nessun immobileelenco vuoto, come una ricerca senza risultati
Stesso prezzocosta come la stessa ricerca senza filtri

Elenco o documento? Le ricerche dati tax-data e immobili-by-name restituiscono solo gli immobili attuali del soggetto, come elenco JSON. Un elenco degli immobili posseduti in passato Sister non lo fornisce: il passato si ottiene soltanto come documento, con la visura storica per soggetto qui sopra.

La mappa catastale ha un suo parametro esplicito, tipo_richiesta (ATTUALITA | STORICA).

TIP

Le ispezioni e le note richiedono il comune nel body: serve a risolvere la conservatoria competente.

IMPORTANT

Le chiamate API non restituiscono mai il PDF: restituiscono solo il link result.pdf_path. Il file va scaricato con una GET separata su quel link, passando il tuo Bearer token (endpoint autenticato, non un link pubblico anonimo). Il link è stabile e permanente (non scade): puoi salvarlo e riutilizzarlo come riferimento in una tua procedura.

⚠️ Con curl la risposta è binaria (Content-Type: application/pdf): senza -o curl non stampa nulla sul terminale (sembra un "200 vuoto"), ma il file c'è. Salvalo:

bash
curl -o visura.pdf "<pdf_path>" -H "Authorization: Bearer <TOKEN>"

search-fiscal-code — elenco nominativi (gratuita) ​

Restituisce tutti i nominativi registrati in Catasto con quel nome e cognome nella provincia indicata (o in tutta Italia se la provincia non viene passata), ognuno con codice fiscale, data e luogo di nascita. La chiamata è gratuita: non scala il wallet e non serve una voce di listino.

POST /api/scanner/search-fiscal-code:

json
{ "first_name": "MARIO", "last_name": "ROSSI", "provincia": "MI" }
CampoObbligatorioNote
first_name✅nome (basta una delle parole: VERONICA trova anche MARIA VERONICA)
last_name✅cognome, confrontato in modo esatto (anche composto: DE LUCA)
provincia—sigla di 2 lettere dell'ufficio catastale in cui cercare. Facoltativa: senza, la ricerca è nazionale. TN e BZ non sono ammesse (catasto tavolare, escluse anche dalla nazionale)
birth_date—data di nascita AAAA-MM-GG. Facoltativa: restringe gli omonimi. Restano nell'elenco anche i soggetti che in Catasto non hanno la data di nascita

NOTE

Nazionale o per provincia? Senza provincia la ricerca copre tutta Italia: è la scelta giusta quando non sai dove sono gli immobili. Con cognomi molto diffusi i risultati sono tanti: se la ricerca va in errore per troppi risultati, ripetila indicando la provincia.

Troppi risultati. Oltre 400 soggetti Sister non restituisce l'elenco (capita con nomi molto comuni anche in una sola provincia, es. MARIA COLOMBO a MI). La richiesta finisce subito in ERROR con details: "TOO_MANY_RESULTS", senza tentativi a vuoto: ripetila con birth_date (e con la provincia, se non l'avevi indicata). Con la data di nascita MARIA COLOMBO a MI passa da oltre 400 a pochi soggetti.

Al termine (socket o polling) il result contiene la lista:

json
{
  "immobili": [],
  "anagrafiche": [
    {
      "cognome": "ROSSI", "nome": "MARIO",
      "data_di_nascita": "17/05/1958", "luogo_di_nascita": "MILANO (MI)",
      "sesso": "M", "codice_fiscale": "RSSMRA58E17F205X"
    },
    {
      "cognome": "ROSSI", "nome": "MARIO",
      "data_di_nascita": "02/11/1971", "luogo_di_nascita": "MONZA (MB)",
      "sesso": "M", "codice_fiscale": "RSSMRA71S02F704Y"
    }
  ]
}

Serve a scegliere il soggetto giusto tra gli omonimi confrontando data e luogo di nascita, e a ricavarne il codice fiscale da usare poi con tax-data.

TIP

Errori di battitura in Catasto. La ricerca per nominativo è esatta: se una persona è registrata con il cognome scritto male (es. RISSI invece di ROSSI), cercando ROSSI non esce. Accenti e apostrofi invece vengono normalizzati. La ricerca per codice fiscale (tax-data) confronta solo il codice fiscale, quindi trova il soggetto anche se in Catasto il nome è scritto male. Flusso consigliato: search-fiscal-code per individuare la persona, poi tax-data con il suo codice fiscale.

search-company — società da ragione sociale ​

Il gemello di search-fiscal-code per le persone giuridiche: restituisce le società registrate in Catasto la cui denominazione contiene le parole cercate, ognuna con sede e codice fiscale. Non richiede visure.

WARNING

A pagamento. A differenza di search-fiscal-code, questa ricerca richiede una voce SEARCH-COMPANY nel tuo listino. Senza, la chiamata risponde 403 PARTNER_PRICING_REQUIRED: contatta Dokicasa per attivarla.

POST /api/scanner/search-company:

json
{ "company_name": "ESEMPIO IMMOBILIARE", "provincia": "MI" }
CampoObbligatorioNote
company_name✅ragione sociale, 2-150 caratteri. Accenti e apostrofi vengono normalizzati
provincia—sigla di 2 lettere. Facoltativa: senza, la ricerca è nazionale. No TN/BZ

Al termine (socket o polling) il result contiene la lista:

json
{
  "immobili": [],
  "anagrafiche": [
    { "denominazione": "ESEMPIO IMMOBILIARE SRL", "sede": "MILANO (MI)", "codice_fiscale": "01234567890" },
    { "denominazione": "ESEMPIO IMMOBILIARE S.R.L. IN LIQUIDAZIONE", "sede": "ROMA (RM)", "codice_fiscale": "09876543210" },
    { "denominazione": "ESEMPIO IMMOBILIARE SRL CON SEDE IN MILANO VIA ESEMPIO 1", "sede": "", "codice_fiscale": "" }
  ]
}

Da sapere:

  • La stessa società compare più volte. Negli atti la denominazione è stata scritta in modi diversi (C.I.M.E.P., C I M E P, con o senza la sede): righe diverse con lo stesso codice_fiscale. Raggruppa per codice fiscale prima di mostrarle.
  • sede e codice_fiscale possono essere vuoti sulle registrazioni più vecchie.
  • Una sola parola generica non basta. Con IMMOBILIARE da sola il Catasto non cerca e la richiesta termina in ERROR con "troppo generica: aggiungere un'altra parola della ragione sociale". Anche oltre il limite di risultati del Catasto la richiesta va in ERROR: aggiungi una parola o indica la provincia.
  • Trovato il codice fiscale, usa tax-data per avere gli immobili della società.

immobili-by-name — ricerca in due fasi ​

immobili-by-name cerca gli immobili di una persona a livello nazionale partendo da nome e cognome. Poiché possono esserci più omonimi, funziona in due fasi.

Fase 1 — lista degli omonimi ​

POST /api/scanner/immobili-by-name con solo nome e cognome:

json
{ "first_name": "CHRISTIAN", "last_name": "CANNATA" }

Al termine (via socket/polling, come ogni chiamata async) il result contiene la lista degli omonimi trovati:

json
{
  "needs_selection": true,
  "omonimi": [
    {
      "value": "9800054753#0#CANNATA#CHRISTIAN#CNNCRS91D08E625S#LIVORNO#08/04/1991#LI",
      "nome": "CHRISTIAN", "cognome": "CANNATA",
      "codice_fiscale": "CNNCRS91D08E625S",
      "data_nascita": "08/04/1991", "luogo_nascita": "LIVORNO (LI)", "sesso": "M"
    }
    // … altri omonimi
  ]
}
  • needs_selection: true → devi scegliere una persona dalla lista.
  • Se c'è un solo omonimo la selezione è automatica: ottieni subito il risultato della Fase 2.
  • La Fase 1 è gratuita: si paga una volta sola, alla Fase 2 (o alla Fase 1 quando c'è un solo omonimo e arrivano subito gli immobili). Anche gli omonimi senza codice fiscale hanno il loro value e si possono scegliere.

Fase 2 — immobili della persona scelta ​

Richiama lo stesso endpoint aggiungendo selected_value = il campo value dell'omonimo scelto nella Fase 1 (copialo tale e quale):

json
{
  "first_name": "CHRISTIAN",
  "last_name": "CANNATA",
  "selected_value": "9800054753#0#CANNATA#CHRISTIAN#CNNCRS91D08E625S#LIVORNO#08/04/1991#LI"
}

Al termine il result contiene:

json
{
  "omonimo_selezionato": { "value": "…", "nome": "CHRISTIAN", "cognome": "CANNATA" },
  "immobili": { "…": "…" },
  "has_soppressi": false
}

Gli immobili sono persistiti: la lista completa (con intestatari) si recupera paginata da GET /api/scanner-request/{id}/results.

NOTE

Entrambe le fasi sono normali chiamate async: la POST torna ENQUEUED, poi via socket o polling arrivi a SUCCESS e leggi il result. Nomi molto comuni possono superare il limite di risultati della Fase 1: restringi con dati più specifici.

Come funziona ​

Tutte le chiamate scanner sono asincrone. Il flusso è sempre lo stesso, cambia solo come ricevi l'aggiornamento di stato.

  1. Crei la richiesta — POST /api/scanner/{type} → ricevi uno scanner_request_id e lo stato iniziale ENQUEUED.
  2. Attendi il completamento — scegli uno di questi modi (in ordine di preferenza):
    • WebSocket — push istantaneo, nessun polling.
    • Webhook callback — il nostro server chiama il tuo a fine elaborazione.
    • Polling (fallback) — solo se non puoi usare i due sopra.
  3. Ottieni il risultato — quando lo stato è SUCCESS:
    • PDF (visure, ispezioni, mappe, note): il file è già nella risposta di GET /api/scanner-request/{id}.
    • Ricerche strutturate: chiama GET /api/scanner-request/{id}/results.

Aggiornamenti realtime via WebSocket ​

⭐ Consigliato per app frontend

Approccio raccomandato per web/mobile: nessun polling, aggiornamenti istantanei, barre di avanzamento e notifiche in push.

Ogni richiesta pubblica eventi su un canale privato dedicato:

private-scanner_request.{id}

dove {id} è lo scanner_request_id ritornato dal POST iniziale.

Ambienti — host e chiave del socket ​

Il socket è separato per ambiente: usa host e chiave in base a dove chiami.

AmbienteBase URL APIwsHost (socket)keyauthEndpoint
Sandbox / testhttps://api-dev.dokicasa.itapi-dev.dokicasa.itstaginghttps://api-dev.dokicasa.it/broadcasting/auth
Produzionehttps://api.dokicasa.itsocket.dokicasa.itra9jcihuz0sx7vtw8yuthttps://api.dokicasa.it/broadcasting/auth

WARNING

Non mischiare gli ambienti: se ti connetti al socket di produzione ma fai le chiamate in sandbox (o viceversa), il canale si iscrive ma non ricevi eventi (è un Reverb diverso). L'esempio sotto usa i valori di produzione; per la sandbox sostituisci key, wsHost e authEndpoint con la riga Sandbox.

Esempio completo (JavaScript + Pusher/Reverb) ​

js
import Pusher from 'pusher-js'

const apiToken = 'YOUR_API_TOKEN'

// 1. Client WebSocket (Reverb sul backend, API Pusher sul client)
const pusher = new Pusher('ra9jcihuz0sx7vtw8yut', {
  wsHost: 'socket.dokicasa.it',
  wsPort: 443,
  wssPort: 443,
  forceTLS: true,
  cluster: 'mt1',
  disableStats: true,
  enabledTransports: ['ws', 'wss'],
  authEndpoint: 'https://api.dokicasa.it/broadcasting/auth',
  auth: { headers: { Authorization: 'Bearer ' + apiToken } },
})

// 2. Creo la richiesta scanner
const { scanner_request_id } = await fetch(
  'https://api.dokicasa.it/api/scanner/intestatari-catasto',
  {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + apiToken,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      comune: 'Roma', provincia: 'RM',
      foglio: '123', particella: '45',
      search_catasto_type: 'F',
    }),
  }
).then((r) => r.json())

// 3. Mi iscrivo al canale e ascolto gli stati
const channel = pusher.subscribe(`private-scanner_request.${scanner_request_id}`)
channel.bind('ScannerRequestNotification', ({ message }) => {
  const { status, percentage } = message
  if (status === 'SCANNING') updateProgressBar(percentage)
  if (status === 'SUCCESS') fetchResult(scanner_request_id)
  if (status === 'ERROR') handleError(message)
})

async function fetchResult(id) {
  const data = await fetch(`https://api.dokicasa.it/api/scanner-request/${id}`, {
    headers: { Authorization: 'Bearer ' + apiToken },
  }).then((r) => r.json())
  console.log('Risultato finale:', data)
}

✓ Pro: nessun polling, ricezione istantanea, UX migliore. Funziona ovunque ci sia un client WS (web, mobile, server con client Pusher).

Webhook e firma ​

⭐ Consigliato per integrazioni backend

Passi un URL nella creazione della richiesta: a fine elaborazione Dokicasa ti POSTa il risultato. Invio asincrono con retry — nessuna configurazione nel pannello.

Ci sono due campi che puoi mettere nel body del POST iniziale (anche entrambi insieme):

Campo (nel body POST)Payload che riceviQuando parte
callbackcompatto: { id, type, status, percentage, result, created_at, updated_at, finished_at }a fine scansione
webhookcompleto: { scanner_request: { …oggetto richiesta intero, con paramseresult… } }sull'evento di completamento
json
{
  "type": "catasto",
  "comune": "Roma", "foglio": "123", "particella": "45", "search_catasto_type": "F",
  "callback": "https://tuo-server.com/dokicasa/scanner-callback",
  "webhook":  "https://tuo-server.com/dokicasa/scanner-webhook"
}

Comuni divisi in sezioni catastali

In alcuni comuni — Roma, Napoli, Genova — foglio e particella da soli non bastano a individuare un immobile: serve anche la sezione catastale. In quei casi aggiungi sezione_comune alla richiesta.

json
{
  "type": "catasto",
  "comune": "Napoli", "provincia": "NA",
  "sezione_comune": "SAN FERDINANDO",
  "foglio": "123", "particella": "45"
}

È facoltativo e concorre a identificare l'immobile insieme a foglio, particella, subalterno e comune: due immobili con la stessa particella ma sezione diversa restano distinti. Se lo ometti in un comune che le usa, la ricerca può restituire l'immobile sbagliato o non trovarlo.

Il nome del campo è sezione_comune: un sezione viene ignorato senza errore.

Il result che ricevi ha la forma che dipende dal tipo — vedi Formato dei risultati per tipo.

callback vs webhook — che differenza c'è ​

Stesso meccanismo (un POST firmato al tuo URL a fine elaborazione, con retry): cambia solo la forma del payload.

json
{
  "id": 2615190,
  "type": "VISURA-CATASTO",
  "status": "SUCCESS",
  "percentage": 98,
  "result": { "pdf_path": "https://.../sample-visura-catasto", "is_empty": false },
  "created_at": "2026-07-02T23:22:59+02:00",
  "updated_at": "2026-07-02T23:23:02+02:00",
  "finished_at": "2026-07-02T23:23:02+02:00"
}
json
{
  "scanner_request": {
    "id": 2615190,
    "user_id": 5128,
    "type": "VISURA-CATASTO",
    "params": { "comune": "ROMA", "provincia": "RM", "foglio": "123", "particella": "45", "tipo_visura": "ANALITICA" },
    "result": { "pdf_path": "https://.../sample-visura-catasto", "is_empty": false },
    "status": "SUCCESS"
  }
}
callbackwebhook
Payloadcompatto, campi flatoggetto scanner_request intero
Include i params d'inputNoSì
Header Signature (HMAC)SìSì
Quando partea fine scansionea fine scansione

Quale usare

  • callback → payload leggero: ti basta reagire all'esito (id, status, result). È la scelta più comune.
  • webhook → l'intero record, inclusi i params originali della richiesta: comodo per riconciliare/loggare senza tenere stato lato tuo.

Sono indipendenti: puoi passarne uno, l'altro, o entrambi (in quel caso ricevi due POST distinti). Entrambi firmati con l'header Signature.

Firma — verifica l'autenticità ​

Ogni webhook include l'header Signature: è l'HMAC-SHA256 del body grezzo, firmato con la tua api_token (la stessa che usi come Bearer). Così non devi gestire nessuna chiave nuova — verifichi con la tua api_key.

Signature = hash_hmac('sha256', <body_grezzo>, <la_tua_api_token>)
php
$raw = file_get_contents('php://input');                 // body GREZZO, non ri-serializzato
$expected = hash_hmac('sha256', $raw, $MY_API_TOKEN);
if (!hash_equals($expected, $_SERVER['HTTP_SIGNATURE'] ?? '')) {
    http_response_code(401); exit;                        // firma non valida
}
js
const crypto = require('crypto')
const raw = req.rawBody                                   // body GREZZO (Buffer/stringa)
const expected = crypto.createHmac('sha256', MY_API_TOKEN).update(raw).digest('hex')
if (expected !== req.get('Signature')) return res.sendStatus(401)

Firma sul body **grezzo**

Calcola l'HMAC sui byte esatti ricevuti, prima di parsare/ri-serializzare il JSON: reincodare cambia spazi e ordine delle chiavi, e la firma non combacia più.

Idempotenza

In caso di errori di rete l'invio viene ritentato: identifica la richiesta con l'id nel body e rispondi 2xx anche sui duplicati, così eviti retry inutili.

Formato dei risultati per tipo ​

Il campo result (lo trovi in GET /api/scanner-request/{id}, ed è lo stesso che arriva via socket e webhook) cambia forma a seconda del tipo di richiesta. Qui sotto le forme principali, con esempi reali.

`result` vs `/results`

  • result = payload completo della scansione (immobili, anagrafiche, PDF…), già pronto.
  • GET /api/scanner-request/{id}/results = vista paginata dei soli immobili persistiti (con intestatari), comoda quando sono tanti. Vale per i tipi che listano immobili (catasto, tax-data, list-addresses, intestatari-catasto); restituisce un paginatore Laravel { current_page, data: [...], links, ... }.
json
{
  "has_soppressi": true,
  "immobili": [
    {
      "foglio": "368", "particella": "435", "subalterno": "5",
      "indirizzo": "VIA ESEMPIO n. 11 Interno 1 Piano S1 - T",
      "comune": "ROMA", "provincia": "RM", "numero_civico": "11", "piano": "S1",
      "categoria": "A02", "classe": "02", "consistenza": "6 vani", "rendita": "1239,50",
      "catasto": "F", "codice_belfiore": "H501", "partita": "", "zona_cens": "004",
      "scanner_immobili_id": 82659
    },
    {
      "foglio": "368", "particella": "435", "subalterno": "24",
      "indirizzo": "VIA ESEMPIO n. 19 Piano T",
      "comune": "ROMA", "provincia": "RM", "numero_civico": "19", "piano": "T",
      "categoria": "C01", "classe": "10", "consistenza": "46 m2", "rendita": "3594,44",
      "catasto": "F", "codice_belfiore": "H501", "partita": "", "zona_cens": "004",
      "scanner_immobili_id": 82678
    }
  ]
}
json
{
  "intestatari": [
    {
      "nome_cognome": "ROSSI MARIO", "codice_fiscale": "RSSMRA80A01H501U",
      "quota": "1/2", "titolarita": "Proprieta'",
      "luogo_nascita": "ROMA", "data_nascita": "01/01/1980",
      "comune": "ROMA", "provincia": "RM"
    },
    {
      "nome_cognome": "ESEMPIO IMMOBILIARE SRL", "codice_fiscale": "01234567890",
      "quota": "1/2", "titolarita": "Nuda proprieta'",
      "luogo_nascita": "", "data_nascita": "",
      "comune": "ROMA", "provincia": null
    }
  ]
}
json
{
  "immobili": [],
  "anagrafiche": [
    {
      "cognome": "ROSSI", "nome": "MARIO",
      "data_di_nascita": "17/05/1938", "luogo_di_nascita": "MILANO (MI)",
      "sesso": "M", "codice_fiscale": "RSSMRA38E17F205T"
    }
  ]
}
json
{
  "immobili": [],
  "anagrafiche": [
    { "denominazione": "ESEMPIO IMMOBILIARE SRL", "sede": "MILANO (MI)", "codice_fiscale": "01234567890" }
  ]
}
json
{
  "addresses": {
    "123494##VIA ROMA": " VIA ROMA ",
    "337696##VIA ROMAGNOSI": " VIA ROMAGNOSI ",
    "12428##VIALE ROMAGNA": " VIALE ROMAGNA "
  }
}
json
{
  "pdf_path": "https://.../visure/<request_id>.pdf",
  "request_id": "…",
  "is_nota_negativa": false,
  "is_empty": false
}

Dove trovi lo schema di ogni singola chiamata

I parametri di input e il tipo esatto di ogni interrogazione (foglio/particella, tax_code, tipo_visura, ecc.) sono sullo Swagger, sezione Scanner: api.dokicasa.it/api/documentation#/Scanner. Questa pagina copre invece come ricevere e interpretare i risultati.

Come testare l'integrazione ​

C'è un ambiente sandbox — https://api-dev.dokicasa.it — che rispecchia le API di produzione ma non fa interrogazioni reali: ogni chiamata torna dati di esempio realistici, non consuma credito e non genera documenti reali. Rispetto alla produzione cambia solo il base URL: endpoint, parametri, formato risposte, WebSocket e webhook sono identici.

🔑 Chiave API di test (sandbox) ​

Usa questo token: è un utente con tutte le chiamate abilitate e gratuite, pensato apposta per provare. In sandbox la whitelist degli origin è disattivata, quindi puoi chiamare anche da localhost o da qualsiasi dominio di test.

HfBfYShns3E02no3kkwCRWQugkVNcJCtrRN2BPoI5NxhqsiXaxxnSPpIHY2O

Passalo come header Authorization: Bearer <token> su ogni chiamata.

È già pronto nel Playground

Nel Playground interattivo questo token è precompilato (e copiabile): apri, premi Lancia e vedi socket + polling in tempo reale, senza configurare nulla.

Endpoint da testare ​

API — base https://api-dev.dokicasa.it

CosaEndpoint
Avvia una ricercaPOST /api/scanner/{type} → { scanner_request_id, status }
Stato + risultatoGET /api/scanner-request/{id} · …/{id}/status
Risultati paginatiGET /api/scanner-request/{id}/results
Crea monitoraggioPOST /api/monitoring/from-catasto
Simula webhook (solo sandbox)POST /api/user-monitorings/{id}/simulate-webhook

Socket — sandbox (vedi anche Ambienti del socket)

ParametroValore
wsHostapi-dev.dokicasa.it
keystaging
authEndpointhttps://api-dev.dokicasa.it/broadcasting/auth
canale · eventoprivate-scanner_request.{id} · ScannerRequestNotification

Prova rapida (curl) ​

bash
curl -X POST https://api-dev.dokicasa.it/api/scanner/list-addresses \
  -H "Authorization: Bearer HfBfYShns3E02no3kkwCRWQugkVNcJCtrRN2BPoI5NxhqsiXaxxnSPpIHY2O" \
  -H "Content-Type: application/json" \
  -d '{"indirizzo":"via roma","comune":"Milano","provincia":"MI"}'
# → { "scanner_request_id": 12345, "status": "ENQUEUED" }
# poi:  GET /api/scanner-request/12345/status   fino a  status = SUCCESS

Cosa verificare:

  1. Flusso async — la POST torna ENQUEUED, poi via socket (istantaneo) o polling arrivi a SUCCESS.
  2. Forma delle risposte — mappa i campi del result (vedi Formato risultati per tipo).
  3. Webhook — punta webhook_url a un inspector (webhook.site); in sandbox il monitoraggio invia subito un evento d'esempio alla creazione (o richiamalo con /simulate-webhook).
  4. Firma — valida l'header Signature (vedi sezione Firma).

NOTE

In sandbox i dati sono rappresentativi e fissi (non variano in base ai parametri): mostrano la struttura del result. Quando sei pronto, passa al base URL e al socket di produzione (tabella Ambienti del socket).

Monitoraggio immobili ​

Oltre alle ricerche una-tantum, puoi monitorare un immobile nel tempo: Dokicasa ricontrolla periodicamente i proprietari al catasto e, quando cambiano, ti invia una notifica webhook con la variazione.

Attivare il monitoraggio ​

POST /api/monitoring/from-catasto — parti direttamente dai dati catastali (non serve un id immobile). Se l'immobile non è ancora a sistema viene prima risolto con una ricerca catastale (proprietari inclusi) e salvato, poi il monitoraggio viene attivato.

json
{
  "foglio": "123",
  "particella": "45",
  "subalterno": "7",
  "comune": "Roma",
  "provincia": "RM",
  "webhook_url": "https://tuo-server.com/dokicasa/monitoring-changed"
}
CampoObbligatorioNote
foglio, particella✅la "tripletta" catastale
subalterno—numerico, opzionale
comune✅codice catastale o nome comune
provincia✅sigla (no TN/BZ)
search_catasto_type—F fabbricati (default) / T terreni
sezione_comune—per comuni con sezioni (Napoli/Roma/Genova)
webhook_url—URL a cui inviare le variazioni (vedi sotto)
name, note—etichette libere

Risposta: { immobile_id, user_monitoring_id, created, scanned, last_monitoring, next_monitoring } (last_monitoring = ultima esecuzione, null se mai girato; next_monitoring = prossima esecuzione).

Webhook di variazione proprietari ​

Se valorizzi webhook_url, ad ogni cambio proprietari rilevato dal monitoraggio Dokicasa fa una POST asincrona a quell'URL con il JSON completo della variazione:

json
{
  "event": "immobile.owners_changed",
  "user_monitoring_id": 12345,
  "immobile": {
    "id": 987, "foglio": "123", "particella": "45",
    "subalterno": "7", "comune": "Roma", "provincia": "RM"
  },
  "changes": [
    {
      "date": "2026-07-01",
      "differences": {
        "added": [
          {
            "quota": "1/2", "nome": "Mario", "cognome": "Rossi", "denominazione": null,
            "codice_fiscale": "RSSMRA80A01H501U", "partita_iva": null,
            "dt_nascita": "1980-01-01", "comune_nascita": "Roma", "provincia_nascita": "RM", "sesso": "M"
          }
        ],
        "removed": [
          {
            "quota": "1/2", "nome": "Luigi", "cognome": "Verdi", "denominazione": null,
            "codice_fiscale": "VRDLGU75M15F205X", "partita_iva": null,
            "dt_nascita": "1975-08-15", "comune_nascita": "Milano", "provincia_nascita": "MI", "sesso": "M"
          }
        ],
        "modified": [
          {
            "id": "CF:BNCLNA82H41H501Y",
            "before": {
              "quota": "1/3", "nome": "Elena", "cognome": "Bianchi", "denominazione": null,
              "codice_fiscale": "BNCLNA82H41H501Y", "partita_iva": null,
              "dt_nascita": "1982-06-01", "comune_nascita": "Roma", "provincia_nascita": "RM", "sesso": "F"
            },
            "after": {
              "quota": "1/2", "nome": "Elena", "cognome": "Bianchi", "denominazione": null,
              "codice_fiscale": "BNCLNA82H41H501Y", "partita_iva": null,
              "dt_nascita": "1982-06-01", "comune_nascita": "Roma", "provincia_nascita": "RM", "sesso": "F"
            }
          }
        ]
      }
    }
  ]
}

Ogni elemento di added / removed (e i blocchi before / after di modified) riporta i dati anagrafici base del soggetto: quota, nome, cognome, denominazione (persona fisica → nome+cognome, azienda → denominazione), codice_fiscale, partita_iva, dt_nascita, comune_nascita, provincia_nascita, sesso. I campi non disponibili sono null. Il codice_fiscale è anche incapsulato nell'id di modified (prefisso CF:, oppure PI: per la P.IVA o NM: se manca l'identificativo fiscale).

NOTE

Il webhook parte in automatico dal cron di monitoraggio, con retry in caso di errori di rete. Rispondi 2xx per confermare la ricezione.

TIP

Il webhook_url è opzionale in fase di creazione: se non lo passi non viene inviato nulla. Puoi aggiungerlo o cambiarlo in un secondo momento (vedi sotto).

Provare il webhook in **sandbox** (staging)

Sull'ambiente di test (https://api-dev.dokicasa.it) non devi aspettare un cambio proprietari reale:

  • Alla creazione, se passi un webhook_url, inviamo subito un evento immobile.owners_changed d'esempio a quell'URL (la risposta della create include simulated_webhook_sent: true).
  • Puoi re-inviarlo quando vuoi con POST /api/user-monitorings/{id}/simulate-webhook.

Il payload ha lo stesso identico formato dell'evento reale: punta webhook_url a un inspector (webhook.site) per vederlo arrivare. In produzione questi automatismi sono disattivati — il webhook parte solo su un cambio proprietari reale.

Elenco dei monitoraggi ​

GET /api/users/me/monitorings — ritorna i tuoi monitoraggi (paginati). Filtri via query string:

ParametroValoriNote
filter.typeIMMOBILE (default) / SOGGETTOtipo di monitoraggio
filter.statusACTIVE, EXPIRED, CANCELLEDanche multipli, separati da virgola
filter.sourceAPI / CENSIMENTOorigine del monitoraggio
filter.change_owner1solo quelli con variazioni proprietari
filter.texttesto liberocerca in note + indirizzo/dati catastali
sort_ends_atASC / DESCordina per scadenza (default: id DESC)
pagenumeropagina (risposta paginata)

Se ti servono solo gli id: GET /api/users/me/monitorings-ids.

NOTE

È un endpoint di sola lettura: funziona sempre col tuo Bearer token, anche senza whitelist origin configurata. Il default filter.type=IMMOBILE ritorna solo i monitoraggi immobile — per i soggetti passa filter.type=SOGGETTO.

Gestione del monitoraggio ​

MetodoPathCosa fa
GET/api/user-monitorings/{id}Dettaglio del monitoraggio: dati del monitoring, immobile collegato (model), proprietari attuali (model.soggetti), eventi di variazione (events) e storico letture proprietari (proprietari_history). Vedi esempio sotto.
PATCH/api/user-monitorings/{id}/webhookImposta o rimuove (passando null) la webhook_url. Body: { "webhook_url": "https://…" }.
PATCH/api/user-monitorings/{id}Cambia lo status: ACTIVE (attivo), DISACTIVE (in pausa), CANCELED (annullato).
POST/api/user-monitorings/{id}/renewRinnova il monitoraggio.
POST/api/user-monitorings/{id}/simulate-webhookSolo sandbox: invia subito al webhook_url un evento immobile.owners_changed di esempio, per provare la ricezione senza attendere un cambio reale. In produzione risponde 404.
DELETE/api/user-monitorings/{id}Elimina il monitoraggio.

Esempio risposta — dettaglio monitoraggio ​

json
{
  "id": 6004,
  "user_id": 5101,
  "status": "ACTIVE",
  "ends_at": "2026-07-10 11:08:01",
  "model_type": "App\\Models\\ScannerImmobile",
  "model_id": 2013602,
  "model": {
    "id": 2013602,
    "comune": "Nardo'", "provincia": "LE",
    "foglio": "129", "particella": "1474", "subalterno": "1",
    "categoria": "A03",
    "indirizzo_completo": "VIA CAVALIERI TEUTONICI n. 6 Piano T",
    "soggetti": [
      {
        "denominazione": "COLELLA ANNA MARIA",
        "codice_fiscale": "CLLNMR35P44F842Z",
        "pivot": { "quota": "1/2", "titolarita": "Proprieta'" }
      }
    ]
  },
  "events": [
    {
      "date": "2025-12-02",
      "differences": {
        "added":    [ { "quota": "1/2", "denominazione": "BERNES ANGELO" } ],
        "removed":  [ { "quota": "",    "denominazione": "AMATEIS DAVIDE" } ],
        "modified": []
      }
    }
  ],
  "proprietari_history": [
    {
      "date": "2025-11-02",
      "read_at": "2025-11-02 00:00:00",
      "scanner_request_id": 2071196,
      "proprietari": [
        { "denominazione": "AMATEIS DAVIDE",     "codice_fiscale": "MTSDVD82H03D208H", "quota": "",    "titolarita": "" },
        { "denominazione": "COLELLA ANNA MARIA", "codice_fiscale": "CLLNMR35P44F842Z", "quota": "1/2", "titolarita": "Proprieta'" }
      ]
    },
    {
      "date": "2025-12-02",
      "read_at": "2025-12-02 00:00:00",
      "scanner_request_id": 2241215,
      "proprietari": [
        { "denominazione": "BERNES ANGELO",      "quota": "1/2", "titolarita": "Proprieta'" },
        { "denominazione": "COLELLA ANNA MARIA", "quota": "1/2", "titolarita": "Proprieta'" }
      ]
    }
  ]
}
CampoCosa contiene
modelDettaglio dell'immobile monitorato (comune, foglio/particella/sub, categoria, indirizzo…)
model.soggettiProprietari attuali (con quota e titolarità nel pivot)
eventsVariazioni proprietari: diff per data (added / removed / modified)
proprietari_historySnapshot dei proprietari a ogni lettura (una entry per scanner_request, con date/read_at): la storia grezza, non deduplicata, di com'erano i proprietari ad ogni controllo

Caso d'uso: ricerca da indirizzo ​

Hai un indirizzo e vuoi gli immobili al catasto associati. Flusso in 2 fasi: risoluzione indirizzo → immobili.

  1. list-addresses (risoluzione indirizzo) — POST /api/scanner/list-addresses. Attendi via socket/webhook/polling. La lista degli indirizzi normalizzati è già nel campo results della risposta di GET /api/scanner-request/{id} — non serve chiamare /results.
  2. Immobili da indirizzo — usa un indirizzo del passo 1 come address_value nella chiamata di scanner immobili (endpoint preciso su Swagger, sezione Scanner).
  3. Recupero risultati — quando arriva SUCCESS, chiama GET /api/scanner-request/{id}/results per la lista immobili.

Caso d'uso: visure, ispezioni, note, mappe (PDF) ​

Tutte le richieste che producono un PDF (visura catastale, ispezione, nota, mappa) seguono lo stesso pattern.

  1. Crei la richiesta — POST /api/scanner/{type} (il tipo dipende dal documento, vedi Swagger).
  2. Attendi il completamento — via WebSocket o webhook: lo stato passa per ENQUEUED → SCANNING fino a SUCCESS o ERROR.
  3. Retry automatico su errori transient — se durante l'elaborazione c'è un errore temporaneo (linea con i servizi esterni), il sistema riprova da solo ogni ~5 minuti. Non devi reinviare nulla. Il credito viene scalato solo al primo esito positivo.
  4. Ottieni il PDF — il file è restituito direttamente nella risposta di GET /api/scanner-request/{id} (non serve /results). In più il PDF viene inviato automaticamente via email all'indirizzo associato al token, quindi l'utente finale lo riceve comunque anche senza integrare il download lato client.

Stato del servizio (Sister) ​

Le interrogazioni catastali passano da Sister, il portale dell'Agenzia delle Entrate, che ogni tanto rallenta, si ferma o va in manutenzione programmata (di solito annunciata con un banner tipo "il servizio è indisponibile dalle 17.00 alle 18:00 circa"). Noi lo controlliamo ogni 10 minuti in base ai tempi reali delle scansioni e leggiamo gli avvisi di manutenzione pubblicati da Sister; puoi usare l'esito per avvisare i tuoi utenti o, se il servizio è fermo, passare le richieste al manuale invece di accodarle.

http
GET /api/scanner/health
Authorization: Bearer <token>

Campi della risposta ​

CampoTipoDescrizione
statusstringok | slow | down | unknown (vedi tabella sotto).
accepting_requestsbooleanfalse solo con status = down. È il campo su cui decidere.
messagestringMessaggio in italiano pronto per l'utente. Può cambiare: non usarlo per decidere.
sincestring (ISO 8601) | nullDa quando dura il problema. Durante una manutenzione è l'orario di inizio. null con ok.
sister_checked_atstring (ISO 8601) | nullUltimo controllo di Sister.
maintenanceobject | nullManutenzione programmata in corso o annunciata (la prossima). null se non ce ne sono.
maintenance.in_progressbooleantrue se adesso siamo dentro la finestra starts_at → ends_at.
maintenance.starts_atstring (ISO 8601)Inizio della manutenzione.
maintenance.ends_atstring (ISO 8601)Fine prevista: a quest'ora il Market Scanner riparte da solo.
maintenance.scanner_blockedbooleantrue se la manutenzione è in corso e il Market Scanner è sospeso.
maintenance.messagestringMessaggio in italiano pronto per l'utente ("sarà in manutenzione dalle 17:00 alle 18:00" / "è in manutenzione fino alle 18:00"). Se la finestra finisce il giorno dopo l'orario riporta anche la data ("fino alle 02:00 del 18/09").
maintenance.sister_messagestring | nullTesto originale del banner di Sister, così com'è.
queue.enqueuedintegerScansioni in attesa (ultime 2 ore).
queue.scanningintegerScansioni in lavorazione.
queue.max_wait_secondsintegerAttesa della richiesta in coda da più tempo.
recommended_poll_secondsintegerOgni quanto richiederlo (300).
generated_atstring (ISO 8601)Quando è stata calcolata la risposta.

Gli orari sono in ora italiana (+02:00 / +01:00).

statusSignificatoCosa conviene fare
okServizio operativo.Niente. Se maintenance non è null, avvisare della manutenzione in arrivo.
slowSister risponde ma più lento del solito: le scansioni arrivano, in più tempo.Avvisare che i tempi sono più lunghi.
downSister non risponde, è in manutenzione programmata, oppure il servizio è sospeso da noi. accepting_requests = false.Bloccare le nuove richieste e passarle al manuale. Con maintenance.in_progress = true puoi dire all'utente a che ora riparte (ends_at).
unknownStato non disponibile (controllo non aggiornato, o troppe poche scansioni per giudicare). Non è un guasto.Trattarlo come ok.

Stato per tipo di scansione: types ​

Sister a volte ferma solo alcuni servizi (per esempio le ispezioni ipotecarie) mentre il resto funziona, e l'avviso non sempre arriva. Per questo types dà lo stato di ogni tipo di scansione, ricavato dagli esiti reali delle richieste dell'ultima ora. La chiave è il type che usi in POST /api/scanner/{type}.

json
"types": {
  "intestatari-catasto": { "status": "ok", "reason": null, "since": null, "requests": { "total": 57, "with_errors": 0 }, "last_success_at": "2026-09-26T10:41:12+02:00" },
  "ispezione-catasto":   { "status": "down", "reason": "errors", "since": "2026-09-26T10:12:03+02:00", "requests": { "total": 4, "with_errors": 3 }, "last_success_at": "2026-09-26T10:05:40+02:00" },
  "mappa-catasto":       { "status": "unknown", "reason": null, "since": null, "requests": { "total": 0, "with_errors": 0 }, "last_success_at": "2026-09-25T17:32:32+02:00" }
}
types.<type>.statusSignificato
okLe richieste di questo tipo vanno a buon fine (anche quando serve un secondo tentativo, normale per le visure col codice di sicurezza).
degradedAlmeno il 30% delle ricerche dell'ultima ora ha dato errore anche dopo i tentativi, su più server.
downDopo l'ultima richiesta andata bene sono fallite almeno 3 ricerche diverse su almeno 2 server, oppure c'è una manutenzione Sister in corso (reason: "maintenance"). since = primo errore.
unknownNessuna richiesta di questo tipo nell'ultima ora: non sappiamo. last_success_at dice quando ha funzionato l'ultima volta (ultime 24 ore). Non è un guasto.
  • È solo informativo: le richieste non vengono bloccate. Decidi tu se avvisare l'utente o passare quel tipo al manuale.
  • Gli errori del dato cercato (comune inesistente, particella che non esiste, troppi risultati) non contano: dicono che la ricerca era sbagliata, non che Sister è fermo.
  • La stessa ricerca ripetuta più volte conta una volta sola, e un errore su un solo server non basta: gli errori di un singolo server li gestiamo noi, spostando la richiesta su un altro.
  • requests.total e requests.with_errors contano le ricerche distinte dell'ultima ora.

Esempi di risposta ​

json
{
  "status": "ok",
  "accepting_requests": true,
  "message": "Servizio operativo.",
  "since": null,
  "sister_checked_at": "2026-09-17T10:40:07+02:00",
  "maintenance": null,
  "queue": { "enqueued": 3, "scanning": 2, "max_wait_seconds": 14 },
  "recommended_poll_seconds": 300,
  "generated_at": "2026-09-17T10:41:00+02:00"
}
json
{
  "status": "ok",
  "accepting_requests": true,
  "message": "Servizio operativo.",
  "since": null,
  "sister_checked_at": "2026-09-17T14:30:05+02:00",
  "maintenance": {
    "in_progress": false,
    "starts_at": "2026-09-17T17:00:00+02:00",
    "ends_at": "2026-09-17T18:00:00+02:00",
    "scanner_blocked": false,
    "message": "Sister (Agenzia delle Entrate) sara' in manutenzione programmata dalle 17:00 alle 18:00.",
    "sister_message": "A causa di un intervento programmato, il servizio e' indisponibile dalle 17.00 alle 18:00 circa"
  },
  "queue": { "enqueued": 5, "scanning": 3, "max_wait_seconds": 22 },
  "recommended_poll_seconds": 300,
  "generated_at": "2026-09-17T14:31:00+02:00"
}
json
{
  "status": "down",
  "accepting_requests": false,
  "message": "Sister (Agenzia delle Entrate) e' in manutenzione programmata fino alle 18:00: le scansioni riprenderanno al termine.",
  "since": "2026-09-17T17:00:00+02:00",
  "sister_checked_at": "2026-09-17T17:12:00+02:00",
  "maintenance": {
    "in_progress": true,
    "starts_at": "2026-09-17T17:00:00+02:00",
    "ends_at": "2026-09-17T18:00:00+02:00",
    "scanner_blocked": true,
    "message": "Sister (Agenzia delle Entrate) e' in manutenzione programmata fino alle 18:00: le scansioni riprenderanno al termine.",
    "sister_message": "A causa di un intervento programmato, il servizio e' indisponibile dalle 17.00 alle 18:00 circa"
  },
  "queue": { "enqueued": 0, "scanning": 0, "max_wait_seconds": 0 },
  "recommended_poll_seconds": 300,
  "generated_at": "2026-09-17T17:12:00+02:00"
}
json
{
  "status": "slow",
  "accepting_requests": true,
  "message": "Sister (Agenzia delle Entrate) e' piu' lento del solito: le scansioni vanno avanti ma impiegano piu' tempo.",
  "since": "2026-09-17T12:20:00+02:00",
  "sister_checked_at": "2026-09-17T12:40:07+02:00",
  "maintenance": null,
  "queue": { "enqueued": 12, "scanning": 4, "max_wait_seconds": 95 },
  "recommended_poll_seconds": 300,
  "generated_at": "2026-09-17T12:41:00+02:00"
}
json
{
  "status": "down",
  "accepting_requests": false,
  "message": "Sister (Agenzia delle Entrate) non risponde: le scansioni restano in attesa.",
  "since": "2026-09-17T09:50:00+02:00",
  "sister_checked_at": "2026-09-17T10:10:04+02:00",
  "maintenance": null,
  "queue": { "enqueued": 27, "scanning": 6, "max_wait_seconds": 1260 },
  "recommended_poll_seconds": 300,
  "generated_at": "2026-09-17T10:11:00+02:00"
}

Da sapere ​

  • Decidi su accepting_requests (o status = down), mai sul testo di message.
  • Una manutenzione annunciata non blocca niente: status resta quello del momento e le scansioni vanno avanti fino a starts_at. Usa maintenance per avvisare l'utente in anticipo.
  • Durante la manutenzione status è down e il Market Scanner è sospeso: POST /api/scanner/{type} risponde 400 con SISTER_FORCE_BLOCK e la stessa manutenzione nel campo maintenance (vedi sotto). Allo scadere di ends_at lo scanner riparte da solo, senza bisogno di altro.
  • Una manutenzione viene rilevata quando le nostre scansioni incontrano l'avviso di Sister: può comparire con poco anticipo, oppure direttamente già in corso.
  • Se ends_at è passato, maintenance torna null (o mostra la manutenzione successiva, se ce n'è un'altra annunciata).
  • Lo stato slow/down misurato sui tempi cambia solo se il problema dura almeno due controlli di fila (10-20 minuti): una scansione lenta da sola non lo accende, e torna ok dopo due controlli normali. La manutenzione invece si applica subito.
  • queue riguarda le scansioni delle ultime 2 ore: max_wait_seconds è l'attesa della richiesta in coda da più tempo.
  • La risposta resta in cache 60 secondi: interrogalo ogni recommended_poll_seconds (5 minuti), non più spesso, dal tuo backend. Se la chiamata fallisce (rete, 5xx) tieni l'ultimo stato noto.
  • Con down dovuto a Sister che non risponde (senza manutenzione) le richieste inviate vengono comunque accettate e restano in coda finché Sister non torna: il blocco è una scelta tua.
  • In sandbox lo stato di Sister è normalmente unknown e maintenance è null.

Richieste inviate durante una manutenzione ​

Creazione rifiutata — se il Market Scanner è sospeso, POST /api/scanner/{type} non crea la richiesta e non scala crediti:

json
// HTTP 400
{
  "error": "SISTER_FORCE_BLOCK",
  "maintenance": {
    "in_progress": true,
    "starts_at": "2026-09-17T17:00:00+02:00",
    "ends_at": "2026-09-17T18:00:00+02:00",
    "scanner_blocked": true,
    "message": "Sister (Agenzia delle Entrate) e' in manutenzione programmata fino alle 18:00: le scansioni riprenderanno al termine.",
    "sister_message": "A causa di un intervento programmato, il servizio e' indisponibile dalle 17.00 alle 18:00 circa"
  }
}

maintenance è null se la sospensione non dipende da una manutenzione programmata (blocco manuale nostro): in quel caso non c'è un orario di ripartenza, controlla GET /api/scanner/health.

Richiesta già partita — se la manutenzione inizia mentre una richiesta è in lavorazione, la richiesta passa a ERROR e il suo result indica il motivo con code:

json
{
  "id": 12345,
  "status": "ERROR",
  "result": {
    "error": "Sister (Agenzia delle Entrate) e' in manutenzione programmata fino alle 18:00: le scansioni riprenderanno al termine.",
    "code": "SISTER_MAINTENANCE",
    "maintenance": {
      "in_progress": true,
      "starts_at": "2026-09-17T17:00:00+02:00",
      "ends_at": "2026-09-17T18:00:00+02:00",
      "scanner_blocked": true,
      "message": "Sister (Agenzia delle Entrate) e' in manutenzione programmata fino alle 18:00: le scansioni riprenderanno al termine.",
      "sister_message": "A causa di un intervento programmato, il servizio e' indisponibile dalle 17.00 alle 18:00 circa"
    }
  }
}
result.codeSignificato
SISTER_MAINTENANCESister in manutenzione programmata; maintenance indica fino a quando (può essere null se l'avviso non riportava gli orari).
SISTER_UNAVAILABLESister ha mostrato un avviso di indisponibilità senza orario; maintenance è null.

Le richieste che producono un PDF (visure, ispezioni, note, mappe) vengono rilanciate da noi dopo la fine della manutenzione, come per gli altri errori temporanei (vedi Caso d'uso: visure, ispezioni, note, mappe). Le altre vanno reinviate dopo ends_at.

Polling — solo come ultima opzione ​

Sconsigliato

Hai due meccanismi push (WebSocket e webhook) che eliminano la necessità di polling. Valutali prima: il polling aumenta latenza e carico server senza vantaggi reali.

Se proprio devi pollare, interroga periodicamente:

http
GET /api/scanner-request/{id}

Linee guida pratiche:

  • Intervallo minimo consigliato: 3 secondi tra una chiamata e l'altra.
  • Backoff esponenziale dopo qualche tentativo (es. 3s → 6s → 12s).
  • Limite di tentativi sensato (es. 5 minuti totali), poi timeout lato tuo.
  • Interrompi appena lo stato è SUCCESS o ERROR.

Risposta tipica:

json
{ "id": 12345, "status": "SCANNING", "percentage": 42 }

Note operative ​

  • Conserva sempre l'id (scanner_request_id) restituito alla creazione: serve per ogni operazione successiva.
  • Gestisci esplicitamente lo stato ERROR: leggi il messaggio per capire la causa.
  • Non condividere mai il token API: trattalo come una password.
  • In produzione, preferisci webhook o socket al polling.