Google Business Profile接続・拠点取込

Google接続リンクを作成し、オーナーのGoogle掲載情報を自分の拠点と照合して対応関係を確定し、対応する拠点がまだない掲載情報をインポートします。

Google アカウントを接続すると、Google 上で拠点の情報を公開し、レポートを取得できます。アカウントを接続するまでは、拠点のプレミアム掲載情報にある Google Maps の行は REQUIRING_ACTION のままで、Google のレビューは取り込まれず、GOOGLE に投稿を公開できず、Google の分析データは null として返されます。アカウントを接続すると、この 4 つすべてが解消されます。

一括接続の手順(手順 1~6)では、Google アカウント全体を一度に接続し、そのアカウントが所有するすべての掲載情報を拠点と照合します。拠点ごとの接続手順(手順 7)では、1 つずつ拠点を接続します。リンクがその拠点に紐づいているため、その後の照合手順はありません。

リンクの対象範囲は API キーを所有するアカウントに限定されるため、リクエストボディに含めるのは 2 つのリダイレクト URL のみです。返された url に所有者を誘導してください。所有者が操作を完了すると、Google は所有者を successUrl または errorUrl に戻します。

Connect link
curl -X POST https://listingsapi.com/api/v4/connected-accounts/connect-google \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "successUrl": "https://app.example.com/connect/google/success",    "errorUrl": "https://app.example.com/connect/google/error"  }}'
JSON
{  "data": {    "bulkConnectLinkForGoogle": {      "success": true,      "url": "https://connect.example.com/oauth?token=8f3c1d92-...",      "errors": null    }  }}

上記の url はプレースホルダーです。ご自身のレスポンスで返された url に所有者を誘導してください。

リンクの有効期間は 24 時間です。success だけで判断せず、url が null でないことを確認してください。後で所有者の認証情報の有効期限が切れた場合は、同じ方法で新しいリンクを作成してください。既存の接続が更新され、重複する接続は作成されません。

2. OAuth から戻った後に接続済みアカウントを一覧表示する

所有者が successUrl に戻ったら、Google アカウントの一覧を取得します。この一覧はオフセット方式でページ分割されており、records[] と pageInfo.totalRecords を含み、page と perPage でページを指定します。

List accounts
curl "https://listingsapi.com/api/v4/connected-accounts?publisher=GoogleAccount&page=1&perPage=20" \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "data": {    "connectedAccountsInfo": {      "pageInfo": { "hasNextPage": false, "totalRecords": 1 },      "records": [        {          "connectedAccountId": "4f712c17-4f95-42dd-90f4-97171a2e67b5",          "email": "owner@brightsmile-dental.com",          "status": "CONNECTED",          "requestMatchesStatus": "MATCH_COMPLETED",          "connectedLocationsCount": 0        }      ]    }  }}

email が同意を完了した所有者であるレコードを選択し、その connectedAccountId を保持します。以降のすべての呼び出しで、この UUID を渡します。

status意味
CONNECTED認証情報は有効です。マッチングを開始しても問題ありません。
MATCH_IN_PROGRESSマッチング処理がキューに登録されているか、実行中です。
CONNECTIVITY_ISSUE認可の有効期限が切れています。理由は connectivityIssue に示されていますので、接続リンクを再発行してください。

省略可能な status クエリフィルターでは、異なる値(Connected、NotConnected、ConnectionIssue)を使用します。レコード自身の status をそのまま渡すと、400 が返されます。

3. マッチングを開始する

マッチングでは、アカウントがアクセスできるすべてのプロフィールを取得し、それぞれをご自身の拠点のいずれかと組み合わせます。生成されるのは候補のみで、この時点では何も紐づけられません。

Trigger matches
curl -X POST https://listingsapi.com/api/v4/connected-accounts/trigger-matches \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "connectedAccountIds": ["4f712c17-4f95-42dd-90f4-97171a2e67b5"] } }'

レスポンスは data.connectedAccountsTriggerMatches で、success と failedIds を含みます。success: true はジョブがキューに登録されたことを意味し、キューに登録できなかったアカウントは failedIds に一覧表示されます。

4. MATCH_COMPLETED になるまでポーリングする

上限を設けたループで、15 秒ごとにアカウントの詳細をポーリングします。この間隔であれば、Launch プランでも 1 分あたり 10 リクエストの制限内に収まります。

