Verifica le schede di un'attività con un listings scan

Controlla un'attività su mappe, motori di ricerca, directory e assistenti AI, nome, indirizzo e telefono per directory; risultati via webhook o polling.

Un listings scan verifica come un'attività è presente nella rete di directory di listingsAPI e indica, per ogni directory, se viene trovata e se nome, indirizzo e numero di telefono corrispondono ai dati forniti. L'attività non deve necessariamente essere una sede nel tuo account. Questa guida crea una scansione, attende che termini e legge i risultati.

Usalo per:

  • verificare un potenziale cliente prima di acquisirlo, in modo che il primo confronto parta dalle schede mancanti o errate;
  • controllare la presenza di un cliente e la coerenza dei dati NAP (nome, indirizzo, numero di telefono) prima e dopo aver corretto le sue schede;
  • consentire a un agente AI di rispondere alla domanda "come è presente questa attività nelle directory?" senza aggiungere l'attività all'account.

1. Crea la scansione

POST /listings/scans accetta i dati dell'attività come un unico oggetto input e restituisce immediatamente la scansione nello stato QUEUED. Le directory vengono controllate in background.

CampoObbligatorioNote
nameSìFino a 150 caratteri.
streetSìFino a 200 caratteri.
citySìFino a 100 caratteri.
stateIsoSì, tranne per GBCodice dello stato o della provincia, fino a 50 caratteri.
postalCodeSì, tranne per NG e AEFino a 20 caratteri.
countryIsoSìCodice ISO 3166-1 di 2 lettere, ad esempio US.
phoneSìDa 6 a 15 cifre. I formati locali vengono interpretati in base a countryIso, ed è accettato anche un + iniziale.
Create a scan
curl -X POST https://listingsapi.com/api/v4/listings/scans \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d @- <<'JSON'{"input": {  "name": "Joe's Pizza",  "street": "7 Carmine Street",  "city": "New York",  "stateIso": "NY",  "postalCode": "10014",  "countryIso": "US",  "phone": "2123661182"}}JSON

La risposta restituisce la scansione, con phone normalizzato nel formato internazionale. results è vuoto finché non vengono controllate le directory, quindi conserva id.

JSON
{  "data": {    "createListingScan": {      "reused": false,      "scan": {        "id": "TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz",        "status": "QUEUED",        "source": "DIRECTORY_SEARCH",        "locationId": null,        "resultsAsOf": null,        "createdAt": "2026-11-05T07:01:08Z",        "startedAt": null,        "completedAt": null,        "business": {          "name": "Joe's Pizza",          "street": "7 Carmine Street",          "city": "New York",          "stateIso": "NY",          "postalCode": "10014",          "countryIso": "US",          "phone": "+12123661182"        },        "summary": {          "total": 0,          "found": 0,          "accurate": 0,          "inaccurate": 0,          "notFound": 0,          "pending": 0,          "unavailable": 0        },        "failure": null,        "results": []      },      "errors": []    }  }}

La risposta include anche un oggetto quota, descritto in Costi, limiti ed errori.

2 aspetti da controllare prima di proseguire:

  • reused. Quando invii una richiesta per un'attività che hai sottoposto a scansione negli ultimi 7 giorni, listingsAPI restituisce quella scansione invece di avviarne una nuova, con reused: true e il suo stato attuale, di solito COMPLETED. La tua quota non viene consumata e non viene inviato un nuovo webhook per quella scansione.
  • errors. Anche una richiesta rifiutata riceve una risposta con stato HTTP 200. Il rifiuto è indicato in data.createListingScan.errors e scan è null. Un numero di telefono troppo corto produce questo risultato:
JSON
{  "data": {    "createListingScan": {      "reused": false,      "scan": null,      "errors": [        {          "code": "SY83003",          "message": "SY83003: Invalid value for phone",          "contextInfo": [            {              "key": "field",              "value": "phone"            }          ]        }      ]    }  }}

contextInfo indica il nome del campo che non ha superato la convalida. Gli altri codici che puoi ricevere sono riportati nella tabella degli errori.

2. Attendi i risultati

Una scansione passa da QUEUED a IN_PROGRESS, poi a COMPLETED o FAILED. La maggior parte termina in un intervallo compreso tra 1 e 7 minuti. Se una directory non ha risposto dopo circa 15 minuti, il suo risultato viene impostato su UNAVAILABLE e la scansione termina con gli altri risultati. Usa un webhook se puoi e ricorri al polling solo come alternativa.

Quando la scansione raggiunge uno stato finale, listingsAPI invia un evento listing_scan.completed o listing_scan.failed all'URL del tuo webhook. Il suo data.scan è lo stesso oggetto restituito da GET, risultati inclusi, quindi non devi recuperare altri dati in seguito.

