Nuntis documentação Referência da API

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.

bash
curl -H "x-api-key: $NUNTIS_API_KEY" \
  'https://api.nuntis.com.br/v1/conversations/conv_9931/avatar'
200 OK — o contato tem foto visível
{
  "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"
}
200 OK — não há foto a mostrar
{
  "url": null,
  "urlExpiresAt": null,
  "fetchedAt": "2026-08-31T19:00:00.000Z"
}
CampoO 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

Não desenhe a tela contando com a foto

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.

Consequência prática

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á fotoNão deu para consultar agora
Você recebe200 com url: nullErro 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 exibirIniciais / 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

Nunca persista a 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 frescorfetchedAt é 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ódigoHTTPRetentávelQuando
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

Nenhuma credencial nova é necessária

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.