Poll details
curl https://listingsapi.com/api/v4/connected-accounts/4f712c17-4f95-42dd-90f4-97171a2e67b5/details \-H "Authorization: API $LISTINGSAPI_KEY"

レコードは data.connectedAccountDetails.details に返され、一覧内のレコードと同じフィールドを持ちます。requestMatchesStatus は、ジョブの実行中は MATCH_IN_PROGRESS、候補の準備ができると MATCH_COMPLETED になります。Completed は照合処理が完了したことを意味し、何かが紐づけられたことを意味するわけではありません。確定するまで、connectedLocationsCount は 0 のままです。

5. 候補を確認して確定する

各候補は、ご自身の拠点の 1 つ(locationInfo。その databaseId は locationId)と、Google の掲載情報 1 件(suggestedLocationInfo)の組み合わせです。両方の情報を担当者に表示するか、名前と市区町村が一致する場合に自動で承認します。

Suggestions
curl "https://listingsapi.com/api/v4/connected-accounts/4f712c17-4f95-42dd-90f4-97171a2e67b5/connection-suggestions?page=1&perPage=20" \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "data": {    "connectionSuggestionsForAccount": {      "pageInfo": { "hasNextPage": false, "totalRecords": 2 },      "records": [        {          "accountType": "GMB",          "matchedDataDatabaseId": "0d5e306f-0da0-46e3-929f-fd3c67ccd8cc",          "locationId": "73415",          "locationInfo": { "name": "My Business", "city": "Lincoln", "stateIso": "NE" },          "suggestedLocationInfo": { "locationInfo": { "name": "My Business", "city": "Lincoln" } }        }      ]    }  }}

候補には id がありません。候補を確定するには、accountType から選んだ型を使用し、<MatchRecordType>:<matchedDataDatabaseId> を base64 でエンコードして照合レコードの id を作成します。Google(GMB)の型は GmbLocationMatchedData なので、上記のレコードをエンコードすると R21iTG9jYXRpb25NYXRjaGVkRGF0YTowZDVlMzA2Zi0wZGEwLTQ2ZTMtOTI5Zi1mZDNjNjdjY2Q4Y2M= になります。Facebook(FB)では FbLocationMatchedData、Apple(APPLE)では AppleLocationMatchedData を使用します。

Confirm matches
# base64 of GmbLocationMatchedData:0d5e306f-0da0-46e3-929f-fd3c67ccd8cccurl -X POST https://listingsapi.com/api/v4/connected-accounts/confirm-matches \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "matchRecordIds": ["R21iTG9jYXRpb25NYXRjaGVkRGF0YTowZDVlMzA2Zi0wZGEwLTQ2ZTMtOTI5Zi1mZDNjNjdjY2Q4Y2M="] } }'

レスポンスは data.confirmConnectMatches で、success と failedIds を含みます。failedIds には、すでに使用済みのレコードや、マッチング後にアーカイブされた拠点など、確定できなかったもののデコード済み matchedDataDatabaseId 値が格納されます。誤った型で作成した id を渡しても 400 は返されず、呼び出しは success: false を含み failedIds を含まない 200 を返すため、毎回 success を確認してください。送信しなかった候補は変更されません。

6. 拠点がまだない掲載情報をインポートする

マッチングでは、掲載情報と既存の拠点のみを対応付けます。残りをインポートするには、アカウントから参照できるすべての掲載情報を一覧表示し、必要な掲載情報ごとに拠点を作成します。

Account listings
curl -X POST https://listingsapi.com/api/v4/connected-accounts/connected-account-listings \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "connectedAccountId": "4f712c17-4f95-42dd-90f4-97171a2e67b5", "perPage": 50 }'
JSON
{  "data": {    "connectedAccountListings": {      "pageInfo": { "hasNextPage": false, "totalRecords": 60 },      "records": [        {          "id": "R21iQnVsa0RhdGFMYWtlOjA2MDQ4NmExLTA0NzQtNDc4NS1iMjNhLTBkOGM4MzhmNGQ5OA==",          "locationName": "Trial",          "address": "123 William St, NY 10038, Manhattan, US",          "liveLink": null        }      ]    }  }}