JSON
{  "event": "listing_scan.completed",  "timestamp": "2026-11-05T07:01:52Z",  "account_id": 12345,  "data": {    "scan": {      "id": "TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz",      "status": "COMPLETED",      "summary": { "total": 62, "found": 21, "accurate": 18, "inaccurate": 2, "notFound": 41, "pending": 0, "unavailable": 0 },      "results": [ "..." ]    }  }}

Il payload è abbreviato qui; l'esempio completo si trova nella documentazione di riferimento degli eventi. Gli eventi vengono firmati e consegnati come tutti gli altri eventi, con 1 tentativo e un tempo massimo di 5 secondi, quindi verifica la firma, memorizza il corpo originale e conferma la ricezione prima di fare qualsiasi altra cosa. Il ricevitore descritto in Reagire agli eventi con i webhook esegue già queste operazioni; l'unica modifica consiste nell'aggiungere un ramo condizionale basato su event.

One more branch in your receiver
# After the signature check and the endpoint.verification branch:if body.get("event") in ("listing_scan.completed", "listing_scan.failed"):  inbox.put(raw)  # commit before the 200; read data.scan from the queue  return {"received": True}, 200

Un evento viene inviato solo agli account con webhook abilitati e un URL verificato. Se il tuo account non soddisfa queste condizioni, o se un evento non arriva, esegui il polling della scansione; in entrambi i casi, la scansione viene conservata per 90 giorni. I dettagli sulla firma si trovano in Firme e verifica.

2b. Usa il polling come alternativa

GET /listings/scans/{scanId} restituisce la scansione con tutti i risultati disponibili fino a quel momento. Durante la scansione, results si popola man mano che le directory rispondono e summary.pending conta quelle ancora in attesa di risposta. Esegui il polling ogni 15 secondi, non più spesso; ogni richiesta viene conteggiata ai fini del tuo limite di frequenza.

Poll until the scan finishes
SCAN_ID="TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz" while true; doscan=$(curl -s "https://listingsapi.com/api/v4/listings/scans/$SCAN_ID" \  -H "Authorization: API $LISTINGSAPI_KEY")status=$(echo "$scan" | jq -r '.data.listingScan.status')echo "status: $status"case "$status" in  COMPLETED|FAILED)    echo "$scan" | jq '.data.listingScan | {status, summary, failure}'    break ;;  null)    echo "$scan"    break ;;esacsleep 15done
statusSignificato
QUEUEDAccettata e in attesa di avvio.
IN_PROGRESSÈ in corso la verifica delle directory. results è parziale.
COMPLETEDOgni directory ha una risposta. results e summary sono definitivi.
FAILEDNon è stato possibile avviare la scansione, ad esempio perché il servizio era temporaneamente non disponibile (SY83010). failure contiene code e message, mentre results è vuoto. Una scansione non riuscita non consuma la tua quota, quindi puoi crearla di nuovo.

Una scansione i cui risultati provengono da una scansione recente o da una delle tue sedi è nello stato COMPLETED fin dalla creazione, quindi basta una sola richiesta GET.

3. Leggi i risultati

Una scansione completata contiene un summary e una voce in results per ogni directory verificata, fino a circa 60 a seconda del paese. business restituisce i dati che hai inviato, con phone normalizzato nel formato internazionale.

JSON
{  "total": 62,  "found": 21,  "accurate": 18,  "inaccurate": 2,  "notFound": 41,  "pending": 0,  "unavailable": 0}

total è il numero di directory. found indica quante contengono una scheda dell'attività e si suddivide in accurate, inaccurate ed eventuali schede che confermano solo la presenza, descritte di seguito. notFound indica quante non contengono una scheda dell'attività. unavailable conta le directory che non è stato possibile verificare questa volta, mentre pending è diverso da zero solo durante la scansione.

Ogni risultato contiene un status per la directory:

status del risultatoSignificato
FOUNDLa directory contiene una scheda dell'attività. listing contiene i dati che mostra.
NOT_FOUNDLa directory è stata verificata e l'attività non è presente.
UNAVAILABLEQuesta volta non è stato possibile verificare la directory. Questo non equivale all'assenza di una scheda, quindi non segnalarla come mancante. Verifica di nuovo la directory in un secondo momento.
PENDINGNon ancora verificata. Compare solo durante la scansione.

