Ubicaciones
Ubicaciones: ejemplos cURL, Python y Node en orden de uso: listar, buscar, consultar por id/código de tienda, crear, actualizar, archivar y fotos.
Las fichas, las reseñas, las publicaciones y las analíticas están vinculadas a un ID de ubicación. El recurso de ubicaciones tiene trece endpoints: cuatro de lectura para encontrar ubicaciones, cuatro de escritura para crearlas, modificarlas y archivarlas, y cinco para gestionar fotos. Una clave Read (lectura) cubre las operaciones de lectura. Cada POST necesita una clave Write (escritura); usar una clave Read en una operación de escritura devuelve HTTP 400 con SY90016.
Todos los id de esta guía son id de Relay en base64, por ejemplo, TG9jYXRpb246MTgwMDI4OQ== (se decodifica como Location:1800289). Ambos SDK también aceptan el databaseId numérico y lo codifican por ti.
Resumen de endpoints
Listar todas las ubicaciones
GET /locations recorre por páginas todas las ubicaciones de la cuenta, empezando por las más recientes. Es una conexión con paginación por cursor: edges[].node contiene cada registro y pageInfo.endCursor proporciona el valor del parámetro after para la página siguiente. Úsalo para sincronizar toda la cuenta con tu propio sistema.
Parámetros
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 } } }}Lee pageInfo.hasNextPage y detente cuando sea false. El recuento de la página es pageInfo.total. El id del nodo es el identificador que esperan todos los demás endpoints.
Buscar ubicaciones
GET /locations/search busca coincidencias de una palabra clave en el nombre de la ubicación, la dirección postal, la ciudad y el id de tienda. Es una conexión con paginación por cursor con la misma estructura de edges[].node y pageInfo.endCursor que el listado. Úsalo para un selector con búsqueda mientras escribes o para confirmar que una tienda existe antes de crearla.
Parámetros
Ambos SDK también aceptan una lista fields para restringir la búsqueda de coincidencias a campos específicos, por ejemplo, solo 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 } } }}Un array edges vacío significa que no hubo coincidencias. El nodo contiene la dirección completa, la categoría y el estado approved, por lo que una fila de resultados no requiere una segunda consulta.
Obtener ubicaciones por IDs
GET /locations-by-ids devuelve el registro completo de cada ubicación de un lote de ids en base64 en una sola llamada, con menos solicitudes que si hicieras una llamada por ubicación. Se resuelven tanto las ubicaciones activas como las archivadas. Devuelve un array simple, no una conexión. Úsalo para obtener los registros completos a partir de los ids de la carga útil de un webhook o de tu propio almacén de datos.
Parámetros
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": [] } ] }}Los ids que no existen en tu cuenta se omiten silenciosamente en lugar de generar un error. Compara la cantidad devuelta con la que enviaste para detectar ids obsoletos.
Obtener ubicaciones por códigos de tienda
GET /locations-by-store-codes realiza la búsqueda inversa: resuelve tus propios códigos de tienda para obtener los registros completos. Devuelve un array simple. Úsalo cuando tu sistema de punto de venta o tu registro de franquicias identifica las ubicaciones por código de tienda y nunca hayas almacenado nuestros ids.
Parámetros
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" } ] }}Relaciona el storeId de cada objeto con la entrada correspondiente de tu registro. Los códigos sin coincidencias no se incluyen. Una ubicación solo tiene un código de tienda si se estableció storeId al crearla o actualizarla.
Crear una ubicación
POST /locations crea una ubicación y la pone en cola para enviarla a todas las plataformas de publicación de tu plan, o solo a enabledSiteIds cuando incluyes ese campo. Todos los campos van dentro de input. Hay 10 campos obligatorios; los demás son opcionales y las plataformas de publicación muestran en blanco los que omitas.
Parámetros
Además de los campos opcionales que figuran en la página de referencia: categorías GCID de Google, URLs de redes sociales, coordenadas, contacto del propietario, métodos de pago, área de servicio y más.
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": [] } } }}Conserva ambos ids: el id en base64 es el que necesita cada escritura posterior, y databaseId es el número que ves en los paneles y los webhooks. status comienza como PENDING y avanza sin otra llamada a la API. Los valores de name y submissionStatus de un enlace de acción del lugar permanecen en null hasta que el enlace se sincroniza con Google. locations.add() de Python valida los campos obligatorios localmente y lanza ValidationError antes de cualquier llamada de red; locations.create(input), en cambio, acepta un dict camelCase sin procesar.
Actualizar una ubicación
POST /locations/update es una actualización parcial: input.id es el único campo obligatorio y solo cambian los campos que envías. La misma llamada permite corregir un número de teléfono o reescribir el horario semanal. Tanto locations.update() de Python como updateLocation() de Node aceptan un único objeto camelCase.
Parámetros
Además de los campos opcionales indicados en la página de referencia.
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": [] } } }}La respuesta devuelve la ubicación actualizada para que puedas confirmar que se aplicaron los nuevos valores. Cada actualización devuelve el conjunto completo y actual de placeActionLinks de la ubicación. Para cambiar la URL de un enlace, envía un delete para el enlace anterior y un add para el nuevo en la misma solicitud.
Archivar ubicaciones
POST /locations/archive programa el archivado de una o más ubicaciones. El archivado no es inmediato: cada ubicación pasa a ARCHIVE_SCHEDULED, permanece activa y se sigue facturando hasta que termina el ciclo de facturación actual, y se archiva cuando se genera la siguiente factura. Úsalo para dar de baja en lote las sucursales cerradas.
Parámetros
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 } ] } }}El success de nivel superior es null; lee en su lugar el success propio de cada entrada de result[], de modo que un id incorrecto no bloquee el resto del lote.
Mientras el archivado está programado, se establece el archivalScheduledAt de la ubicación en los resultados de las consultas de lista y búsqueda. Una ubicación archivada no se puede actualizar hasta que se reactive.
Cancelar el archivado programado
POST /locations/cancel_archive devuelve las ubicaciones que están en ARCHIVE_SCHEDULED a ACTIVE, y la facturación continúa. Solo funciona mientras el ciclo de facturación sigue abierto; una vez que una ubicación está completamente archivada, esta llamada devuelve 404 para ella.
Parámetros
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 } ] } }}Cada entrada de result[] confirma que la ubicación ha vuelto a ACTIVE. Ambos SDK requieren 2 argumentos posicionales adicionales, un tipo de selección y un identificador changedBy, que envían junto con locationIds; el cuerpo de la solicitud REST solo necesita los ids.
Subir fotos a una ubicación
POST /locations/photos adjunta imágenes mediante una URL pública: tú alojas la imagen y la API la obtiene y la almacena. Una ubicación conserva un LOGO, un COVER y cualquier número de fotos ADDITIONAL. Volver a subir un logotipo o una portada reemplaza el elemento existente.
Parámetros
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 } }}Conserva el id de cada foto; los endpoints para marcar con una estrella y eliminar lo aceptan. Esta es la estructura de la respuesta directa para un lote pequeño. En cambio, un lote grande se pone en cola: photos se devuelve vacío y se establece request.requestId, y debes consultar periódicamente el siguiente endpoint con ese id. El umbral se configura en el servidor, así que elige la rama de ejecución según esté presente o no request.requestId.
Consultar el estado de una carga masiva de fotos
GET /locations/photos/requests/{requestId} consulta periódicamente una carga en cola hasta que finaliza y, a continuación, informa del resultado de cada imagen. Úsalo para actualizar una barra de progreso y mostrar exactamente qué archivos se rechazaron y por qué.
Parámetros
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." } ] } }}El estado del trabajo solo indica que el procesamiento ha finalizado, no que todas las imágenes se hayan cargado correctamente. Deja pasar unos segundos entre consultas; un bucle sin pausas puede hacer que alcances 429 rápidamente.
Obtener las fotos de una ubicación
GET /locations/{locationId}/photos devuelve todos los archivos multimedia de una ubicación en un array simple: logotipo, portada e imágenes adicionales, cada uno con su url público, thumbnailUrl, categoría, indicador starred y viewsCount acumulado desde su creación. Llámalo antes de una carga para ver qué espacio falta por cubrir y recopilar los identificadores para las llamadas de marcado como destacado y eliminación.
Parámetros
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 es Logo, Cover o Additional. Usa thumbnailUrl para una cuadrícula y url para la vista a tamaño completo. Una ubicación sin archivos multimedia devuelve un array vacío, no un error.
Marcar fotos de una ubicación como destacadas
POST /locations/photos/star marca fotos adicionales para darles una posición prioritaria en las plataformas de publicación que admiten imágenes destacadas. Solo se pueden marcar como destacadas las fotos ADDITIONAL, con un máximo de 4 por ubicación. Quitar la marca de destacada funciona de la misma manera: el indicador starred del cuerpo determina qué ocurre.
Parámetros
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 } }}La clave de respuesta es starUnstarLocationPhotos en ambas rutas y contiene un único error, no un array errors. Una quinta foto destacada hace que toda la llamada falle con SY15012; la comprobación cuenta las fotos que ya están marcadas como destacadas más las de esta solicitud.
Eliminar fotos de una ubicación
POST /locations/photos/remove desvincula fotos adicionales mediante su identificador de archivo multimedia. Úsalo para retirar imágenes desactualizadas de la fachada del establecimiento o deshacer una importación masiva incorrecta. Solo se pueden eliminar las fotos ADDITIONAL; para sustituir un logotipo o una portada, carga una nueva imagen con ese type.
Parámetros
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 indica cuántos archivos se desvincularon realmente, por lo que 0 con errors: null significa que ninguno de esos identificadores estaba vinculado a esta ubicación. La eliminación desvincula el archivo; no necesariamente lo borra de tu biblioteca multimedia.
Errores que verás
Las operaciones de escritura también pueden devolver HTTP 200 con success: false y el detalle en data.<operation>.errors. La lista completa está en códigos de error.
Próximos pasos
- Añadir una ubicación, Actualizar una ubicación, Subir fotos de una ubicación y Buscar y archivar ubicaciones: las guías de tareas basadas en estos endpoints.
- Consultar países y categorías: los valores de
countryIso,stateIsoysubCategoryIdnecesarios para una operación de creación. - Enlaces de acción de lugares: el modelo completo de los botones de acción de Google para las operaciones de creación y actualización.
- Ubicaciones: la página principal del recurso y las referencias del SDK para Python y Node.
- Los métodos de fotos tienen sus propias páginas en la documentación del SDK: Fotos en Python y Fotos en Node.