地點
依使用順序介紹所有地點端點,每個端點均附有 cURL、Python 和 Node 範例,涵蓋列出、搜尋、依 id 和門市代碼查詢、建立、更新、封存及相片管理。
商家資訊、評論、貼文和分析資料皆以地點 ID 作為索引鍵。地點資源共有 13 個端點:4 個用於尋找地點的讀取端點、4 個用於建立、變更和封存地點的寫入端點,以及 5 個用於管理相片的端點。Read(讀取)金鑰可用於讀取端點。每個 POST 都需要 Write(寫入)金鑰;使用 Read 金鑰呼叫寫入端點時,端點會傳回 HTTP 400,並附上 SY90016。
本指南中的每個 id 都是 base64 編碼的 Relay id,例如 TG9jYXRpb246MTgwMDI4OQ==(解碼後為 Location:1800289)。兩個 SDK 也都接受數字形式的 databaseId,並會為您編碼。
端點一覽
列出所有地點
GET /locations 會分頁列出帳戶中的所有地點,從最新的開始。它採用游標連線:edges[].node 包含各筆記錄,而 pageInfo.endCursor 則作為下一頁的 after 參數值。使用此端點可將整個帳戶的資料同步至您自己的系統。
參數
curl "https://listingsapi.com/api/v4/locations?first=50" \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "allLocations": { "edges": [ { "cursor": "TG9jYXRpb246MTgwMDI4OQ==", "node": { "id": "TG9jYXRpb246MTgwMDI4OQ==", "databaseId": 1800289, "name": "Jenny Home", "storeId": null, "approved": "APPROVED", "archived": false, "archivalScheduledAt": null, "city": "Austin", "stateIso": "TX", "countryIso": "US", "subCategoryId": 1432, "planName": "Launch", "placeActionLinks": [], "discoveredPlaceActionLinks": [] } } ], "pageInfo": { "hasNextPage": false, "hasPreviousPage": false, "endCursor": "TG9jYXRpb246MTgwMDI4OQ==", "total": 1 } } }}讀取 pageInfo.hasNextPage,並在其值為 false 時停止。該頁的筆數為 pageInfo.total。節點的 id 是其他所有端點所需的識別碼。
搜尋地點
GET /locations/search 會以關鍵字比對地點名稱、街道地址、城市和商店 id。它採用游標連線,edges[].node 和 pageInfo.endCursor 的結構與列出端點相同。您可將它用於輸入時即顯示建議的選擇器,或在建立商店前確認商店是否已存在。
參數
兩個 SDK 也都接受 fields 清單,將比對範圍限制在特定欄位,例如僅比對 name。
curl "https://listingsapi.com/api/v4/locations/search?query=Jenny&first=10" \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "searchLocations": { "edges": [ { "cursor": "TG9jYXRpb246MTgwMDI4OQ==", "node": { "id": "TG9jYXRpb246MTgwMDI4OQ==", "databaseId": 1800289, "name": "Jenny Home", "storeId": null, "street": "600 Congress Ave, Suite 200", "city": "Austin", "approved": "APPROVED", "archived": false } } ], "pageInfo": { "endCursor": "TG9jYXRpb246MTgwMDI4OQ==", "hasNextPage": false, "hasPreviousPage": false, "total": 1 } } }}空的 edges 陣列表示沒有符合的結果。節點包含完整地址、類別和 approved 狀態,因此每筆結果都不需要再次查詢。
依 ID 取得地點
GET /locations-by-ids 可在一次呼叫中傳回一批 base64 ID 對應的完整記錄,比逐一呼叫每個地點所需的請求更少。啟用中與已封存的地點都能查得。它會傳回一般陣列,而非游標連線。您可用它取得 webhook 承載資料或您自己資料庫中各個 ID 對應的完整資料。
參數
curl -G https://listingsapi.com/api/v4/locations-by-ids \--data-urlencode 'ids=["TG9jYXRpb246MTgwMDI4OQ==","TG9jYXRpb246MTgwMDI5MA=="]' \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "getLocationsByIds": [ { "id": "TG9jYXRpb246MTgwMDI4OQ==", "databaseId": 1800289, "storeId": null, "name": "Jenny Home", "archived": false, "archivedAt": null, "city": "Austin", "stateIso": "TX", "countryIso": "US", "subCategoryId": 1432, "businessHours": [], "locationPhotos": [], "placeActionLinks": [], "discoveredPlaceActionLinks": [] } ] }}您帳戶中不存在的 ID 會直接略過,不會引發錯誤。請比較傳回的數量與您送出的數量,以找出過期的 ID。
依門市代碼取得地點
GET /locations-by-store-codes 提供反向查詢:它會依您自己的門市代碼查出完整記錄。它會傳回一般陣列。如果您的 POS 或加盟店名冊以門市代碼作為地點的索引鍵,而您從未儲存我們的 ID,即可使用此功能。
參數
curl -G https://listingsapi.com/api/v4/locations-by-store-codes \--data-urlencode 'storeCodes=["ACME01","ACME02"]' \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "getLocationsByStoreCodes": [ { "id": "TG9jYXRpb246MTgwMDI4OQ==", "databaseId": 1800289, "storeId": "ACME01", "name": "Jenny Home", "archived": false, "city": "Austin", "countryIso": "US", "planName": "Launch" } ] }}請使用每個物件的 storeId,對應回您名冊中的項目。找不到相符項目的代碼不會出現在結果中。只有在建立或更新時設定了 storeId,地點才會有門市代碼。
建立地點
POST /locations 會建立地點,並將其加入佇列,準備提交至您方案中的每個發布平台;若您傳入 enabledSiteIds 欄位,則只會提交至該欄位指定的發布平台。所有欄位都放在 input 之下。共有 10 個必填欄位,其餘為選填,您省略的欄位在發布平台上會顯示為空白。
參數
另外還有參考頁面列出的選填欄位:Google GCID 類別、社群網址、座標、擁有者聯絡資訊、付款方式、服務區域等。
curl -X POST https://listingsapi.com/api/v4/locations \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "name": "Acme Kitchen", "description": "Acme Kitchen is a family-owned neighborhood restaurant in downtown New York serving wood-fired pizza, hand-rolled pasta, and a short seasonal menu built around produce from nearby growers. Our dining room seats forty, takeout and delivery run until close, and the bar pours regional wine and beer on tap.", "storeId": "ACME01", "street": "123 Jump Street", "city": "New York", "stateIso": "NY", "postalCode": "10013", "countryIso": "US", "phone": "6443859313", "bizUrl": "https://acmekitchen.example.com", "subCategoryId": 1432, "businessHours": [ { "day": "MONDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "TUESDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "WEDNESDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "THURSDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "FRIDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "SATURDAY", "type": "OPEN", "slots": [{ "start": "10:00am", "end": "02:00pm" }] }, { "day": "SUNDAY", "type": "CLOSED", "slots": [] } ], "placeActionLinks": [ { "placeActionType": "FOOD_ORDERING", "uri": "https://order.acmekitchen.example.com", "isPreferred": true } ] }}'{ "data": { "createLocation": { "clientMutationId": null, "success": true, "errors": null, "location": { "id": "TG9jYXRpb246MTgwMDI5MA==", "databaseId": 1800290, "storeId": "ACME01", "name": "Acme Kitchen", "city": "New York", "countryIso": "US", "status": "PENDING", "placeActionLinks": [ { "placeActionType": "FOOD_ORDERING", "uri": "https://order.acmekitchen.example.com", "isPreferred": true, "name": null, "submissionStatus": null } ], "discoveredPlaceActionLinks": [] } } }}請保留這兩個 ID:base64 格式的 id 是後續每次寫入所需的值,而 databaseId 則是您在儀表板和 webhook 中看到的數字。status 的初始值為 PENDING,之後會持續更新,無須再次呼叫 API。地點操作連結的 name 和 submissionStatus 會維持為 null,直到該連結同步至 Google。Python 的 locations.add() 會在本機驗證必填欄位,並在發出任何網路呼叫之前拋出 ValidationError;locations.create(input) 則接受原始的 camelCase dict。
更新地點
POST /locations/update 採用部分更新:input.id 是唯一的必填欄位,只有您傳送的欄位會變更。同一個呼叫可用來修正電話號碼或重寫每週時間表。Python 的 locations.update() 和 Node 的 updateLocation() 都接受一個 camelCase 物件。
參數
另可包含參考頁面列出的選填欄位。
curl -X POST https://listingsapi.com/api/v4/locations/update \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "id": "TG9jYXRpb246MTgwMDI4OQ==", "phone": "5125550199", "bizUrl": "https://acmekitchen.example.com", "primaryGbpSiteCategoryId": "gcid:restaurant", "businessHours": [ { "day": "MONDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "TUESDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "WEDNESDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "THURSDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "FRIDAY", "type": "OPEN", "slots": [{ "start": "09:00am", "end": "05:00pm" }] }, { "day": "SATURDAY", "type": "OPEN", "slots": [{ "start": "10:00am", "end": "02:00pm" }] }, { "day": "SUNDAY", "type": "CLOSED", "slots": [] } ] }}'{ "data": { "updateLocation": { "clientMutationId": null, "success": true, "errors": null, "location": { "id": "TG9jYXRpb246MTgwMDI4OQ==", "databaseId": 1800289, "name": "Jenny Home", "phone": "5125550199", "status": "APPROVED", "placeActionLinks": [ { "placeActionType": "FOOD_ORDERING", "uri": "https://order.example.com", "isPreferred": true, "name": null, "submissionStatus": null } ], "discoveredPlaceActionLinks": [] } } }}回應會傳回更新後的地點,讓您確認新值已套用。每次更新都會傳回該地點目前完整的 placeActionLinks 集合。若要變更連結的 URL,請在同一個請求中,針對舊連結傳送 delete,並針對新連結傳送 add。
封存地點
POST /locations/archive 會排定封存一個或多個地點。封存不會立即生效:每個地點都會進入 ARCHIVE_SCHEDULED 狀態,在目前的計費週期結束前持續上線並計費,並於下一張帳單產生時封存。您可以使用此功能批次停用已結束營業的分店。
參數
curl -X POST https://listingsapi.com/api/v4/locations/archive \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "locationIds": ["TG9jYXRpb246MTgwMDI4OQ==", "TG9jYXRpb246MTgwMDI5MA=="] }}'{ "data": { "archiveLocations": { "clientMutationId": null, "bulkEditTaskId": null, "errors": null, "success": null, "result": [ { "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "status": "ARCHIVE_SCHEDULED", "success": true, "errors": null }, { "locationId": "TG9jYXRpb246MTgwMDI5MA==", "status": "ARCHIVE_SCHEDULED", "success": true, "errors": null } ] } }}最上層的 success 為 null;請改為讀取各個 result[] 項目本身的 success,如此一來,單一無效 id 就不會阻擋批次中的其餘項目。
地點排定封存後,清單與搜尋結果中會設定該地點的 archivalScheduledAt。已封存的地點必須重新啟用後才能更新。
取消已排定的封存
POST /locations/cancel_archive 會將處於 ARCHIVE_SCHEDULED 狀態的地點恢復為 ACTIVE,並繼續計費。此呼叫僅在計費週期尚未結束時有效;地點一旦完全封存,此呼叫就會針對該地點傳回 404。
參數
curl -X POST https://listingsapi.com/api/v4/locations/cancel_archive \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="] } }'{ "data": { "cancelLocationsArchive": { "clientMutationId": null, "bulkEditTaskId": null, "errors": null, "success": null, "result": [ { "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "status": "ACTIVE", "success": true, "errors": null } ] } }}每個 result[] 項目都會確認該地點已恢復為 ACTIVE。兩個 SDK 都需要額外提供兩個位置引數,分別為選取類型和 changedBy 識別碼,並會將這些引數與 locationIds 一併傳送;REST 請求主體只需要 id。
將相片上傳至地點
POST /locations/photos 會透過公開 URL 附加圖片:由您託管圖片,API 會擷取並儲存圖片。一個地點可保留 1 張 LOGO、1 張 COVER,以及任意數量的 ADDITIONAL 相片。重新上傳標誌或封面會取代現有圖片。
參數
curl -X POST https://listingsapi.com/api/v4/locations/photos \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "photos": [ { "photo": "https://cdn.example.com/jenny-home/logo.png", "type": "LOGO" }, { "photo": "https://cdn.example.com/jenny-home/storefront.jpg", "type": "ADDITIONAL" } ] }}'{ "data": { "addLocationPhotos": { "clientMutationId": null, "success": true, "request": {}, "photos": [ { "id": "TWVkaWFGaWxlOjU4Nzg5MzE=", "databaseId": 5878931, "type": "LOGO", "starred": false, "url": "https://sy-media-store.s3-us-west-2.amazonaws.com/f868bb3f/59c670a3/f47d62a6.png" }, { "id": "TWVkaWFGaWxlOjU4Nzg5MzI=", "databaseId": 5878932, "type": "ADDITIONAL", "starred": false, "url": "https://sy-media-store.s3-us-west-2.amazonaws.com/f868bb3f/59c670a3/ef4ffb12.jpg" } ], "errors": null } }}請保留每張相片的 id;標星與移除端點都需要此值。這是小批次直接回傳時的格式。大批次則會加入佇列:傳回的 photos 為空,且會設定 request.requestId,您需使用該 id 輪詢下一個端點。門檻由伺服器端設定,因此請根據是否存在 request.requestId 決定處理分支。
檢查大量相片上傳狀態
GET /locations/photos/requests/{requestId} 會輪詢已排入佇列的上傳作業,直到作業結束,然後回報每張圖片的結果。您可以用它來更新進度列,並明確顯示哪些檔案遭到拒絕及其原因。
參數
curl https://listingsapi.com/api/v4/locations/photos/requests/e6c2d9d9-9a18-4015-88cf-9a4e19a6f49a \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "getLocationPhotosUploadStatus": { "requestId": "e6c2d9d9-9a18-4015-88cf-9a4e19a6f49a", "status": "SUCCESS", "photos": [ { "mediaId": "TWVkaWFGaWxlOjkxODI3", "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "status": "SUCCESS", "errorMessage": null }, { "mediaId": null, "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "status": "ERROR", "errorMessage": "Image dimensions below the minimum 250x250." } ] } }}作業狀態只表示處理已完成,不代表每張圖片都已成功上傳。每次輪詢之間請間隔幾秒;在此處密集循環輪詢,很快就會觸發 429。
取得地點的相片
GET /locations/{locationId}/photos 會以單純的陣列傳回地點上的所有媒體檔案:標誌、封面及其他圖片,每個檔案都包含其公開的 url、thumbnailUrl、類別、starred 旗標,以及整個存續期間的 viewsCount。請在上傳前呼叫此端點,確認哪個欄位尚未有圖片,並收集供加上星號及移除呼叫使用的 id。
參數
curl https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/photos \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "mediaFilesOfLocation": [ { "id": "TWVkaWFGaWxlOjU4Nzg5MzE=", "categoryName": "Logo", "categoryId": "4", "fileType": "image", "starred": false, "viewsCount": 0, "url": "https://sy-media-store.s3-us-west-2.amazonaws.com/f868bb3f/59c670a3/f47d62a6.jpg", "thumbnailUrl": "https://sy-media-store.s3-us-west-2.amazonaws.com/f868bb3f/59c670a3/thumbnail_f47d62a6.jpg" }, { "id": "TWVkaWFGaWxlOjU4Nzg5MzI=", "categoryName": "Additional", "categoryId": "12", "fileType": "image", "starred": true, "viewsCount": 143, "url": "https://sy-media-store.s3-us-west-2.amazonaws.com/f868bb3f/59c670a3/ef4ffb12.jpg", "thumbnailUrl": "https://sy-media-store.s3-us-west-2.amazonaws.com/f868bb3f/59c670a3/thumbnail_ef4ffb12.jpg" } ] }}categoryName 為 Logo、Cover 或 Additional。格狀檢視請使用 thumbnailUrl,完整尺寸檢視請使用 url。沒有媒體的地點會傳回空陣列,而非錯誤。
為地點相片加上星號
POST /locations/photos/star 會標記其他相片,讓支援精選圖片的發布平台優先展示這些相片。只有 ADDITIONAL 相片可以加上星號,每個地點最多 4 張。取消星號的方式相同:由請求主體中的 starred 旗標決定執行哪項操作。
參數
curl -X POST https://listingsapi.com/api/v4/locations/photos/star \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "photoIds": ["TWVkaWFGaWxlOjU4Nzg5MzI="], "starred": true }}'{ "data": { "starUnstarLocationPhotos": { "success": true, "photos": [ { "id": "TWVkaWFGaWxlOjU4Nzg5MzI=", "databaseId": 5878932, "type": "ADDITIONAL", "starred": true } ], "error": null } }}這兩個路徑的回應鍵都是 starUnstarLocationPhotos,其中包含單一 error,而非 errors 陣列。第五張加上星號的相片會使整個呼叫失敗,並傳回 SY15012;檢查時計算的數量包含已加上星號的相片,以及本次請求中的相片。
從地點移除相片
POST /locations/photos/remove 會依媒體 id 解除其他相片與地點的關聯。您可以用它來移除過時的店面圖片,或復原有誤的大量匯入。只有 ADDITIONAL 相片可以移除;若要替換標誌或封面,請使用對應的 type 上傳新圖片。
參數
curl -X POST https://listingsapi.com/api/v4/locations/photos/remove \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "locationId": "TG9jYXRpb246MTgwMDI4OQ==", "photoIds": ["TWVkaWFGaWxlOjU4Nzg5MzI="] }}'{ "data": { "removeLocationPhotos": { "clientMutationId": null, "removedCount": 1, "errors": null } }}removedCount 會回報實際解除關聯的檔案數,因此,若 0 且 errors: null,就表示這些 id 都未與此地點建立關聯。移除操作會解除檔案的關聯,但不一定會將其從您的媒體庫中徹底刪除。
您會遇到的錯誤
寫入操作也可能傳回 HTTP 200,並包含 success: false,詳細資訊則位於 data.<operation>.errors。完整清單請參閱 錯誤代碼。