Per un risultato FOUND, accurate indica se la scheda corrisponde ai dati che hai inviato:

  • true significa che nome, indirizzo e numero di telefono corrispondono tutti.
  • false significa che almeno uno non corrisponde. Un campo omesso dalla scheda viene considerato non corrispondente, quindi una scheda senza numero di telefono è false.
  • null significa che l'attività è stata trovata, ma non è stata eseguita una verifica campo per campo.

accuracy spiega l'esito con un status e un message per ciascuno dei campi name, address e phone. Il valore di status è ACCURATE, INACCURATE o MISSING, mentre message è null quando il campo è corretto. Ecco una pagina Facebook in cui nome e indirizzo corrispondono, ma manca il numero di telefono:

JSON
{  "directory": {    "id": 184,    "name": "Facebook",    "url": "facebook.com"  },  "status": "FOUND",  "accurate": false,  "accuracySource": "DIRECTORY_MATCH",  "accuracy": {    "name": {      "status": "ACCURATE",      "message": null    },    "address": {      "status": "ACCURATE",      "message": null    },    "phone": {      "status": "MISSING",      "message": "No phone number on the listing"    }  },  "listing": {    "name": "Joe's Pizza",    "street": "7 Carmine St",    "city": "New York",    "state": "NY",    "postalCode": "10014",    "country": "US",    "fullAddress": "7 Carmine St, New York, NY 10014",    "phones": [],    "url": "https://www.facebook.com/177353602413952"  },  "syncStatus": null}

Alcune directory confermano solo che l'attività esiste e non mostrano dettagli sufficienti per un confronto. Per queste directory viene restituito FOUND con accurate: null e i risultati vengono conteggiati solo in found, non in accurate o inaccurate. Considera queste schede presenti, ma non verificate:

JSON
{  "directory": {    "id": 374,    "name": "Map Quest",    "url": "www.mapquest.com"  },  "status": "FOUND",  "accurate": null,  "accuracySource": "DIRECTORY_MATCH",  "accuracy": {    "name": {      "status": "ACCURATE",      "message": null    },    "address": {      "status": "MISSING",      "message": "Not shown"    },    "phone": {      "status": "MISSING",      "message": "Not shown"    }  },  "listing": {    "name": "Joe's Pizza",    "street": "7 Carmine St",    "city": "New York",    "state": "NY",    "postalCode": "10014",    "country": null,    "fullAddress": "7 Carmine St, New York, NY 10014, US",    "phones": [],    "url": null  },  "syncStatus": null}

Un risultato NOT_FOUND non contiene né listing né accuracy:

JSON
{  "directory": {    "id": 352,    "name": "YellowPages",    "url": "www.yellowpages.com"  },  "status": "NOT_FOUND",  "accurate": null,  "accuracySource": "DIRECTORY_MATCH",  "accuracy": null,  "listing": null,  "syncStatus": null}

Diverse directory non dispongono di dati propri. Uber, Waze e Lyft mostrano i dati di Google Maps, mentre Copilot mostra quelli di Bing. I loro risultati rispecchiano la directory di origine, quindi di solito ripetono quanto restituito da Google Maps o Bing e non contengono un url della scheda. Conteggiali come voci separate in summary, ma non considerarli conferme indipendenti:

JSON
{  "directory": {    "id": 350,    "name": "Uber",    "url": "uber.com"  },  "status": "FOUND",  "accurate": true,  "accuracySource": "DIRECTORY_MATCH",  "accuracy": {    "name": {      "status": "ACCURATE",      "message": null    },    "address": {      "status": "ACCURATE",      "message": null    },    "phone": {      "status": "ACCURATE",      "message": null    }  },  "listing": {    "name": "Joe's Pizza",    "street": "7 Carmine Street",    "city": "New York",    "state": "NY",    "postalCode": "10014",    "country": "US",    "fullAddress": "7 Carmine St, New York, NY 10014, USA",    "phones": [      "+1 212-366-1182"    ],    "url": null  },  "syncStatus": null}

Sono inclusi anche gli assistenti di IA come ChatGPT e Gemini. Per questi, listing mostra ciò che l'assistente sa dell'attività, spesso solo un nome, un indirizzo completo e un numero di telefono, e non è presente un accuracy per ogni campo.

Per trasformare una scansione completata in un elenco di correzioni, stampa ogni directory con un campo errato o mancante:

Directories that need attention
curl -s https://listingsapi.com/api/v4/listings/scans/TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz \-H "Authorization: API $LISTINGSAPI_KEY" \| jq -r '.data.listingScan.results[]         | select(.accurate == false)         | .directory.name as $directory         | .accuracy         | to_entries[]         | select(.value.status != "ACCURATE")         | $directory + " " + .key + ": " + (.value.message // .value.status)'

