Listings
Every Listings endpoint with a cURL, Python and Node sample: premium sync status, voice assistants, duplicate detection per location and per account, and duplicate suppression.
A listing is one location's entry on one publisher directory. Creating a
location queues a listing per publisher in your plan, and the Listings
endpoints read where each one stands, list the duplicates detection has
found, and confirm or dismiss them. The four GET endpoints work with a
Read key; the two POST endpoints need a Write key.
Endpoints at a glance
Read premium listing status
GET /locations/{locationId}/listings/premium
Returns one record per premium publisher in the location's plan under
data.listingsForLocation. It is a plain array, not a paginated list. The
sibling path /listings/additional returns the free directories in the same
shape; there is no listingType query parameter, and a value sent there is
ignored.
Parameters
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/listings/premium \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "listingsForLocation": [ { "id": "TGlzdGluZzoxMDQ0Njg3MjM=", "site": { "databaseId": "1", "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" }, { "id": "TGlzdGluZzoxMDQ0Njg3Mjc=", "site": { "databaseId": "387", "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" }, { "id": "TGlzdGluZzoxMDQ0Njg3MDk=", "site": { "databaseId": "301", "name": "Local Mint", "url": "localmint.com" }, "syncStatus": "FAILED", "displayStatus": "Partner experiencing issues; awaiting response", "actionRequired": false, "listingUrl": null, "syncIssue": "Publisher requires website URL", "lastUpdate": "2026-07-06T18:34:03Z" } ] }}Branch on syncStatus. Show displayStatus to a person, read syncIssue
for the publisher's reason, and use lastUpdate to spot a submission that
has stalled. listingUrl is only set on live rows.
Google Maps, Facebook, and Waze sit at REQUIRING_ACTION with
displayStatus: "Connect your account to sync the listing" until the
matching account is connected. That is expected for a new location.
Read voice assistant status
GET /locations/{locationId}/voice-assistants
Returns one record per voice surface under
data.voiceAssistantsForLocation: Google Assistant, Amazon Alexa, Apple
Siri, Microsoft Cortana, and Samsung Bixby. It is a plain array with a
smaller status set than the premium endpoint.
Parameters
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/voice-assistants \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "voiceAssistantsForLocation": [ { "databaseId": "104468723", "voiceIdentifier": "GOOGLE_ASSISTANT", "name": "Google Assistant", "connectedAccountId": null, "syncStatus": "REQUIRING_ACTION", "actionRequired": true, "syncIssue": null, "errors": [] }, { "databaseId": "104468711", "voiceIdentifier": "SIRI", "name": "Siri", "connectedAccountId": null, "syncStatus": "IN_PROGRESS", "actionRequired": false, "syncIssue": "Google connection is required.", "errors": [] } ] }}Flag every row where actionRequired is true or syncIssue is set.
errors carries publisher-side error strings, and connectedAccountId
names the connected account driving the sync when one applies. Siri's
"Google connection is required." is the common blocker: Apple's voice
results are seeded from the Google Business Profile connection.
Roll up duplicates across the account
GET /locations/listings/duplicates
Returns every detected duplicate the key can see, across all locations and
publishers, under data.duplicateListingsRollup. It is an offset page:
page and perPage in, records[] plus pageInfo.totalRecords,
pageInfo.totalPages, and pageInfo.hasNextPage out. Filters are flat
query parameters, not JSON. With nothing supplied the gateway defaults
status to POTENTIAL and tag to all, so a bare call returns the
account's open duplicates.
Parameters
The Python and Node wrappers expose tag and page only. To filter by
status or siteId, or to set perPage, call the route directly as shown
in Handle duplicate listings.
curl "https://listingsapi.com/api/v4/locations/listings/duplicates?status=POTENTIAL&page=1&perPage=50" \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "duplicateListingsRollup": { "pageInfo": { "hasNextPage": true, "hasPreviousPage": false, "totalPages": 34, "totalRecords": 1673 }, "records": [ { "id": "3329229", "name": "Woodbury Train Station", "nameValid": false, "duplicateProcessingState": "POTENTIAL", "liveLink": "http://citysquares.com/b/woodbury-train-station-23598450", "locationId": "73451", "siteName": "City Squares", "siteUrl": "citysquares.com" } ] } }}The rollup tells you where duplicates are: each record names the
locationId and the siteName. duplicateProcessingState is the same
lifecycle status the per-location endpoint reports as status. Walk pages
until hasNextPage is false, one page at a time; a tight loop is the
quickest way to a 429.
List duplicates for one location
GET /locations/{locationId}/listings/duplicates
Returns every detected duplicate for a single location under
data.duplicateListingsForLocation, grouped by publisher site. It is a
plain array. Each group is a site (name, url) with a listings array,
and each entry carries the listingItemId that both mark endpoints take.
An empty array means nothing was detected.
Parameters
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/listings/duplicates \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "duplicateListingsForLocation": [ { "id": "U2l0ZToxNTA=", "name": "City Squares", "url": "citysquares.com", "listings": [ { "id": "RHVwbGljYXRlOjIzNTk4NDUw", "listingItemId": "TGlzdGluZ0l0ZW06MjM1OTg0NTA=", "name": "Woodbury Train Station", "liveLink": "http://citysquares.com/b/woodbury-train-station-23598450", "status": "POTENTIAL" } ] } ] }}Open liveLink to judge the entry, then keep listingItemId. In the Node
SDK the flattened d.id is the value to pass on. status uses the same
four values as the account rollup above.
Confirm listing items as duplicates
POST /locations/listings/mark-as-duplicate
Flags listing items as confirmed duplicates so they can be suppressed with
the publisher. Send the listingItemId values under input, and batch every
confirmed item for a location into one call rather than one call per item.
This is a write and needs a Write key.
Parameters
curl -X POST https://listingsapi.com/api/v4/locations/listings/mark-as-duplicate \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "listingItemIds": ["TGlzdGluZ0l0ZW06MjM1OTg0NTA="] }}'{ "data": { "markAsDuplicate": { "success": true, "errors": null } }}success: true means the items were accepted and move to PROCESSING. A
200 with success: false, or a 400, comes with an errors array whose
entries carry code, message, and path; an unknown or malformed id is
the usual cause. The Python SDK returns the payload; the Node SDK throws
ValidationError instead, so a resolved promise means it succeeded.
Dismiss listing items as not duplicates
POST /locations/listings/mark-as-not-duplicate
Dismisses potential duplicates as false positives. Detection is conservative and will flag a franchise sibling, a second legitimate profile, or a renamed location. Once dismissed, the items stop appearing in both duplicates endpoints. It takes the identical body to the endpoint above and is also how you reverse an item flagged in error.
Parameters
curl -X POST https://listingsapi.com/api/v4/locations/listings/mark-as-not-duplicate \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "listingItemIds": ["TGlzdGluZ0l0ZW06MjM1OTg0NTA="] }}'{ "data": { "markAsNotDuplicate": { "success": true, "errors": null } }}The response is data.markAsNotDuplicate with the same success and
errors fields as the mark-as-duplicate call. A dismissed item is recorded
as a confirmed non-duplicate and does not come back on the next detection
pass.
Errors you will see
Every code is listed in the error reference.
Next steps
- Check listing status and live links: turn the premium and voice endpoints into a coverage dashboard.
- Find and fix listing errors: work
through
REQUIRING_ACTIONandFAILEDrows. - Handle duplicate listings: the review loop across the four duplicate endpoints, including status tracking.
- Connect Google and import locations: clears the most common blocker on Google Maps, Apple, and voice.
- Resource landing: Listings. SDK references: Python listings, Node listings.