Audita las fichas de un negocio con un listings scan

Revisa un negocio en mapas, buscadores, directorios y asistentes de IA, la precisión de sus datos por directorio y recibe resultados por webhook o polling.

Un listings scan comprueba cómo aparece un negocio en la red de directorios de listingsAPI y te indica, directorio por directorio, si se encuentra y si su nombre, dirección y teléfono coinciden con los que proporcionaste. El negocio no tiene que ser una ubicación de tu cuenta. Esta guía crea un escaneo, espera a que termine y lee los resultados.

Úsalo para:

  • auditar a un posible cliente antes de incorporarlo, para que la primera conversación empiece por las fichas que faltan o contienen errores;
  • comprobar la presencia de un cliente y la coherencia de sus datos NAP (nombre, dirección y teléfono) antes y después de corregir sus fichas;
  • dar a un agente de IA una forma de responder a «¿cómo aparece este negocio en los directorios?» sin añadir el negocio a la cuenta.

1. Crea el escaneo

POST /listings/scans recibe los datos del negocio en un único objeto input y devuelve de inmediato el escaneo en estado QUEUED. Los directorios se comprueban en segundo plano.

CampoObligatorioNotas
nameSíHasta 150 caracteres.
streetSíHasta 200 caracteres.
citySíHasta 100 caracteres.
stateIsoSí, excepto GBCódigo del estado o provincia, hasta 50 caracteres.
postalCodeSí, excepto NG y AEHasta 20 caracteres.
countryIsoSíCódigo ISO 3166-1 de 2 letras, por ejemplo, US.
phoneSíDe 6 a 15 dígitos. Los formatos locales se interpretan en el contexto de countryIso, y también se admite un + inicial.
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 respuesta devuelve el escaneo, con phone normalizado al formato internacional. results está vacío hasta que se comprueban los directorios, así que 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 respuesta también incluye un objeto quota, que se explica en Costes, límites y errores.

Comprueba estas 2 cosas antes de continuar:

  • reused. Cuando solicitas un escaneo de un negocio que ya escaneaste en los últimos 7 días, listingsAPI devuelve ese escaneo en lugar de iniciar uno nuevo, con reused: true y su estado actual, normalmente COMPLETED. No se descuenta nada de tu cuota ni se envía un nuevo webhook para ese escaneo.
  • errors. Una solicitud rechazada también recibe el estado HTTP 200. El rechazo se indica en data.createListingScan.errors, y scan es null. Un número de teléfono demasiado corto produce este resultado:
JSON
{  "data": {    "createListingScan": {      "reused": false,      "scan": null,      "errors": [        {          "code": "SY83003",          "message": "SY83003: Invalid value for phone",          "contextInfo": [            {              "key": "field",              "value": "phone"            }          ]        }      ]    }  }}

contextInfo indica el nombre del campo que falló. Los demás códigos que puedes recibir se encuentran en la tabla de errores.

2. Espera los resultados

Un escaneo pasa de QUEUED a IN_PROGRESS y después a COMPLETED o FAILED. La mayoría termina en un plazo de entre 1 y 7 minutos. Si un directorio no ha respondido al cabo de unos 15 minutos, su resultado se establece en UNAVAILABLE y el escaneo se completa con los demás resultados. Usa un webhook si puedes y recurre a las consultas periódicas solo como alternativa.

Cuando el escaneo alcanza un estado final, listingsAPI envía un único evento listing_scan.completed o listing_scan.failed a tu URL de webhook. Su data.scan es el mismo objeto que devuelve GET, con los resultados incluidos, así que no tienes que obtener nada más después.

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": [ "..." ]    }  }}

Aquí se muestra una versión abreviada del cuerpo del evento; el ejemplo completo está en la referencia de eventos. Los eventos se firman y se entregan como cualquier otro evento, con 1 intento y un tiempo máximo de 5 segundos, así que verifica la firma, guarda el cuerpo sin procesar y confirma la recepción antes de hacer cualquier otra cosa. El receptor de Reaccionar a eventos con webhooks ya hace esto; el único cambio es añadir una rama más según 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 solo se envía a las cuentas que tienen los webhooks habilitados y una URL verificada. Si tu cuenta no cumple estas condiciones, o si un evento nunca llega, consulta periódicamente el escaneo; el escaneo se conserva durante 90 días en cualquier caso. Los detalles de la firma están en Firmas y verificación.