Per la scansione di Joe's Pizza vengono stampate due righe: su Facebook manca il numero di telefono e Judys Book ne mostra uno diverso.

Text
Facebook phone: No phone number on the listingJudys Book phone: Phone number does not match

Da dove provengono i risultati

source indica come sono stati ottenuti i risultati della scansione e resultsAsOf indica quando sono stati raccolti i dati.

sourceSignificato
DIRECTORY_SEARCHUna nuova ricerca nelle directory. resultsAsOf indica quando è terminata questa scansione.
RECENT_SCANUna copia di una scansione della stessa attività eseguita da chiunque negli ultimi 7 giorni. Si completa immediatamente e resultsAsOf indica quando sono stati raccolti i dati originali, quindi può essere precedente a createdAt.
LOCATION_LISTINGSL'attività corrisponde a una delle tue sedi per numero di telefono, codice postale e paese, quindi i risultati provengono dalle schede effettive di quella sede. locationId è impostato, accuracySource è SYNC_STATUS anziché DIRECTORY_MATCH e ogni risultato include il valore syncStatus di quella scheda.

Costi, limiti ed errori

Solo una nuova scansione DIRECTORY_SEARCH consuma la quota mensile, che dipende dal tuo piano e si rinnova a ogni periodo di fatturazione. Queste operazioni sono gratuite:

  • ripetere una scansione della stessa attività entro 7 giorni. Viene restituita la tua scansione precedente con reused: true;
  • una scansione i cui risultati possono essere ricavati da una scansione recente della stessa attività eseguita da qualsiasi account. È una copia RECENT_SCAN;
  • scansioni che corrispondono a una delle tue sedi (LOCATION_LISTINGS);
  • scansioni che terminano con FAILED, per le quali viene rimborsata la quota consumata.

Le scansioni vengono conservate per 90 giorni. Trascorso questo periodo, GET restituisce 404 con SY83005.

Le risposte di create e list includono un oggetto quota, così puoi verificare la quota disponibile prima di avviare un gruppo di scansioni. List restituisce le tue scansioni dalla più recente alla meno recente, in pagine da 1 a 50 elementi con perPage, senza results.

Check your allowance
curl -s "https://listingsapi.com/api/v4/listings/scans?page=1&perPage=20" \-H "Authorization: API $LISTINGSAPI_KEY" \| jq '.data.listingScans.quota'
Campo quotaSignificato
limitNuove scansioni consentite dal tuo piano nel periodo corrente.
usedNuove scansioni utilizzate finora.
remaininglimit meno used, mai inferiore a 0.
resetsAtQuando si rinnova la quota.

Questi sono gli errori che incontrerai più spesso. Create restituisce gli errori nel corpo della risposta con stato HTTP 200; get e list restituiscono il consueto stato HTTP.

CodiceDoveSignificato e soluzione
SY83002CreateLa quota mensile è esaurita. Il messaggio include il limite e il momento del rinnovo, e viene restituito quota. Attendi resetsAt oppure riutilizza gratuitamente una scansione degli ultimi 7 giorni.
SY83003Create oppure failure di una scansioneUn valore è mancante o non valido. contextInfo indica il nome del campo. Correggi i dati di input e inviali di nuovo.
SY83004CreateListings scans non sono disponibili per quel valore di countryIso.
SY83001CreateIl tuo piano non include listings scans.
SY83005GetNon esiste alcuna scansione con quell'id, oppure la scansione appartiene a un altro account o risale a più di 90 giorni fa. HTTP 404.
SY83010Create oppure failure di una scansioneLe scansioni sono temporaneamente non disponibili. Riprova più tardi.
SY90016CreateLa chiave è di sola lettura. Usa una chiave Write.

Una richiesta GET per un id che non esiste restituisce:

JSON
{  "data": {    "listingScan": null  },  "errors": [    {      "message": "SY83005: Listing scan not found"    }  ]}

Una scansione che termina con FAILED riporta il motivo in failure e results rimane vuoto:

JSON
{  "status": "FAILED",  "failure": {    "code": "SY83010",    "message": "SY83010: Listing scans are temporarily unavailable, try again later",    "field": null  }}

Il formato generale degli errori è descritto in Codici di errore.

Per gli agenti IA

Il server MCP ospitato espone questa funzionalità tramite 3 strumenti: listings_scans_create, listings_scans_get e listings_scans_list. Un assistente può avviare una scansione per un'attività indicata dall'utente, verificarne periodicamente lo stato con listings_scans_get e riepilogare le directory che richiedono attenzione. Come per REST, la creazione di una scansione richiede una chiave Write. La configurazione è descritta in Collega gli agenti IA a MCP di listingsAPI.

Passaggi successivi