使用案例

完整的 TypeScript 實作範例,說明如何結合 listingsapi-js 資源,建立實際的整合應用。

這些範例示範跨資源的工作流程,適合在單一方法的說明頁不足以滿足需求時使用。每個範例都是完整且可執行的 TypeScript 程式,整合 2 個以上的資源,可供實際部署。安裝固定版本的 SDK(npm install listingsapi-js@0.3.2),使用您從儀表板取得的金鑰匯出環境變數 LISTINGSAPI_KEY,即可透過 npx tsx <file>.ts 直接執行這些程式。

如果您剛開始使用 SDK,請先閱讀 Node SDK 概覽。其中涵蓋用戶端建立、驗證、錯誤與分頁,下列每個範例都假設您已瞭解這些內容。

1. 快速入門:取得並列印地點

最基本的整合:完成驗證、擷取一頁地點,並列印其名稱。

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);}}

未使用 fetchAll 時,fetchAllLocations 會傳回包含 locations、pageInfo 與 raw 酬載的物件。將 pageInfo.endCursor 作為 after 傳入,即可取得下一頁。

2. 批次匯出為 JSON

自動分頁擷取帳戶內的所有地點,並將結果寫入磁碟。

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 會自動依游標逐頁擷取,並傳回扁平的 Location[]。若有 10 000 個以上的地點,請將 pageSize 維持為 100(預設值),讓個別請求保持快速。

3. 設定新地點

使用 fetchSubcategories 查詢正確的類別 ID,然後建立地點。createLocation 的 subCategoryId 預期接收子類別的 databaseId。API 也要求提供至少 200 個字元的描述。

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);

驗證失敗(缺少欄位、描述過短、類別無效)時,會擲回 ValidationError,其 err.errors 包含平台的錯誤項目,因此沒有可供檢查的 success 旗標。

4. 批次將貼文發布至 Google 與 Facebook

bulkPublish 只需呼叫 1 次,即可將同一篇貼文發布到多個地點在這 2 個網站上的頁面。它會在用戶端驗證酬載、為各網站產生訊息內容,並為您編碼數字形式的地點 ID。

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 statusconst bulk = await client.fetchBulkPost(result.socialPost.id);console.log(bulk);

sites 預設為 ['GOOGLE', 'FACEBOOK']。若各網站的文案應有所不同,請將各網站的對應表作為 message({ GOOGLE: '...', FACEBOOK: '...' })傳入。若要為單一活動使用具型別的選項,請改用 createAnnouncement、createEvent 或 createOffer。

5. 評論監控:標記低評分

掃描每個地點的 1 星與 2 星評論,並記錄需要關注的評論。

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. 回覆尚未回覆的評論

自動回覆每則尚未收到回覆的評論。使用範本,或將 buildResponse 替換為 LLM 呼叫。

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. 分析報告:Google、Bing、Facebook 與評論統計資料

平行擷取單一地點的商家檔案分析資料與評論統計資料,並列印一頁摘要。

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));

請注意參數的差異:平台分析使用 fromDate/toDate,評論分析則使用 startDate/endDate。

8. 商家資訊稽核:找出未同步與重複的商家檔案

檢查單一地點中未同步的進階商家資訊,以及 ListingsAPI 找到的重複商家檔案,然後標記重複項目。

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');}

若要檢查整個帳戶,fetchAllDuplicateListings({ page: 1 }) 會傳回帳戶內所有地點的重複項目彙總。

9. 已連結帳戶:Google 流程

連結 Google 帳戶 1 次,將其商家資訊與您的地點配對,並確認建議的配對。

09_connected_accounts.ts
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) 直接連結 1 筆商家資訊。

10. 搜尋地點並批次更新欄位

搜尋符合查詢條件的地點,然後更新每筆結果中的欄位,例如將某個城市內的所有符合條件地點更新為新的電話號碼。

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');

後續閱讀

  • 地點:完整的方法參考文件,含參數表
  • 評論:分析、設定、詞句分析
  • 商家資訊:進階商家資訊、語音商家資訊、重複項目
  • 分析:Google、Bing、Facebook 洞察資料
  • 錯誤處理:APIError 子類別與重試策略