2b. Consulta periódicamente como alternativa

GET /listings/scans/{scanId} devuelve el escaneo con los resultados disponibles hasta ese momento. Mientras se ejecuta el escaneo, results se completa a medida que responden los directorios y summary.pending cuenta los que siguen pendientes. Consulta cada 15 segundos y no con mayor frecuencia; cada solicitud cuenta para tu límite de solicitudes.

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
statusSignificado
QUEUEDAceptado y pendiente de inicio.
IN_PROGRESSSe están comprobando los directorios. results es parcial.
COMPLETEDTodos los directorios tienen una respuesta. results y summary son definitivos.
FAILEDNo se pudo iniciar el escaneo, por ejemplo, porque el servicio no estaba disponible temporalmente (SY83010). failure contiene code y message, y results está vacío. Un escaneo fallido no consume tu cuota, por lo que puedes volver a crearlo.

Un escaneo cuyos resultados proceden de un escaneo reciente o de una de tus propias ubicaciones tiene el estado COMPLETED desde el momento en que se crea, por lo que basta con una sola solicitud GET.

3. Lee los resultados

Un escaneo completado tiene un summary y una entrada en results por cada directorio comprobado, hasta unos 60 según el país. business devuelve los datos que enviaste, con phone normalizado al formato internacional.

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

total es el número de directorios. found indica cuántos tienen una ficha del negocio y se desglosa en accurate, inaccurate y las fichas que solo confirman la presencia descritas a continuación. notFound indica cuántos no tienen una ficha del negocio. unavailable cuenta los directorios que no se pudieron comprobar en esta ocasión, y pending solo es distinto de cero mientras se ejecuta el escaneo.

Cada resultado tiene un status correspondiente al directorio:

status del resultadoSignificado
FOUNDEl directorio tiene una ficha del negocio. listing contiene la información que muestra.
NOT_FOUNDSe comprobó el directorio y el negocio no aparece en él.
UNAVAILABLENo se pudo comprobar el directorio en esta ocasión. Esto no equivale a que no haya una ficha, por lo que no debes indicarlo como una ausencia. Vuelve a comprobar ese directorio más adelante.
PENDINGAún no se ha comprobado. Solo aparece mientras se ejecuta el escaneo.

Para un resultado FOUND, accurate indica si la ficha coincide con los datos que enviaste:

  • true significa que el nombre, la dirección y el teléfono coinciden.
  • false significa que al menos uno no coincide. Un campo que la ficha omite se considera una discrepancia, por lo que una ficha sin número de teléfono es false.
  • null significa que se encontró el negocio, pero no se realizó una comprobación campo por campo.

accuracy explica la evaluación con un status y un message para cada uno de los campos name, address y phone. El valor de status es ACCURATE, INACCURATE o MISSING, y message es null cuando el campo es correcto. Esta página de Facebook coincide en el nombre y la dirección, pero no tiene número de teléfono:

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}

Algunos directorios solo confirman que el negocio existe y no muestran suficientes detalles para comparar. Estos resultados se devuelven como FOUND con accurate: null y se cuentan únicamente en found, no en accurate ni en inaccurate. Trátalos como presentes, no como verificados:

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 resultado NOT_FOUND no tiene listing ni 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}

Varios directorios no almacenan datos propios. Uber, Waze y Lyft muestran datos de Google Maps, y Copilot muestra datos de Bing. Sus resultados reflejan los del directorio de origen, por lo que suelen repetir lo que devolvieron Google Maps o Bing, y no tienen un url de la ficha. Cuéntalos como entradas separadas en summary, pero no los interpretes como una confirmación independiente:

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}

También se incluyen asistentes de IA como ChatGPT y Gemini. En su caso, listing muestra lo que el asistente sabe sobre el negocio, que a menudo se limita a un nombre, una dirección completa y un número de teléfono, y no hay un accuracy por campo.

Para convertir un escaneo completado en una lista de correcciones, imprime cada directorio que tenga un campo incorrecto o ausente:

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)'

