Use Cases
End-to-end TypeScript recipes showing how to combine listingsapi-js resources into real integrations.
These recipes are cross-resource workflows, the kind of scripts you reach
for when a single method page isn't enough. Each one is a complete,
runnable TypeScript program that stitches together two or more resources
into something you'd actually deploy. Install a pinned SDK
(npm install listingsapi-js@0.3.0), export LISTINGSAPI_KEY with a key
from your dashboard, and you can run them as-is with
npx tsx <file>.ts.
If you are new to the SDK, read the Node SDK overview first. It covers client construction, auth, errors, and pagination, which every recipe below assumes.
1. Quick start: fetch and print locations
The minimum viable integration: authenticate, pull one page of locations, and print their names.
import { ListingsAPI } from 'listingsapi-js'; const client = new ListingsAPI(); // reads LISTINGSAPI_KEY from env const page = await client.fetchAllLocations({ first: 5 });if (!Array.isArray(page)) {for (const loc of page.locations) { console.log(loc.name + ' - ' + loc.city);}}Without fetchAll, fetchAllLocations returns an object with locations,
pageInfo, and the raw payload. Pass pageInfo.endCursor as after to
get the next page.
2. Bulk export to JSON
Auto-paginate every location in the account and write the results to disk.
import { writeFileSync } from 'fs';import { ListingsAPI } from 'listingsapi-js';import type { Location } from 'listingsapi-js'; const client = new ListingsAPI(); const locations = (await client.fetchAllLocations({fetchAll: true,pageSize: 100,})) as Location[]; writeFileSync('locations.json', JSON.stringify(locations, null, 2));console.log('Exported ' + locations.length + ' locations to locations.json');fetchAll: true follows cursor pages automatically and returns a flat
Location[]. For 10 000+ locations, keep pageSize at 100 (the default)
so individual requests stay fast.
3. Onboard a new location
Look up the right category ID with fetchSubcategories, then create the
location. The databaseId of a subcategory is what createLocation expects
as subCategoryId. The API also requires a description of 200+ characters.
import { ListingsAPI } from 'listingsapi-js'; const client = new ListingsAPI(); const categories = await client.fetchSubcategories();const dentist = categories.find((c) => c.name === 'Dentist');if (!dentist) throw new Error('Category not found'); const description ='Acme Dental is a family-owned dental practice in downtown New York ' +'offering preventive care, cosmetic dentistry, orthodontics, and ' +'emergency appointments. Our team has served the neighborhood for over ' +'twenty years with same-week scheduling and weekend hours.'; const created = await client.createLocation({name: 'Acme Dental',storeId: 'ACME-NY-001',street: '123 Jump Street',city: 'New York',stateIso: 'NY',postalCode: '10013',countryIso: 'US',phone: '6443859313',subCategoryId: dentist.databaseId,description: description,}); console.log('Created location', created.location?.id);A validation failure (missing field, short description, bad category) throws
a ValidationError with the platform's error entries on err.errors, so
there is no success flag to inspect.
4. Publish a post to Google and Facebook in bulk
bulkPublish puts one post on both sites across many locations in a single
call. It validates the payload client-side, expands the message per site, and
encodes numeric location IDs for you.
import { ListingsAPI } from 'listingsapi-js'; const client = new ListingsAPI(); const result = await client.bulkPublish({name: 'Holiday hours',locationIds: [16808, 16809, 16810],message: 'Open until 10pm through the holidays!',mediaUrl: 'https://cdn.example.com/holiday.jpg',ctaType: 'LEARN_MORE',ctaUrl: 'https://example.com/holiday-hours',}); console.log('Campaign:', result.socialPost?.id, result.socialPost?.status); // Publishing is asynchronous; poll the bulk campaign for per-location statusconst bulk = await client.fetchBulkPost(result.socialPost.id);console.log(bulk);sites defaults to ['GOOGLE', 'FACEBOOK']. Pass a per-site map as
message ({ GOOGLE: '...', FACEBOOK: '...' }) when the copy should differ.
For single campaigns with typed options, use createAnnouncement,
createEvent, or createOffer instead.
5. Review monitoring: flag low ratings
Scan every location for 1- and 2-star reviews and log the ones that need attention.
import { ListingsAPI, APIError } from 'listingsapi-js';import type { Interaction, Location } from 'listingsapi-js'; const client = new ListingsAPI(); const locations = (await client.fetchAllLocations({ fetchAll: true })) as Location[]; for (const loc of locations) {let reviews: Interaction[];try { reviews = (await client.fetchInteractions(loc.id, { fetchAll: true, startDate: '2026-01-01', ratingFilters: [1, 2], })) as Interaction[];} catch (err) { if (err instanceof APIError) { console.error(loc.name + ': ' + err.statusCode); continue; } throw err;} for (const review of reviews) { const who = review.reviewerName ?? 'Anonymous'; console.log('NEEDS ATTENTION [' + loc.name + '] ' + who + ' (' + review.rating + ' stars)');}}6. Respond to unanswered reviews
Auto-reply to every review that does not yet have a response. Use a template
or swap buildResponse for an LLM call.
import { ListingsAPI } from 'listingsapi-js';import type { Interaction } from 'listingsapi-js'; const client = new ListingsAPI();const LOCATION_ID = 16808; function buildResponse(review: Interaction): string {const name = review.reviewerName ?? 'there';if ((review.rating ?? 5) <= 2) { return 'Hi ' + name + ', we are sorry to hear about your experience. Our team will reach out shortly.';}return 'Hi ' + name + ', thank you for the kind words. We look forward to seeing you again!';} const reviews = (await client.fetchInteractions(LOCATION_ID, {fetchAll: true,startDate: '2026-01-01',})) as Interaction[]; let responded = 0;for (const review of reviews) {if (review.responses && review.responses.length > 0) continue;await client.respondToReview(review.id, buildResponse(review));responded++;}console.log('Responded to ' + responded + ' reviews');7. Analytics report: Google, Bing, Facebook, and review stats
Pull profile analytics and review stats for a single location in parallel and print a one-page summary.
import { ListingsAPI } from 'listingsapi-js'; const client = new ListingsAPI();const LOCATION_ID = 16808;const range = { fromDate: '2026-01-01', toDate: '2026-06-30' }; const [google, bing, facebook, reviewOverview] = await Promise.all([client.fetchGoogleAnalytics(LOCATION_ID, range),client.fetchBingAnalytics(LOCATION_ID, range),client.fetchFacebookAnalytics(LOCATION_ID, range),client.fetchReviewAnalyticsOverview(LOCATION_ID, { startDate: range.fromDate, endDate: range.toDate,}),]); console.log('Google insights:', JSON.stringify(google));console.log('Bing insights:', JSON.stringify(bing));console.log('Facebook insights:', JSON.stringify(facebook));console.log('Review overview:', JSON.stringify(reviewOverview));Note the parameter split: platform analytics take fromDate/toDate while
review analytics take startDate/endDate.
8. Listings audit: find unsynced and duplicate profiles
Sweep one location for premium listings that are out of sync and for duplicate profiles detected by the crawler, then mark the duplicates.
import { ListingsAPI } from 'listingsapi-js'; const client = new ListingsAPI();const LOCATION_ID = 16808; const [premium, voice, duplicates] = await Promise.all([client.fetchPremiumListings(LOCATION_ID),client.fetchVoiceListings(LOCATION_ID),client.fetchDuplicateListings(LOCATION_ID),]); for (const l of premium) {if (l.syncStatus !== 'SYNCED') { console.log('Not synced: ' + l.site + ' - ' + l.syncStatus);}} console.log('Voice listings: ' + voice.length);console.log('Duplicates detected: ' + duplicates.length); const ids = duplicates.map((d) => d.id).filter((id): id is string => typeof id === 'string'); if (ids.length > 0) {await client.markListingsAsDuplicate(LOCATION_ID, ids);console.log('Marked ' + ids.length + ' listings as duplicate');}For an account-wide sweep, fetchAllDuplicateListings({ page: 1 }) returns a
rollup of duplicates across every location.
9. Connected accounts: the Google flow
Connect a Google account once, match its listings to your locations, and confirm the suggested matches.
import { ListingsAPI } from 'listingsapi-js'; const client = new ListingsAPI(); // 1. Get an OAuth URL and send the account owner thereconst connect = await client.connectGoogleAccount('https://example.com/connected','https://example.com/connect-error',);console.log('Send the owner to:', JSON.stringify(connect)); // 2. After the owner completes OAuth, list connected accountsconst accounts = await client.fetchConnectedAccounts({ page: 1, perPage: 25 });console.log(JSON.stringify(accounts)); // 3. Trigger matching between the account's listings and your locationsconst accountId = 'connected-account-uuid';await client.triggerConnectedAccountMatches([accountId]); const suggestions = await client.fetchConnectionSuggestions(accountId, {page: 1,perPage: 25,});console.log(JSON.stringify(suggestions)); // 4. Confirm the matches you agree withawait client.confirmConnectedAccountMatches(['match-record-id-1','match-record-id-2',]);console.log('Matches confirmed');To connect a single location instead, use
getOauthConnectUrl(locationId, 'GOOGLE', successUrl, errorUrl), and
connectListing(locationId, listingId, accountId) to link one listing
directly.
10. Search locations and update a field in bulk
Search for locations matching a query, then update a field on each result, for example pushing a new phone number across a city.
import { ListingsAPI } from 'listingsapi-js';import type { Location } from 'listingsapi-js'; const client = new ListingsAPI(); const matches = (await client.searchLocations('Brooklyn', { fetchAll: true })) as Location[]; let updated = 0;for (const loc of matches) {await client.updateLocation({ id: loc.id, phone: '7185550100',});updated++;}console.log('Updated phone on ' + updated + ' Brooklyn locations');Where to go next
- Locations: full method reference with parameter tables
- Reviews: analytics, settings, phrase analysis
- Listings: premium, voice, duplicates
- Analytics: Google, Bing, Facebook insights
- Error handling:
APIErrorsubclasses and retry strategy