GuideGuias/Receitas/Read stored Google photos
How do I read a location's Google Business Profile photos through the API?

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
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 por id.
  • presence é uma observação. seen_in_last_complete_scan significa que uma resposta do Google incluiu a foto desde que a última verificação completa começou. not_seen_in_last_complete_scan significa que ela não estava nessa verificação. Nenhum dos dois diz que o Google excluiu algo, e unknown significa 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 spam ou needs_review em moderation.status continuam 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 partial ou unavailable, 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.