掲載情報の多いアカウントでは、locationInfo に検索語を指定すると、名前、住所、電話番号、郵便番号、店舗コードで絞り込めます。各レコードの id は、次の 2 つの呼び出しで受け取る connectedAccountListingId です。どちらの SDK も create-location-from-listing のラッパーをまだ提供していないため、Python と Node のサンプルでは、同じヘッダーを使用して REST を直接呼び出します。

Create from listing
curl -X POST https://listingsapi.com/api/v4/connected-accounts/create-location-from-listing \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "connectedAccountId": "4f712c17-4f95-42dd-90f4-97171a2e67b5",    "connectedAccountListingId": "R21iQnVsa0RhdGFMYWtlOjA2MDQ4NmExLTA0NzQtNDc4NS1iMjNhLTBkOGM4MzhmNGQ5OA=="  }}'
JSON
{  "data": {    "createLocationFromConnectedAccountListing": { "success": true, "locationId": 88277, "errors": null }  }}

Google のデータ(名前、住所、営業時間、カテゴリ、電話番号、属性)から拠点が作成され、その場で元の掲載情報にリンクされます。Google の掲載情報は、拠点への最初の更新がその掲載情報に公開されるまで IN_PROGRESS のままです。locationId は、そのままの databaseId です。失敗すると、success は false となり、errors には SY81067(掲載情報の id に対応する掲載情報が見つからなかった)などのコードが含まれます。フィールド名は connectedAccountListingId です。listingId はスキーマで拒否されます。

掲載情報が既存の拠点に属している場合は、重複する拠点を作成せず、その掲載情報をリンクします。

Link a listing
curl -X POST https://listingsapi.com/api/v4/connected-accounts/connect-listing \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "locationId": "TG9jYXRpb246MTgwMDI4OQ==",    "connectedAccountId": "4f712c17-4f95-42dd-90f4-97171a2e67b5",    "connectedAccountListingId": "R21iQnVsa0RhdGFMYWtlOjA2MDQ4NmExLTA0NzQtNDc4NS1iMjNhLTBkOGM4MzhmNGQ5OA=="  }}'

リンクに失敗すると、success: false とともに、Listing record not found などの message、または掲載情報がすでに別の拠点に接続されていることを示すメッセージが返されます。

7. 代わりに単一の拠点を接続する

オーナーがどの拠点を接続するかがすでにわかっている場合は、マッチングを省略し、その拠点に紐づくリンクを作成します。成功すると、オーナーは指定した successUrl に戻り、認証情報はその拠点にのみ紐づけられます。

Single-location link
curl -X POST https://listingsapi.com/api/v4/locations/oauth_connect_url \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "locationId": "TG9jYXRpb246MTgwMDI4OQ==",    "site": "GOOGLE",    "successUrl": "https://app.example.com/locations/1800289/connected",    "errorUrl": "https://app.example.com/locations/1800289/connect-failed"  }}'

レスポンスは data.createConnectUrl で、success、url、errors が含まれます。リンクは 1 回限り有効なので、試行ごとに新しいリンクを作成します。リダイレクト URL が相対 URL または解析できない URL の場合は、null url を伴う success: true が返されるため、url を確認してください。この拠点へのアクセス権限がないキーを使用すると、SY90003 を伴う 403 が返されます。

8. 接続後の動作

掲載情報をリンクすると、拠点のプレミアム掲載情報にある Google Mapsの行は REQUIRING_ACTION から IN_PROGRESS に移行し、最終的に listingUrl を伴う SYNCED になります。Google はレビューの取得元として自動的に追加されるため、インタラクション一覧にレビューが届き始め、拠点に加えた更新はプロフィールに反映されます。

ダッシュボードで Webhook URL を設定すると、ポーリングは不要です。各ペイロードには location_id と data.connected_account_id が含まれます。

イベント発生条件対応
connection.location_connected掲載情報が拠点にリンクされ、data.platform が google になります。UI で拠点を接続済みとして表示します。
connection.reauth_requiredアカウントの認証情報が無効になりました。再認可されるまで、何も同期されません。新しいリンクを作成し(手順 1)、オーナーに再度サインインするよう依頼します。
connection.listing_inaccessible認証情報は有効ですが、その 1 件の掲載情報へのアクセス権限が削除されました。オーナーに Google でアクセス権限を復元するよう依頼するか、別の掲載情報をリンクします。

次の手順