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

MethodPathWhat it doesReference
GET/locations/{locationId}/listings/premiumSync status of every premium publisher for a location.Get premium listings
GET/locations/{locationId}/voice-assistantsSync status on Google Assistant, Alexa, Siri, Cortana, Bixby.Get voice assistant listings
GET/locations/listings/duplicatesAccount-wide, paged rollup of detected duplicates.Get all duplicate listings for an account
GET/locations/{locationId}/listings/duplicatesDuplicates for one location, grouped by site.Get duplicate listings for a location
POST/locations/listings/mark-as-duplicateConfirm listing items as duplicates for suppression.Mark listing as duplicate
POST/locations/listings/mark-as-not-duplicateDismiss listing items as false positives.Mark listing as not a duplicate

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

NameInRequiredNotes
locationIdpathYesBase64 id or numeric databaseId.
Premium listings
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/listings/premium \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "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.

syncStatusMeaning
AVAILABLEReady to sync, not started yet.
IN_PROGRESSSubmitted to the publisher, awaiting a response.
PENDING_APPROVALThe publisher is reviewing it.
SYNCED or COMPLETEDLive. listingUrl is populated.
REQUIRING_ACTIONBlocked on you. actionRequired is true; read displayStatus.
FAILEDThe sync failed. Read syncIssue.
CANNOT_SUBMITThis publisher cannot take this location at all.

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

NameInRequiredNotes
locationIdpathYesBase64 id or numeric databaseId.
Voice assistants
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/voice-assistants \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "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.

syncStatusMeaning
NOT_SUBMITTEDNot sent to the assistant yet.
IN_PROGRESSSubmission underway.
REQUIRING_ACTIONCustomer input needed before the sync can proceed.
LIVEPublished. There is no live link for a voice assistant.

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

NameInRequiredNotes
statusqueryNoPOTENTIAL (default), PROCESSING, FIXED, or FAILED. Case-insensitive. Any other value is a 400.
siteIdqueryNoNarrow to one publisher by its numeric site id (the site.databaseId from the premium endpoint, or a plan-sites id).
tagqueryNoScope the rollup to one tag. Defaults to all.
pagequeryNo1-based page number. Defaults to 1.
perPagequeryNoRecords per page.

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.

Account rollup
curl "https://listingsapi.com/api/v4/locations/listings/duplicates?status=POTENTIAL&page=1&perPage=50" \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "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.

StatusMeaning
POTENTIALDetected, nobody has acted on it.
PROCESSINGConfirmed as a duplicate; suppression is under way with the publisher.
FIXEDThe publisher removed or merged it.
FAILEDSuppression did not go through. Check the liveLink.

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

NameInRequiredNotes
locationIdpathYesBase64 id or numeric databaseId.
Duplicates for a location
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/listings/duplicates \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "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

NameInRequiredNotes
input.listingItemIdsbodyYesArray of base64 listingItemId values from the duplicates endpoints.
input.locationIdbodyNoBase64 or numeric location id. Scopes the operation to one location.
Mark as duplicate
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="]  }}'
JSON
{  "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

NameInRequiredNotes
input.listingItemIdsbodyYesArray of base64 listingItemId values to dismiss.
input.locationIdbodyNoBase64 or numeric location id. Scopes the operation to one location.
Mark as not duplicate
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="]  }}'
JSON
{  "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

Code or statusMeaningFix
401Missing or invalid API key.Send Authorization: API $LISTINGSAPI_KEY.
400 on the account rollupstatus is not one of POTENTIAL, PROCESSING, FIXED, FAILED.Use one of the four values.
400 on a mark endpointUnknown or malformed listing item id. Read errors[].code and message.Send listingItemId values from the duplicates endpoints.
200 with success: falseThe mutation ran but rejected the input. Details are in data.<op>.errors.Check the body of every write, not just the status.
SY90016 (HTTP 400)A Read key on a write.Use a Write key for the two mark endpoints.
429Rate limit exceeded. The Launch plan allows 10 requests per minute.Wait for Retry-After, then resume. See Rate limits.

Every code is listed in the error reference.

Next steps