地点
按使用顺序介绍所有 Locations 端点,每个端点均提供 cURL、Python 和 Node 示例,涵盖列出、搜索、按 id 和门店代码查找、创建、更新、归档以及照片管理。
商家信息、评价、帖子和分析数据均以地点 ID 为关联键。Locations 资源包含 13 个端点:4 个用于查找地点的读取端点、4 个用于创建、更改和归档地点的写入端点,以及 5 个用于管理照片的端点。Read(读取)密钥可用于读取操作。每个 POST 都需要 Write(写入)密钥;使用 Read 密钥执行写入操作会返回 HTTP 400,并附带 SY90016。
本指南中的每个 id 都是 base64 编码的 Relay id,例如 TG9jYXRpb246MTgwMDI4OQ==(解码后为 Location:1800289)。这 2 个 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 的结构与列表端点相同。您可以将它用于输入时即时搜索的选择器,或在创建门店前确认该门店是否已存在。
参数
这 2 个 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 类别、社交平台 URL、坐标、所有者联系方式、付款方式、服务区域等。
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 是后续每次写入所需的 ID,而 databaseId 是您在控制台和 webhook 中看到的数字。status 的初始值为 PENDING,之后会自动推进,无需再次调用 API。地点操作链接的 name 和 submissionStatus 在同步至 Google 之前会一直保持为 null。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 数组。第 5 张星标照片会导致整个调用失败,并返回 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 中提供详细信息。完整列表请参阅 错误代码。