Check listing status and get live links
Read every publisher's sync status for a location, collect the live listing URLs, summarize coverage, check voice assistants, and get notified by webhook instead of polling.
Once a location exists, one listing per publisher in your plan moves through submission on its own. This guide shows how to read where each one stands, pull the URLs of the listings that are live, and stop polling once webhooks are set up.
1. Fetch the premium listings for a location
GET /locations/{locationId}/listings/premium returns one record per premium
publisher in the plan. It is a plain array, not a paginated list. The sibling
route /listings/additional returns the free directories with the same
shape; there is no listingType query parameter.
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/listings/premium \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "listingsForLocation": [ { "site": { "name": "Google Maps", "url": "maps.google.com" }, "syncStatus": "REQUIRING_ACTION", "displayStatus": "Connect your account to sync the listing", "actionRequired": true, "listingUrl": null, "syncIssue": null, "lastUpdate": "2026-07-06T18:33:28Z" }, { "site": { "name": "BeLocalFocussed", "url": "belocalfocussed.com" }, "syncStatus": "SYNCED", "displayStatus": "Synced", "actionRequired": false, "listingUrl": "https://belocalfocussed.com/jenny-home-26x0ffkd0e", "syncIssue": null, "lastUpdate": "2026-07-06T18:33:30Z" } ] }}Each row pairs a site with a machine-readable syncStatus and a
human-readable displayStatus. lastUpdate tells you when the row last
changed, which is useful for spotting a submission that has stalled.
2. Read the status of each publisher
syncStatus is the field to branch on. displayStatus is the text to show a
person, and syncIssue carries the publisher's reason when there is one.
Google Maps, Facebook, and Apple stay at REQUIRING_ACTION until the
matching account is connected. That is expected for a new location, not a
fault.
3. Collect the live links
listingUrl is only set on live rows. Filter to SYNCED (and COMPLETED)
and keep the URL with the site name so you can render a "see it live" list or
store it against your own record.
curl -s https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/listings/premium \-H "Authorization: API $LISTINGSAPI_KEY" \| jq '[.data.listingsForLocation[] | select(.listingUrl != null) | {site: .site.name, url: .listingUrl}]'A SYNCED row with a null listingUrl does happen: some directories do not
expose a public page URL. Treat the status as the source of truth and the
link as a bonus.
4. Build a coverage summary
Counting rows by syncStatus gives a one-line health figure per location:
how many publishers are live, how many are still in flight, and how many are
blocked. This is the number to put on a dashboard.
curl -s https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/listings/premium \-H "Authorization: API $LISTINGSAPI_KEY" \| jq '.data.listingsForLocation | group_by(.syncStatus) | map({status: .[0].syncStatus, count: length})'Run this across many locations by paging GET /locations and calling the
premium endpoint once per location. Each call counts against your rate limit,
so leave a gap between them and honor Retry-After on a 429. Find and fix listing errors shows the
loop as an account-wide sweep.
5. Check voice assistants
Voice surfaces (Google Assistant, Alexa, Siri, Cortana, Bixby) have their own
endpoint, GET /locations/{locationId}/voice-assistants, with a smaller
status set. Siri and Google Assistant depend on the Google connection, so a
location without one shows a blocker here too.
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/voice-assistants \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "voiceAssistantsForLocation": [ { "voiceIdentifier": "GOOGLE_ASSISTANT", "name": "Google Assistant", "syncStatus": "REQUIRING_ACTION", "actionRequired": true, "syncIssue": null, "errors": [] }, { "voiceIdentifier": "SIRI", "name": "Siri", "syncStatus": "IN_PROGRESS", "actionRequired": false, "syncIssue": "Google connection is required.", "errors": [] } ] }}There is no live link for a voice assistant; LIVE is the end state.
6. Get told instead of polling
If you have a webhook URL configured at /dashboard/webhooks, the
listing.submission event fires
each time a publisher submission for one location reaches a terminal state.
It carries status (success, incomplete, or canceled), the site, and
live_link on success, which is the same URL you collected in step 3.
import base64 def handle(payload: dict) -> None: if payload["event"] != "listing.submission": return data = payload["data"] # "TG9jYXRpb246Mjc5Mzgx" -> "Location:279381" global_id = base64.b64decode(data["location_id"]).decode() database_id = int(global_id.split(":")[1]) site = data["site"]["name"] status = payload["status"] if status == "success": live_link = payload.get("live_link") save_live_link(database_id, site, live_link) elif status == "incomplete": flag_for_review(database_id, site, payload.get("error_message"))Respond with 200 within 5 seconds and do the work afterwards; there are no
retries. For the receiver skeleton and signature check, see
React to events with webhooks.
Next steps
- Find and fix listing errors: turn
REQUIRING_ACTIONandFAILEDrows into fixes. - Connect Google and import locations: clears the most common blocker on Google Maps, Apple, and voice.
- Find and suppress duplicate listings: the other half of listing health.
- Reference: Get premium listings, Get voice assistant listings, Event reference.