POST/api/v4/listings/scans

Create a listings scan

Starts an audit of how any business is listed across the directory network, and returns the scan immediately so you can wait for the completion webhook.

Requires an API key. See Authentication for header format and key rotation.

Parameters

Body

NameTypeRequiredDescription
inputobjectrequiredWrapper object. The entire request body must be nested under `input`.
input.namestringrequiredBusiness name, up to 150 characters.
input.streetstringrequiredStreet address, up to 200 characters.
input.citystringrequiredCity, up to 100 characters.
input.stateIsostringoptionalState or province code, up to 50 characters. Required for every country except `GB`.
input.postalCodestringoptionalPostal code, up to 20 characters. Required for every country except `NG` and `AE`.
input.countryIsostringrequiredTwo-letter ISO 3166-1 country code, for example `US`.
input.phonestringrequiredBusiness phone number with 6 to 15 digits. Local formats are read in the context of `countryIso`, and an international format with a leading `+` works too.

Sample request

Ready-to-paste body. Replace placeholder IDs and values with yours.

Request body
{  "input": {    "name": "Franklin Barbecue",    "street": "900 E 11th St",    "city": "Austin",    "stateIso": "TX",    "postalCode": "78702",    "countryIso": "US",    "phone": "+15126531187"  }}

Responses

200The scan was accepted and is `QUEUED`. `reused` is `false` and `results` is empty until directories are checked. Wait for the `listing_scan.completed` webhook, or poll the scan.
Response
{  "data": {    "createListingScan": {      "reused": false,      "scan": {        "id": "TGlzdGluZ1NjYW46N2MxZTRmNTItOWEzYi00ZDY4LWIwZTEtNWYyYThjOWQzZTQ3",        "status": "QUEUED",        "source": "DIRECTORY_SEARCH",        "locationId": null,        "resultsAsOf": null,        "createdAt": "2026-11-05T07:00:41Z",        "startedAt": null,        "completedAt": null,        "business": {          "name": "Franklin Barbecue",          "street": "900 E 11th St",          "city": "Austin",          "stateIso": "TX",          "postalCode": "78702",          "countryIso": "US",          "phone": "+15126531187"        },        "summary": {          "total": 0,          "found": 0,          "accurate": 0,          "inaccurate": 0,          "notFound": 0,          "pending": 0,          "unavailable": 0        },        "failure": null,        "results": []      },      "quota": {        "limit": 50,        "used": 6,        "remaining": 44,        "resetsAt": "2026-11-23T12:38:03Z"      },      "errors": []    }  }}
200The request was rejected, so **no scan was created**. The error is reported in `errors[]` on an `HTTP 200`, `scan` is `null`, and `quota` still returns the current allowance. This example is a phone number with too few digits.
Response
{  "data": {    "createListingScan": {      "reused": false,      "scan": null,      "quota": {        "limit": 50,        "used": 5,        "remaining": 45,        "resetsAt": "2026-11-23T12:38:03Z"      },      "errors": [        {          "code": "SY83003",          "message": "SY83003: Invalid value for phone",          "contextInfo": [            {              "key": "field",              "value": "phone"            }          ]        }      ]    }  }}
400A read-only key was used on this write (`SY90016`). Use a Write key.
401Unauthenticated, missing or invalid API key.
429Rate limit exceeded. Retry after the `Retry-After` header value.

Starts a scan of one business. The scan audits whether, and how accurately, the business is listed across the directory network: maps and search engines such as Google, Bing, Apple Maps, Yelp, Facebook and MapQuest, data aggregators and local directories, and AI assistants such as ChatGPT and Gemini. Depending on the country, a scan checks up to about 60 directories. The business does not need to be a location in your account, so you can audit a prospect, a client before onboarding.

The call returns immediately with the scan in QUEUED status. The directory checks run in the background, and the result is read with GET /listings/scans/{scanId}.

Use case: a listing audit for a prospect. Your sales tool collects a business name, address and phone number. Create a scan, store the returned scan.id, and read the finished scan from data.scan when the listing_scan.completed event arrives. The summary gives the headline (how many directories found the business, and how many of those are accurate), and results[] gives the per-directory detail to show in the report.

