Pular para o conteúdo

Foto de perfil

Retorna a foto de perfil do contato. Quando a foto está disponível, a resposta é um redirecionamento 302 para uma URL pré-assinada; quando não está, a resposta é 200 OK com um corpo JSON que explica o motivo. Este endpoint não usa 404 para “sem foto” (veja a tabela abaixo).

O 302 aponta para uma URL pré-assinada do seu BYO Storage (nunca a chave do objeto no bucket). Uma foto marcada como unavailable enquanto você não tinha armazenamento configurado é reavaliada na próxima mensagem trocada com o contato (recebida ou enviada, inclusive pela API) depois que você habilita o BYO Storage, respeitada a janela de 6 horas se já houve consulta (veja Quando as fotos aparecem). O webhook contact.picture_changed é enviado quando a foto chega ao seu bucket.

Endpoint

GET /v1/contacts/{contact_id}/profile-picture

Escopo obrigatório: contacts:read.

Para números extras: GET /v1/numbers/{ref}/contacts/{contact_id}/profile-picture, onde {ref} é o number_id do envelope do evento de contato (num_…, não muda) ou o alias atual do número.

Comportamento por picture.kind

O picture retornado tem apenas dois estados possíveis: available (foto no seu BYO Storage) ou unavailable (qualquer outra situação, com a causa em reason, calculada no momento da leitura):

picture.kindResposta HTTP
available302 Found: Location aponta para a URL pré-assinada do BYO Storage (R2/S3/Supabase), nunca para a chave do objeto no bucket
unavailable200 OK: corpo JSON { "kind": "unavailable", "reason": ..., "setup_hint"?: ... } (ver campo reason abaixo)

Um 404 contact_picture_unavailable só ocorre em um caso raro: a foto já está armazenada, mas a geração da URL pré-assinada falhou nesta requisição (por exemplo, um erro passageiro do provedor). Não é a resposta para “contato sem foto” nem para “sem armazenamento configurado”: esses casos sempre retornam 200.

Exemplo: foto disponível

Terminal window
curl -I "https://api.turbonotify.com/v1/contacts/ct_.../profile-picture" \
-H "Authorization: Bearer SUA_CHAVE"
HTTP/1.1 302 Found
Location: https://<bucket>.r2.cloudflarestorage.com/...
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache

Exemplo: foto indisponível (sem armazenamento)

Status HTTP: 200
{
  "kind": "unavailable",
  "reason": "no_storage",
  "setup_hint": {
    "setup_url": "https://dashboard.turbonotify.com/storage",
    "docs_url": "https://docs.turbonotify.com/general/byo-storage-setup/",
    "message": "Configure object storage (S3, Cloudflare R2, or Supabase Storage, free tiers available) to receive media. Visit your dashboard or read the docs."
  }
}

O campo reason indica a causa: no_storage (sem armazenamento configurado), storage_circuit_open (o seu armazenamento vinha falhando e o envio foi suspenso temporariamente), upload_failed (o envio da foto para o seu bucket falhou desta vez: não há nova tentativa imediata; ao contrário da mídia de mensagem, em que a falha é definitiva, a foto é buscada de novo na próxima mensagem trocada com o contato (recebida ou enviada, inclusive pela API)) ou peer_has_none (o contato não tem foto no WhatsApp). O setup_hint só é preenchido para no_storage e storage_circuit_open. Para upload_failed e peer_has_none ele vem como null.

Cache

  • A resposta vem com Cache-Control: no-store, no-cache, must-revalidate e Pragma: no-cache. Ela não deve ser guardada em cache, inclusive o redirecionamento 302. Refaça a chamada sempre que precisar do estado atual. A URL pré-assinada indicada em Location continua válida pelo próprio prazo dela, e é essa URL que você deve reutilizar se quiser evitar um novo download.
  • A URL pré-assinada tem validade curta: 15 minutos por padrão (configurável entre 1 minuto e 1 hora na sua integração de armazenamento). Não reutilize a URL além desse prazo; refaça a chamada para obter uma nova.