/api/v4/listings/scansCreate 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.
Parameters
Body
| Name | Type | Required | Description |
|---|---|---|---|
| input | object | required | Wrapper object. The entire request body must be nested under `input`. |
| input.name | string | required | Business name, up to 150 characters. |
| input.street | string | required | Street address, up to 200 characters. |
| input.city | string | required | City, up to 100 characters. |
| input.stateIso | string | optional | State or province code, up to 50 characters. Required for every country except `GB`. |
| input.postalCode | string | optional | Postal code, up to 20 characters. Required for every country except `NG` and `AE`. |
| input.countryIso | string | required | Two-letter ISO 3166-1 country code, for example `US`. |
| input.phone | string | required | Business 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.
{ "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.
{ "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.
{ "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.completedandlisting_scan.failedevents are delivered once per scan, with the same scan object thatGET /listings/scans/{scanId}returns, results included. They go to accounts that have webhooks enabled and a verified webhook URL, signed like every other event. Seelisting_scan.completed. - Poll as a fallback. If you cannot receive webhooks, read the scan every
15 seconds until
statusisCOMPLETEDorFAILED. 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:
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:
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_SCANcopy, - a
LOCATION_LISTINGSscan, - 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.
The full list of codes is in Error codes.
Notes.
- The request body must be wrapped in an
inputobject. - Check
errors, not the HTTP status. A rejected create is anHTTP 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
404withSY83005. - This call needs a Write key.
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" } }'