Use case: a health check on a business you already manage. Scan the business details of one of your own locations. The scan recognises it and builds its results from that location's real listings instead of searching again (see Reuse and free scans).

How a scan finishes

A scan moves through QUEUED, IN_PROGRESS and then ends as COMPLETED or FAILED. Most scans complete in one to seven minutes. If a directory has not answered after about 15 minutes, its result is set to UNAVAILABLE and the scan completes with the other results.

  • Prefer the webhook. The listing_scan.completed and listing_scan.failed events are delivered once per scan, with the same scan object that GET /listings/scans/{scanId} returns, results included. They go to accounts that have webhooks enabled and a verified webhook URL, signed like every other event. See listing_scan.completed.
  • Poll as a fallback. If you cannot receive webhooks, read the scan every 15 seconds until status is COMPLETED or FAILED. Polling faster gains nothing and spends your request budget.

Required fields

name, street, city, countryIso and phone are always required. Two fields depend on the country:

FieldRequiredExceptions
stateIsoYesNot required for GB.
postalCodeYesNot required for NG and AE.

A missing or malformed value is rejected with SY83003, and the error names the field. phone must have between 6 and 15 digits, and countryIso must be two letters.

Reuse and free scans

A scan is not always a fresh search. The source field on the returned scan says where its results came from:

sourceWhen you get it
DIRECTORY_SEARCHA fresh search of the directories. This is the only kind that uses your monthly allowance.
RECENT_SCANThe same business was scanned by anyone in the last 7 days, so its results were copied. resultsAsOf shows when the data was collected. Returned already COMPLETED.
LOCATION_LISTINGSThe business matches one of your own locations by phone number, postal code and country. The results come from that location's real listings, locationId is set, and each result includes a syncStatus.

If you scan the same business again within 7 days, the existing scan from your account comes back instead of a new one, with reused: true on the response. No new scan is started and no new webhook is sent, so read the returned scan (it may still be running) rather than waiting for another event. Scanning one of your own locations again within the reuse window behaves the same way.

Monthly allowance

Listings scans are included on paid plans with a monthly allowance that depends on your plan. They are not available on a free sandbox day pass. Only a fresh DIRECTORY_SEARCH scan counts against the allowance. These are free:

  • a repeat of the same business within 7 days (reused: true),
  • a RECENT_SCAN copy,
  • a LOCATION_LISTINGS scan,
  • a scan that fails, which is not counted.

The allowance resets with your account's billing period. Every create response includes quota with limit, used, remaining and resetsAt, including when the request is rejected. See Plans and add-ons.

Errors

A create that is rejected comes back as an HTTP 200 with scan: null and the reason in errors[], so check errors and not just the status code. Read-only keys cannot create scans: that is the one request-level failure, an HTTP 400 with SY90016.

CodeMeaningWhat to do
SY83001Listings scans are not available on your plan.Use an account on a paid plan. Day passes cannot run scans.
SY83002The monthly allowance is used up. The message includes the limit and the reset time, and quota is returned.Wait for quota.resetsAt, or contact us about a higher allowance. Reusing a recent scan is free.
SY83003A field is missing or invalid. contextInfo names it under field.Fix that field and resend.
SY83004Scans are not available for that country.Check countryIso. The message names the code that was refused.
SY83010Scans are temporarily unavailable.Retry later.

The full list of codes is in Error codes.

Notes.

  • The request body must be wrapped in an input object.
  • Check errors, not the HTTP status. A rejected create is an HTTP 200.
  • Phone numbers are normalized before the scan runs, and two requests that differ only in formatting, letter case or spacing count as the same business.
  • A create costs one request against your rate limit, however many directories the scan checks.
  • A scan is kept for 90 days. After that, reading it returns 404 with SY83005.
  • This call needs a Write key.
Request
curl -X POST 'https://listingsapi.com/api/v4/listings/scans' \  -H "Authorization: API $LISTINGSAPI_KEY" \  -H 'Content-Type: application/json' \  -d '{    "input": {      "name": "Franklin Barbecue",      "street": "900 E 11th St",      "city": "Austin",      "stateIso": "TX",      "postalCode": "78702",      "countryIso": "US",      "phone": "+15126531187"    }  }'