Para el escaneo de Joe's Pizza, esto imprime dos líneas: Facebook no tiene número de teléfono y Judys Book muestra uno diferente.

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

De dónde proceden los resultados

source indica cómo se obtuvieron los resultados del escaneo, y resultsAsOf indica cuándo se recopilaron los datos.

sourceSignificado
DIRECTORY_SEARCHUna nueva búsqueda en los directorios. resultsAsOf indica cuándo terminó este escaneo.
RECENT_SCANUna copia de los resultados de un escaneo del mismo negocio realizado por cualquier usuario en los últimos 7 días. Se completa de inmediato, y resultsAsOf indica cuándo se recopilaron los datos originales, por lo que puede ser anterior a createdAt.
LOCATION_LISTINGSEl negocio coincidió con una de tus propias ubicaciones por teléfono, código postal y país, por lo que los resultados proceden de las fichas reales de esa ubicación. locationId tiene un valor asignado, accuracySource es SYNC_STATUS en lugar de DIRECTORY_MATCH, y cada resultado incluye el valor de syncStatus de esa ficha.

Costes, límites y errores

Solo un nuevo escaneo DIRECTORY_SEARCH consume tu cuota mensual, que depende de tu plan y se restablece con tu periodo de facturación. Los siguientes casos son gratuitos:

  • repetir un escaneo del mismo negocio en un plazo de 7 días. Se devuelve tu propio escaneo anterior con reused: true;
  • un escaneo cuyos resultados pueden obtenerse de un escaneo reciente del mismo negocio realizado desde cualquier cuenta. Es una copia RECENT_SCAN;
  • los escaneos que coinciden con una de tus propias ubicaciones (LOCATION_LISTINGS);
  • los escaneos que terminan en FAILED, cuyo consumo se reembolsa.

Los escaneos se conservan durante 90 días. Después de ese plazo, GET devuelve 404 con SY83005.

Las respuestas de creación y listado incluyen un objeto quota para que puedas consultar tu consumo antes de iniciar un lote. La operación de listado devuelve tus escaneos del más reciente al más antiguo, en páginas de 1 a 50 con perPage, sin 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 quotaSignificado
limitEscaneos nuevos que permite tu plan en el periodo actual.
usedEscaneos nuevos consumidos hasta el momento.
remaininglimit menos used, nunca inferior a cero.
resetsAtCuándo se restablece la cuota.

Estos son los errores que verás con más frecuencia. La operación de creación devuelve sus errores en el cuerpo con HTTP 200; las operaciones de obtención y listado devuelven el estado HTTP habitual.

CódigoDóndeSignificado y solución
SY83002CreaciónLa cuota mensual se ha agotado. El mensaje incluye el límite y el momento en que se restablece la cuota, y se devuelve quota. Espera hasta resetsAt o reutiliza un escaneo de los últimos 7 días, que no tiene coste.
SY83003Creación, o failure de un escaneoFalta un valor o no es válido. contextInfo indica el nombre del campo. Corrige los datos de entrada y vuelve a enviarlos.
SY83004CreaciónLos Listings scans no están disponibles para ese countryIso.
SY83001CreaciónTu plan no incluye listings scans.
SY83005ObtenciónNo existe ningún escaneo con ese id, el escaneo pertenece a otra cuenta o tiene más de 90 días. HTTP 404.
SY83010Creación, o failure de un escaneoLos escaneos no están disponibles temporalmente. Vuelve a intentarlo más tarde.
SY90016CreaciónLa clave es de solo lectura. Usa una clave Write.

Una solicitud GET para un id que no existe devuelve:

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

Un escaneo que termina en FAILED tiene el motivo en failure, y results permanece vacío:

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

El formato general de los errores se describe en Códigos de error.

Para agentes de IA

El servidor MCP alojado ofrece esta función mediante tres herramientas: listings_scans_create, listings_scans_get y listings_scans_list. Un asistente puede iniciar un escaneo de un negocio que indique el usuario, consultar periódicamente su estado con listings_scans_get y resumir los directorios que requieren atención. Al igual que con REST, crear un escaneo requiere una clave Write. La configuración se describe en Conecta agentes de IA al MCP de listingsAPI.

Siguientes pasos