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
unavailableenquanto 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 webhookcontact.picture_changedé enviado quando a foto chega ao seu bucket.
Endpoint
GET /v1/contacts/{contact_id}/profile-pictureEscopo 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.kind | Resposta HTTP |
|---|---|
available | 302 Found: Location aponta para a URL pré-assinada do BYO Storage (R2/S3/Supabase), nunca para a chave do objeto no bucket |
unavailable | 200 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
curl -I "https://api.turbonotify.com/v1/contacts/ct_.../profile-picture" \ -H "Authorization: Bearer SUA_CHAVE"
HTTP/1.1 302 FoundLocation: https://<bucket>.r2.cloudflarestorage.com/...Cache-Control: no-store, no-cache, must-revalidatePragma: no-cacheExemplo: foto indisponível (sem armazenamento)
{
"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-revalidateePragma: no-cache. Ela não deve ser guardada em cache, inclusive o redirecionamento302. Refaça a chamada sempre que precisar do estado atual. A URL pré-assinada indicada emLocationcontinua 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.