Read a Location's Google Photos from Stored Data
List the Google Business Profile photos Synup has stored for a location, owner and customer, with freshness and paging, without calling Google.
Em resumo
- O que este guia realiza
- List a location's stored owner and customer Google photos, page through them, and read how fresh the data is.
- APIs utilizadas
- get
/api/v1/locations/{id}/media/googleListar as fotos do Google armazenadas de um local - get
/api/v1/media/googleListar as fotos do Google armazenadas - get
/api/v1/mediaObter mídia do local - post
/api/v1/locations/{id}/mediaEnviar fotos para um local
- get
- Referência
- MediaLocations
- Pré-requisitos
- An API key with the media:read scope
- A location id (loc_...), from the locations endpoints
- Casos de uso típicos
- Showing a location's Google photos in your own dashboard
- Checking which photos customers added to a listing
- Telling a stale photo list from a complete one
As fotos do Google chegam até você de duas formas, e não são os mesmos dados. GET /api/v1/media retorna a biblioteca de mídia da própria Synup para um local, agrupada por categoria. Os dois endpoints deste guia, GET /api/v1/locations/{id}/media/google e GET /api/v1/media/google, retornam as fotos que a Synup armazenou a partir do próprio Google: as enviadas pela empresa (owner) e as adicionadas por clientes (ugc).
As duas leituras consultam apenas o que está armazenado. Elas nunca chamam o Google nem iniciam uma sincronização, então é seguro consultá-las com frequência. Cada foto é uma referência a uma URL hospedada no Google, não uma cópia (uma foto enviada pela Synup pode manter a URL de origem até a próxima coleta agendada substituí-la pela do Google), e seu externalId (o nome completo do recurso no Google) é sua identidade: a mesma URL com dois ids diferentes são duas fotos.
Leia as fotos da empresa, depois as de clientes e então a próxima página
Os ids, nomes e o cursor destes exemplos são fictícios e ilustrativos, não executáveis: passe sempre o nextCursor real que sua própria resposta retornar. Comece pelas fotos da própria empresa, uma por página para que a paginação apareça.
curl "https://ai.synup.com/api/v1/locations/loc_456/media/google?kind=owner&limit=1" \
-H "Authorization: Bearer $SYNUP_API_KEY"{
"data": {
"items": [
{
"id": "9b2e7c14-5d3a-4f6e-8a21-c0d7f3a1b645",
"associationKey": "9b2e7c14-5d3a-4f6e-8a21-c0d7f3a1b645:loc_456",
"externalId": "accounts/102938475610293847561/locations/5647382910563748291/media/AF1QipOwnerLobby01",
"url": "https://lh3.googleusercontent.com/p/AF1QipOwnerLobby01",
"category": "INTERIOR",
"format": "PHOTO",
"kind": "owner",
"lastSeenAt": "2026-10-12T04:01:58.000Z",
"presence": "seen_in_last_complete_scan",
"moderation": { "status": null },
"location": { "id": "loc_456", "name": "Acme Dental, Downtown", "clientId": "cli_123" },
"freshness": { "coverage": "complete", "outcome": "complete", "lastAttemptAt": "2026-10-12T04:02:11.000Z", "lastSuccessAt": "2026-10-12T04:02:11.000Z", "completedScanAt": "2026-10-12T04:01:58.000Z" }
}
],
"location": {
"id": "loc_456", "name": "Acme Dental, Downtown", "clientId": "cli_123",
"owner": { "coverage": "complete", "outcome": "complete", "lastAttemptAt": "2026-10-12T04:02:11.000Z", "lastSuccessAt": "2026-10-12T04:02:11.000Z", "completedScanAt": "2026-10-12T04:01:58.000Z" },
"ugc": { "coverage": "unavailable", "outcome": "unavailable", "lastAttemptAt": "2026-10-12T04:02:12.000Z", "lastSuccessAt": null, "completedScanAt": null }
},
"limit": 1,
"nextCursor": "NEXT_CURSOR_FROM_THIS_RESPONSE"
}
}O bloco location volta em toda leitura de um local, mesmo sem itens, e traz a atualização das fotos da empresa e de clientes. Agora, as fotos de clientes.
curl "https://ai.synup.com/api/v1/locations/loc_456/media/google?kind=ugc" \
-H "Authorization: Bearer $SYNUP_API_KEY"{
"data": {
"items": [],
"location": {
"id": "loc_456", "name": "Acme Dental, Downtown", "clientId": "cli_123",
"owner": { "coverage": "complete", "outcome": "complete", "lastAttemptAt": "2026-10-12T04:02:11.000Z", "lastSuccessAt": "2026-10-12T04:02:11.000Z", "completedScanAt": "2026-10-12T04:01:58.000Z" },
"ugc": { "coverage": "unavailable", "outcome": "unavailable", "lastAttemptAt": "2026-10-12T04:02:12.000Z", "lastSuccessAt": null, "completedScanAt": null }
},
"limit": 50,
"nextCursor": null
}
}Aqui, uma lista items vazia não significa "sem fotos de clientes". ugc.coverage é unavailable, então o Google não respondeu sobre fotos de clientes na última tentativa. Leia location antes de mostrar uma galeria vazia a um cliente. Por fim, siga o nextCursor da primeira página com os mesmos filtros.
curl "https://ai.synup.com/api/v1/locations/loc_456/media/google?kind=owner&limit=1&cursor=NEXT_CURSOR_FROM_THIS_RESPONSE" \
-H "Authorization: Bearer $SYNUP_API_KEY"{
"data": {
"items": [
{
"id": "c5d0e3a7-81b4-4a29-b6f2-3e9a7d1c0f58",
"associationKey": "c5d0e3a7-81b4-4a29-b6f2-3e9a7d1c0f58:loc_456",
"externalId": "accounts/102938475610293847561/locations/5647382910563748291/media/AF1QipOwnerFront02",
"url": "https://lh3.googleusercontent.com/p/AF1QipOwnerFront02",
"category": "EXTERIOR",
"format": "PHOTO",
"kind": "owner",
"lastSeenAt": "2026-10-12T04:01:58.000Z",
"presence": "seen_in_last_complete_scan",
"moderation": { "status": null },
"location": { "id": "loc_456", "name": "Acme Dental, Downtown", "clientId": "cli_123" },
"freshness": { "coverage": "complete", "outcome": "complete", "lastAttemptAt": "2026-10-12T04:02:11.000Z", "lastSuccessAt": "2026-10-12T04:02:11.000Z", "completedScanAt": "2026-10-12T04:01:58.000Z" }
}
],
"location": { "id": "loc_456", "...": "same location block as the first page" },
"limit": 1,
"nextCursor": null
}
}nextCursor é null, então era a última página. Um cursor só funciona com os filtros com que foi emitido. limit aceita um inteiro de 1 a 100 (padrão 50) e qualquer outro valor resulta em 400, sem ajuste. Para todos os locais de uma vez, chame GET /api/v1/media/google, que também aceita locationId, clientId e kind; seu location é null somente quando você não envia locationId; com locationId ele traz a cobertura desse local mesmo com items vazio. Cada item também traz o seu.
O que os campos garantem
- Um item por foto e local. Uma foto compartilhada por vários locais aparece uma vez para cada local a que você tem acesso. Identifique as linhas por
associationKey, não porid. presenceé uma observação.seen_in_last_complete_scansignifica que uma resposta do Google incluiu a foto desde que a última verificação completa começou.not_seen_in_last_complete_scansignifica que ela não estava nessa verificação. Nenhum dos dois diz que o Google excluiu algo, eunknownsignifica que não há verificação completa para comparar. Uma foto que você enviou depois do início de uma verificação também conta como vista.- Nada é ocultado. Fotos marcadas como
spamouneeds_reviewemmoderation.statuscontinuam sendo retornadas, e linhas antigas são mantidas em vez de filtradas, então aplique a sua própria política. - O acesso limita o que você vê. Uma chave limitada a certos clientes vê só os locais desses clientes, um local sem cliente só é visível para chaves de toda a agência, e os outros locais de uma foto compartilhada nunca são retornados nem contados.
- A atualização é honesta. Uma atualização bem-sucedida é agendada a cada 72 horas por um despachante diário às 04:00 UTC. Uma conexão com o Google ausente ou um erro aparece como cobertura
partialouunavailable, e um 403 ou 404 do Google para fotos de clientes pode significar indisponível, não vazio. Estar elegível para nova tentativa não promete que ela rode naquele momento.
A partir de um cliente de IA (MCP)
A ferramenta list_google_location_media lê os mesmos dados armazenados com os mesmos filtros (locationId, clientId, kind, limit, cursor) e retorna a mesma página.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_google_location_media",
"arguments": { "locationId": "loc_456", "kind": "owner", "limit": 25 }
}
}Ela não atualiza nada. fetch_gbp_media é outra ferramenta: consulta o Google sob demanda e altera os dados armazenados, então use-a só quando quiser uma nova coleta.