使用场景
端到端 TypeScript 示例,展示如何组合使用 listingsapi-js 资源来实现实际集成。
这些示例展示了跨资源工作流,适用于单个方法的文档无法满足需求、需要编写脚本的情况。每个示例都是完整且可运行的 TypeScript 程序,将 2 个或更多资源组合起来,可用于实际部署。安装固定版本的 SDK(npm install listingsapi-js@0.3.2),使用您从控制台获取的密钥导出环境变量 LISTINGSAPI_KEY,即可通过 npx tsx <file>.ts 原样运行这些程序。
如果您刚开始使用 SDK,请先阅读 Node SDK 概述。其中介绍了客户端构建、身份验证、错误和分页,以下所有示例都以您了解这些内容为前提。
1. 快速入门:获取并打印地点
最小可用集成:完成身份验证,获取一页地点,并打印其名称。
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);}}未使用 fetchAll 时,fetchAllLocations 返回的对象包含 locations、pageInfo 和 raw 数据。将 pageInfo.endCursor 作为 after 传入,即可获取下一页。
2. 批量导出为 JSON
自动分页获取账户中的所有地点,并将结果写入磁盘。
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 会自动按游标分页获取数据,并返回扁平化的 Location[]。对于超过 10 000 个地点的情况,请将 pageSize 保持为 100(默认值),以确保每次请求都能快速完成。
3. 添加新地点
使用 fetchSubcategories 查找正确的类别 ID,然后创建地点。createLocation 的 subCategoryId 参数需要传入子类别的 databaseId。API 还要求提供至少 200 个字符的描述。
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);验证失败(缺少字段、描述过短或类别无效)时,会抛出 ValidationError,其 err.errors 中包含平台返回的错误条目,因此没有可供检查的 success 标志。
4. 批量向 Google 和 Facebook 发布帖子
bulkPublish 通过一次调用,即可将同一篇帖子发布到多个地点在这 2 个站点上的页面。它会在客户端验证请求数据,为每个站点生成相应的消息,并为您编码数字形式的地点 ID。
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 默认为 ['GOOGLE', 'FACEBOOK']。如果不同站点需要使用不同文案,请将按站点配置的映射作为 message({ GOOGLE: '...', FACEBOOK: '...' })传入。对于使用带类型选项的单个营销活动,请改用 createAnnouncement、createEvent 或 createOffer。
5. 评价监控:标记低评分
扫描每个地点的 1 星和 2 星评价,并记录需要关注的评价。
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. 回复尚未回复的评价
自动回复每条尚未收到回复的评价。您可以使用模板,也可以将 buildResponse 替换为 LLM 调用。
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. 分析报告:Google、Bing、Facebook 和评价统计
并行获取单个地点的商家资料分析数据和评价统计数据,并打印一页摘要。
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));请注意参数的区别:平台分析使用 fromDate/toDate,评价分析则使用 startDate/endDate。
8. 商家信息审查:查找未同步和重复的商家资料
检查单个地点中未同步的高级商家信息,以及 ListingsAPI 发现的重复商家资料,然后标记重复项。
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');}对于账户范围内的检查,fetchAllDuplicateListings({ page: 1 }) 会返回该账户所有地点的重复项汇总。
9. 已关联账户:Google 关联流程
关联一次 Google 账户,将其中的商家信息与您的地点匹配,然后确认建议的匹配结果。
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');如果您只需关联单个地点,请使用 getOauthConnectUrl(locationId, 'GOOGLE', successUrl, errorUrl),并通过 connectListing(locationId, listingId, accountId) 直接关联一条商家信息。
10. 搜索地点并批量更新字段
搜索符合查询条件的地点,然后更新每个结果中的某个字段,例如为某个城市中的所有匹配地点更新电话号码。
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');