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.

01_quickstart.ts
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.

02_bulk_export.ts
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.

03_onboarding.ts
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.

04_bulk_publish.ts
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 status
const 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.

05_review_monitoring.ts
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.

06_bulk_respond.ts
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.

07_analytics_report.ts
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.

08_listings_audit.ts
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.

09_connected_accounts.ts
import { ListingsAPI } from 'listingsapi-js';
 
const client = new ListingsAPI();
 
// 1. Get an OAuth URL and send the account owner there
const 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 accounts
const accounts = await client.fetchConnectedAccounts({ page: 1, perPage: 25 });
console.log(JSON.stringify(accounts));
 
// 3. Trigger matching between the account's listings and your locations
const 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 with
await 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.

10_bulk_update.ts
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: APIError subclasses and retry strategy