ユースケース

listingsapi-jsのリソースを組み合わせて実際の連携を実装する、一連の手順を示したTypeScriptの実装例です。

これらの実装例は、複数のリソースを組み合わせたワークフローであり、単一のメソッドの説明ページだけでは対応できない場合に使うスクリプトです。それぞれが、2つ以上のリソースを組み合わせ、実際にデプロイできる完全な実行可能なTypeScriptプログラムになっています。バージョンを固定したSDK(npm install listingsapi-js@0.3.2)をインストールし、ダッシュボードで取得したキーを使ってLISTINGSAPI_KEYを環境変数としてエクスポートすれば、npx tsx <file>.tsでそのまま実行できます。

SDKを初めて使う場合は、まずNode SDKの概要をお読みください。以下のすべての実装例で前提となる、クライアントの作成、認証、エラー、ページネーションについて説明しています。

1. クイックスタート:拠点を取得して表示する

最小限の連携として、認証を行い、拠点を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);

検証に失敗すると(必須フィールドの欠落、説明の文字数不足、不正なカテゴリ)、プラットフォームのエラー項目をerr.errorsに格納したValidationErrorがスローされるため、確認するためのsuccessフラグはありません。

4. GoogleとFacebookに投稿を一括公開する

bulkPublishは、1回の呼び出しで、多数の拠点について両方のサイトに1つの投稿を公開します。クライアント側でペイロードを検証し、サイトごとにメッセージを展開し、数値の拠点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、およびレビューの統計

1つの拠点のプロフィール分析データとレビュー統計を並列に取得し、1ページの要約を表示します。

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. 掲載情報の監査:未同期のプロフィールと重複するプロフィールを見つける

1つの拠点について、同期されていないプレミアム掲載情報と、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');

代わりに1つの拠点を接続する場合はgetOauthConnectUrl(locationId, 'GOOGLE', successUrl, errorUrl)を使い、1つの掲載情報を直接紐付けるにはconnectListing(locationId, listingId, accountId)を使います。

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のサブクラスと再試行の方針