Enviar e receber
Conversas
Uma conversa é o par número seu × contato do outro lado. O
conversationId já está na sua mão: ele vem em toda mensagem e em
todo webhook. Hoje há uma rota pendurada nele — a foto de perfil do contato.
Foto de perfil do contato
GET /v1/conversations/{conversationId}/avatar
Devolve a foto de perfil do contato daquela conversa como URL assinada e temporária, no mesmo formato que você já conhece da mídia de mensagem. Serve para a foto aparecer no card do lead na sua interface — e é só para isso que ela existe.
curl -H "x-api-key: $NUNTIS_API_KEY" \
'https://api.nuntis.com.br/v1/conversations/conv_9931/avatar'
{
"url": "https://bucket.devari.com.br/nuntis/avatars/9931.jpg?X-Amz-Signature=...",
"urlExpiresAt": "2026-08-31T20:00:00.000Z",
"fetchedAt": "2026-08-31T19:00:00.000Z"
}
{
"url": null,
"urlExpiresAt": null,
"fetchedAt": "2026-08-31T19:00:00.000Z"
}
| Campo | O que é |
|---|---|
url |
URL assinada e temporária da foto. null é resposta de sucesso, não erro — veja abaixo. |
urlExpiresAt |
Quando a url deixa de funcionar (ISO 8601). null quando url é null. Não há rota de renovação: peça o recurso de novo. |
fetchedAt |
Quando o Nuntis consultou o canal pela última vez — não quando esta resposta foi montada. É a idade da informação. Pode vir null; trate como idade desconhecida. |
Foto ausente é o caso comum, não a exceção
Numa amostra real de 14 contatos, medida durante a construção desta rota, 5 tinham foto visível. Os outros 9 não: 7 simplesmente não têm foto, e 2 têm privacidade fechada. É amostra pequena e não é promessa estatística — mas a ordem de grandeza é essa, e a conclusão de produto não muda: a maior parte dos seus leads vai cair no caso sem foto.
Uma interface que reserva espaço para a foto e não trata a ausência fica com
buraco na maioria das linhas. Trate url: null como o caminho
normal: renderize iniciais ou um placeholder, e faça a foto ser o
enfeite que ela é.
Dois motivos diferentes levam ao mesmo url: null, e para a sua
interface eles são a mesma coisa: o contato não tem foto, ou
a privacidade dele esconde a foto de quem não está na agenda
dele. Em nenhum dos dois o Nuntis falhou, e em nenhum dos dois adianta pedir de
novo em seguida.
Por que a rota é por conversa, e não por contato
Porque a visibilidade da foto é do par, não da pessoa. Um contato pode configurar “foto visível só para meus contatos” — e aí a foto aparece ou não dependendo de qual dos seus números está perguntando: se o telefone daquele operador estiver na agenda do lead, a foto aparece; se for outro número seu, que ele não tem salvo, não aparece.
O mesmo lead, portanto, pode ter foto para um número do seu produto e não ter para outro. A conversa carrega exatamente esse par (número, contato) — por isso ela é a âncora. Uma rota “por contato” teria de escolher um número por conta própria, e qualquer escolha implícita daria uma resposta que não corresponde ao que aquele operador enxerga.
Não faça cache da foto por contato do seu lado. Se você guardar “o lead X não tem foto” e exibir isso em todas as caixas, vai esconder a foto que outro número do seu produto enxerga perfeitamente. A chave é a conversa.
“Não tem foto” e “não consegui buscar agora” são respostas diferentes
Esta é a distinção que o contrato existe para preservar, e ela muda o que o seu código deve fazer:
| O canal respondeu que não há foto | Não deu para consultar agora | |
|---|---|---|
| Você recebe | 200 com url: null | Erro tipado — 503 CHANNEL_UNREACHABLE |
| É um fato? | Sim. Foi o canal que disse. | Não. É ignorância nossa, temporária. |
| Retry ajuda? | Não em seguida — a resposta não vai mudar. | Sim. retryable: true. |
| O que exibir | Iniciais / placeholder. | Mantenha o que já havia, ou o placeholder — e tente de novo depois. |
Uma falha temporária nunca é devolvida como url: null.
Se fosse, uma indisponibilidade de meio minuto apagaria da sua interface, por
horas, a foto de um lead que tem foto — sem erro em lugar nenhum para
denunciar. Por isso as duas respostas são de tipos diferentes: se você recebeu
200, a ausência é um fato do canal e você pode confiar nela.
A URL é temporária — gere na hora de exibir
url
Ela é assinada e expira (urlExpiresAt diz quando). Gravá-la no
seu banco é gravar uma mentira com hora marcada: em algum momento os cards do
seu inbox passam a mostrar imagem quebrada. Guarde o
conversationId e peça o recurso quando for renderizar. É a mesma
disciplina da mídia de mensagem.
Pedir de novo é barato: a resposta vem do estado que o Nuntis mantém, sem
consultar o canal a cada chamada. Em compensação não há promessa de
frescor — fetchedAt é a idade da informação, e a foto
pode ter mudado no canal depois daquele instante. Foto de perfil é enfeite de
interface; nenhuma decisão do seu produto deveria depender de ela estar
atualizada ao segundo.
Antes de reservar o espaço na interface
Nem todo canal entrega foto de perfil. Quem responde isso é o campo
avatar em
GET /v1/channels: onde ele é
false, esta rota responde
422 CHANNEL_CAPABILITY_UNSUPPORTED — e o certo é nem chamá-la,
escondendo o espaço da foto para números daquele canal.
avatar: true não promete que existe foto
Ele promete que o canal sabe responder a pergunta. A foto em si continua podendo não existir — e, como visto acima, na maioria das vezes é o que acontece. São duas condições diferentes: a capacidade decide se você pergunta; a resposta decide se você desenha.
Erros desta rota
| Código | HTTP | Retentável | Quando |
|---|---|---|---|
UNAUTHORIZED |
401 | não | Credencial ausente ou inválida. |
NOT_FOUND |
404 | não | Nenhuma conversa acessível com esse identificador. Indistinguível de conversa que pertence a outro cliente da plataforma — de propósito. |
CHANNEL_CAPABILITY_UNSUPPORTED |
422 | não | O canal deste número não declara avatar. Consulte GET /v1/channels. |
NUMBER_DEACTIVATED |
422 | não | O número da conversa foi desativado. Reativar não é self-service. |
NUMBER_NOT_CONNECTED |
422 | não | O número não tem sessão conectada no canal, e sem sessão não há como perguntar nada sobre um contato. Retry não resolve — o remédio é parear o número e conferir GET /v1/numbers/{id}/status. |
CHANNEL_UNREACHABLE |
503 | sim | Não foi possível consultar o canal agora. Tente de novo com backoff — e não trate como “sem foto”. |
A forma do erro é a mesma do resto do contrato, e a regra também: decida o
retry por retryable, nunca por status HTTP nem por texto. Veja
Erros.
Permissão
Esta rota já está coberta por messages:read — o mesmo escopo que
a sua chave usa para listar mensagens. Se você já lê mensagens, você já lê a
foto de perfil: não é preciso pedir uma chave nova nem uma
permissão extra.
Escopos e rotação: Autenticação.