listings scanで事業者の掲載情報を監査
任意の事業者が地図、検索エンジン、ディレクトリ、AIアシスタントでどのように表示されるかを確認し、ディレクトリごとに名前、住所、電話番号の正確性を確認して、Webhookまたはポーリングで結果を取得します。
listings scan は、1つの事業者がlistingsAPIのディレクトリネットワーク全体でどのように掲載されているかを確認し、各ディレクトリで掲載情報が見つかったかどうかと、名称、住所、電話番号が指定した情報と一致するかどうかを示します。事業者がアカウント内の拠点として登録されている必要はありません。このガイドでは、スキャンを作成し、完了を待って結果を確認します。
次の用途に使用できます。
- 導入前の見込み顧客の掲載状況を調査し、掲載情報の不足や誤りを最初の打ち合わせの議題にします。
- 顧客の掲載情報を修正する前後で、掲載状況とNAP(名称、住所、電話番号)の一貫性を確認します。
- 事業者をアカウントに追加せずに、AIエージェントが「この事業者はどのように掲載されていますか」という質問に回答できるようにします。
1. スキャンを作成する
POST /listings/scansは、事業者の情報を単一のinputオブジェクトとして受け取り、QUEUEDの状態のスキャンを即座に返します。ディレクトリの確認はバックグラウンドで実行されます。
curl -X POST https://listingsapi.com/api/v4/listings/scans \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d @- <<'JSON'{"input": { "name": "Joe's Pizza", "street": "7 Carmine Street", "city": "New York", "stateIso": "NY", "postalCode": "10014", "countryIso": "US", "phone": "2123661182"}}JSONレスポンスでは、phoneが国際形式に正規化されたスキャンが返されます。ディレクトリの確認が行われるまでresultsは空なので、idを保存しておいてください。
{ "data": { "createListingScan": { "reused": false, "scan": { "id": "TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz", "status": "QUEUED", "source": "DIRECTORY_SEARCH", "locationId": null, "resultsAsOf": null, "createdAt": "2026-11-05T07:01:08Z", "startedAt": null, "completedAt": null, "business": { "name": "Joe's Pizza", "street": "7 Carmine Street", "city": "New York", "stateIso": "NY", "postalCode": "10014", "countryIso": "US", "phone": "+12123661182" }, "summary": { "total": 0, "found": 0, "accurate": 0, "inaccurate": 0, "notFound": 0, "pending": 0, "unavailable": 0 }, "failure": null, "results": [] }, "errors": [] } }}レスポンスにはquotaオブジェクトも含まれます。詳細は料金、制限、エラーで説明します。
先に進む前に、次の2点を確認してください。
reused。 過去7日間にスキャンした事業者を指定すると、listingsAPIは新しいスキャンを開始せずに、既存のスキャンをreused: trueと現在の状態(通常はCOMPLETED)とともに返します。利用可能回数は消費されず、そのスキャンについて新たなWebhookも送信されません。errors。 リクエストが拒否された場合も、HTTP200が返されます。拒否の詳細はdata.createListingScan.errorsに含まれ、scanはnullになります。電話番号が短すぎる場合は、次のようになります。
{ "data": { "createListingScan": { "reused": false, "scan": null, "errors": [ { "code": "SY83003", "message": "SY83003: Invalid value for phone", "contextInfo": [ { "key": "field", "value": "phone" } ] } ] } }}contextInfoは、検証に失敗したフィールドの名前を示します。返される可能性があるその他のコードは、エラー一覧に記載されています。
2. 結果を待つ
スキャンの状態は、QUEUEDからIN_PROGRESSへ、その後COMPLETEDまたはFAILEDへと変わります。ほとんどのスキャンは1〜7分で完了します。約15分経過しても応答しないディレクトリがある場合、そのディレクトリの結果はUNAVAILABLEに設定され、他の結果とともにスキャンが完了します。可能であればWebhookを使用し、ポーリングは代替手段としてのみ使用してください。
2a. Webhookを受信する(推奨)
スキャンが最終状態に達すると、listingsAPIはlisting_scan.completedまたはlisting_scan.failedのイベントを1件、Webhook URLに送信します。そのdata.scanは、結果を含めてGETが返すオブジェクトと同じなので、後から取得する必要はありません。
{ "event": "listing_scan.completed", "timestamp": "2026-11-05T07:01:52Z", "account_id": 12345, "data": { "scan": { "id": "TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz", "status": "COMPLETED", "summary": { "total": 62, "found": 21, "accurate": 18, "inaccurate": 2, "notFound": 41, "pending": 0, "unavailable": 0 }, "results": [ "..." ] } }}ここではペイロードを省略して示しています。完全な例はイベントリファレンスにあります。イベントは他のすべてのイベントと同様に署名され、制限時間5秒で1回だけ配信が試行されるため、他の処理を行う前に署名を検証し、生のボディを保存して、受信確認を返してください。Webhookでイベントに対応するの受信処理には、これらの処理がすでに実装されています。必要な変更は、eventの分岐を1つ追加することだけです。
# After the signature check and the endpoint.verification branch:if body.get("event") in ("listing_scan.completed", "listing_scan.failed"): inbox.put(raw) # commit before the 200; read data.scan from the queue return {"received": True}, 200イベントは、Webhookが有効で、URLが検証済みのアカウントにのみ送信されます。アカウントがこの条件を満たさない場合やイベントが届かない場合は、スキャンをポーリングしてください。どちらの場合も、スキャンは90日間保持されます。署名の詳細は署名と検証を参照してください。
2b. 代替手段としてポーリングする
GET /listings/scans/{scanId}は、その時点で得られている結果を含むスキャンを返します。スキャンの実行中は、ディレクトリから応答があるたびにresultsが埋まり、summary.pendingは応答待ちのディレクトリ数を示します。ポーリングは15秒間隔で行い、それより短い間隔では行わないでください。各リクエストはレート制限の対象になります。
SCAN_ID="TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz" while true; doscan=$(curl -s "https://listingsapi.com/api/v4/listings/scans/$SCAN_ID" \ -H "Authorization: API $LISTINGSAPI_KEY")status=$(echo "$scan" | jq -r '.data.listingScan.status')echo "status: $status"case "$status" in COMPLETED|FAILED) echo "$scan" | jq '.data.listingScan | {status, summary, failure}' break ;; null) echo "$scan" break ;;esacsleep 15done最近のスキャンまたは自身の拠点のいずれかのデータを使用して結果を返すスキャンは、作成時点でCOMPLETEDになるため、GETを1回実行するだけで十分です。
3. 結果を読む
完了したスキャンにはsummaryがあり、resultsには確認したディレクトリごとに1件のエントリが含まれます。エントリ数は国によって異なり、最大で約60件です。businessは、送信した入力を、phoneを国際形式に正規化して返します。
{ "total": 62, "found": 21, "accurate": 18, "inaccurate": 2, "notFound": 41, "pending": 0, "unavailable": 0}totalはディレクトリの総数です。foundはビジネスを掲載しているディレクトリ数で、その内訳はaccurate、inaccurate、および後述する掲載の有無のみを確認できる掲載情報です。notFoundはビジネスを掲載していないディレクトリ数です。unavailableは今回確認できなかったディレクトリ数を示し、pendingが0以外になるのはスキャンの実行中のみです。
各結果には、そのディレクトリのstatusが含まれます。
FOUNDの結果では、accurateが掲載情報と入力の一致 여부を示します。
trueは、名前、住所、電話番号がすべて一致していることを意味します。falseは、少なくとも1つが一致していないことを意味します。掲載情報にないフィールドも不一致として扱うため、電話番号のない掲載情報はfalseになります。nullは、ビジネスは見つかったものの、フィールドごとの確認は行われていないことを意味します。
accuracyは、name、address、phoneのそれぞれについてstatusとmessageで判定内容を説明します。statusはACCURATE、INACCURATE、MISSINGのいずれかで、フィールドが正確な場合、messageはnullです。以下は、名前と住所は一致していますが、電話番号がないFacebookページの例です。
{ "directory": { "id": 184, "name": "Facebook", "url": "facebook.com" }, "status": "FOUND", "accurate": false, "accuracySource": "DIRECTORY_MATCH", "accuracy": { "name": { "status": "ACCURATE", "message": null }, "address": { "status": "ACCURATE", "message": null }, "phone": { "status": "MISSING", "message": "No phone number on the listing" } }, "listing": { "name": "Joe's Pizza", "street": "7 Carmine St", "city": "New York", "state": "NY", "postalCode": "10014", "country": "US", "fullAddress": "7 Carmine St, New York, NY 10014", "phones": [], "url": "https://www.facebook.com/177353602413952" }, "syncStatus": null}一部のディレクトリでは、ビジネスの存在のみを確認でき、比較に必要な詳細情報は表示されません。これらはFOUNDとして返され、accurate: nullが設定されます。また、foundにのみ計上され、accurateやinaccurateには計上されません。検証済みではなく、掲載ありとして扱ってください。
{ "directory": { "id": 374, "name": "Map Quest", "url": "www.mapquest.com" }, "status": "FOUND", "accurate": null, "accuracySource": "DIRECTORY_MATCH", "accuracy": { "name": { "status": "ACCURATE", "message": null }, "address": { "status": "MISSING", "message": "Not shown" }, "phone": { "status": "MISSING", "message": "Not shown" } }, "listing": { "name": "Joe's Pizza", "street": "7 Carmine St", "city": "New York", "state": "NY", "postalCode": "10014", "country": null, "fullAddress": "7 Carmine St, New York, NY 10014, US", "phones": [], "url": null }, "syncStatus": null}NOT_FOUNDの結果には、listingもaccuracyもありません。
{ "directory": { "id": 352, "name": "YellowPages", "url": "www.yellowpages.com" }, "status": "NOT_FOUND", "accurate": null, "accuracySource": "DIRECTORY_MATCH", "accuracy": null, "listing": null, "syncStatus": null}独自のデータを保持していないディレクトリもあります。Uber、Waze、LyftはGoogle Mapsのデータを表示し、CopilotはBingのデータを表示します。これらの結果は参照元のディレクトリを反映するため、通常はGoogle MapsやBingが返したものと同じ内容になり、掲載情報のurlはありません。summaryでは個別のエントリとして数えますが、独立した確認結果とは解釈しないでください。
{ "directory": { "id": 350, "name": "Uber", "url": "uber.com" }, "status": "FOUND", "accurate": true, "accuracySource": "DIRECTORY_MATCH", "accuracy": { "name": { "status": "ACCURATE", "message": null }, "address": { "status": "ACCURATE", "message": null }, "phone": { "status": "ACCURATE", "message": null } }, "listing": { "name": "Joe's Pizza", "street": "7 Carmine Street", "city": "New York", "state": "NY", "postalCode": "10014", "country": "US", "fullAddress": "7 Carmine St, New York, NY 10014, USA", "phones": [ "+1 212-366-1182" ], "url": null }, "syncStatus": null}ChatGPTやGeminiなどのAIアシスタントも含まれます。これらの場合、listingにはアシスタントが把握しているビジネスの情報が表示されますが、多くの場合は名前、完全な住所、電話番号のみで、フィールドごとのaccuracyはありません。
完了したスキャンから修正リストを作成するには、誤っているフィールドや欠けているフィールドがあるディレクトリをすべて出力します。
curl -s https://listingsapi.com/api/v4/listings/scans/TGlzdGluZ1NjYW46Mzk0YTRhNWMtZjcwMy00NjZiLTg2NjAtMDU4MTlmNDhkZjMz \-H "Authorization: API $LISTINGSAPI_KEY" \| jq -r '.data.listingScan.results[] | select(.accurate == false) | .directory.name as $directory | .accuracy | to_entries[] | select(.value.status != "ACCURATE") | $directory + " " + .key + ": " + (.value.message // .value.status)'Joe's Pizzaのスキャンでは、Facebookに電話番号がないことと、Judys Bookに異なる電話番号が表示されていることを示す2行が出力されます。
Facebook phone: No phone number on the listingJudys Book phone: Phone number does not match結果の取得元
sourceはスキャン結果の取得方法を示し、resultsAsOfはデータが収集された日時を示します。
料金、制限、エラー
月間利用枠を消費するのは、新たに実行するDIRECTORY_SEARCHスキャンのみです。この利用枠はプランによって異なり、請求期間ごとにリセットされます。以下は無料です。
- 7日以内に同じビジネスを再度スキャンする場合。自分が以前に実行したスキャンが
reused: trueとともに返されます。 - いずれかのアカウントが同じビジネスに対して最近実行したスキャンの結果を利用できる場合。これは
RECENT_SCANのコピーです。 - 自分の拠点のいずれかと一致するスキャン(
LOCATION_LISTINGS)。 FAILEDで終了するスキャン。この場合、消費した利用枠は返還されます。
スキャンは90日間保存されます。その後、GETはSY83005とともに404を返します。
作成と一覧取得のレスポンスにはquotaオブジェクトが含まれるため、バッチ処理を開始する前に利用状況を確認できます。一覧取得では、perPageを使用し、resultsを使用せずに、1ページあたり1〜50件のスキャンを新しい順に返します。
curl -s "https://listingsapi.com/api/v4/listings/scans?page=1&perPage=20" \-H "Authorization: API $LISTINGSAPI_KEY" \| jq '.data.listingScans.quota'以下は、よく発生するエラーです。作成ではHTTP 200とともにレスポンスボディにエラーが返され、取得と一覧取得では通常のHTTPステータスが返されます。
存在しないidに対するGETは、次の結果を返します。
{ "data": { "listingScan": null }, "errors": [ { "message": "SY83005: Listing scan not found" } ]}FAILEDで終了したスキャンでは、理由がfailureに格納され、resultsは空のままです。
{ "status": "FAILED", "failure": { "code": "SY83010", "message": "SY83010: Listing scans are temporarily unavailable, try again later", "field": null }}一般的なエラー形式はエラーコードに記載されています。
AIエージェント向け
ホスト型MCPサーバーは、この機能をlistings_scans_create、listings_scans_get、listings_scans_listの3つのツールとして提供します。アシスタントは、ユーザーが指定したビジネスのスキャンを開始し、listings_scans_getでポーリングして、対応が必要なディレクトリをまとめることができます。RESTと同様に、スキャンの作成にはWriteキーが必要です。設定方法はAIエージェントをlistingsAPI MCPに接続するに記載されています。
次の手順
- Webhookでイベントに対応する:スキャンイベントを受け取る受信側の処理について説明します。
- 掲載情報のステータスと公開リンクを確認するおよび掲載情報のエラーを見つけて修正する:ビジネスをアカウント内の拠点として登録した後、その掲載情報を追跡して修正します。
- 拠点を追加する:監査したビジネスを登録します。
- リファレンス:listings scanを作成する、listings scanを取得する、listings scansの一覧を取得する、イベントリファレンス、エラーコード。