API — Conversas
Conversa é o fio de mensagens com um cliente, em qualquer canal. Para as mensagens dentro dela, veja Mensagens.
Endpoints
Seção intitulada “Endpoints”| Método | Endpoint | Ação |
|---|---|---|
GET | /conversations | Lista |
GET | /conversations/meta | Só as contagens por status |
POST | /conversations/filter | Filtro combinado |
GET | /conversations/search | Busca no texto das mensagens |
POST | /conversations | Abre conversa |
GET | /conversations/{id} | Detalhes |
PATCH | /conversations/{id} | Só muda a prioridade |
DELETE | /conversations/{id} | Remove |
POST | /conversations/{id}/toggle_status | Resolver, reabrir, adiar |
POST | /conversations/{id}/assignments | Atribuir a pessoa ou a equipe |
POST | /conversations/{id}/labels | Etiquetas |
POST | /conversations/{id}/toggle_priority | Prioridade |
POST | /conversations/{id}/mute · /unmute | Silenciar |
POST | /conversations/{id}/toggle_bot | Liga e desliga o bot naquela conversa |
POST | /conversations/{id}/custom_attributes | Campos personalizados |
GET | /conversations/{id}/attachments | Anexos trocados |
POST | /conversations/{id}/transcript | Manda a transcrição por e-mail |
Todos sob /api/v1/accounts/{account_id}.
Três formatos de resposta diferentes
Seção intitulada “Três formatos de resposta diferentes”Esta é a parte que mais derruba integração nova, então vem antes dos exemplos:
// GET /conversations — tem a camada "data", e o meta vem VAZIO{ "data": { "meta": {}, "payload": [ ... ] } }
// POST /conversations/filter — sem a camada "data"{ "meta": {}, "payload": [ ... ] }
// GET /conversations/{id} — o objeto direto{ "id": 8821, "status": "open", ... }O total não vem em nenhum dos três. Para contar, existe endpoint separado:
curl "https://app.smart2.com.br/api/v1/accounts/1/conversations/meta?status=open" \ -H "api_access_token: SEU_TOKEN"Campos do recurso
Seção intitulada “Campos do recurso”{ "id": 8821, "account_id": 1, "inbox_id": 3, "uuid": "b3f1...", "status": "open", "priority": "high", "labels": ["vip", "lead-novo"], "custom_attributes": { "motivo": "duvida-comercial" }, "additional_attributes": {}, "can_reply": true, "muted": false, "bot_active": true, "is_group": false, "snoozed_until": null, "sla_policy_id": null, "unread_count": 1, "meta": { "sender": { "id": 4521, "name": "Maria Silva", "phone_number": "+5511999990000" }, "assignee": { "id": 7, "name": "João Souza", "email": "joao@..." }, "assignee_type": "User", "team": { "id": 2, "name": "Vendas SP" }, "channel": "Channel::Whatsapp", "queue_state": null, "hmac_verified": false }, "messages": [ { "id": 99012, "content": "Oi, queria um orçamento" } ], "last_non_activity_message": { "...": "última mensagem de gente, ignorando eventos" }, "created_at": 1758542400, "updated_at": 1758542400.0, "last_activity_at": 1758542400, "timestamp": 1758542400, "waiting_since": 1758542400, "first_reply_created_at": 0, "messaging_window_expires_at": 1758628800, "agent_last_seen_at": 1758542400, "contact_last_seen_at": 1758542400}Cinco coisas que costumam surpreender:
idé o número da URL (odisplay_id), que conta a partir de 1 dentro da conta. Não é chave global.- Cliente, responsável, equipe e canal ficam dentro de
meta, não no primeiro nível. messagestraz só a última. Para o histórico,GET /conversations/{id}/messages.- As datas são números — segundos desde 1970.
updated_atvem com fração. channelé o nome da classe:Channel::Whatsapp,Channel::WebWidget,Channel::FacebookPage,Channel::Email,Channel::Api.
messaging_window_expires_at é a janela de 24 horas do WhatsApp — passada ela, só modelo aprovado sai. can_reply já resume isso num booleano.
| Status | Significado |
|---|---|
open | Em andamento |
pending | Esperando a primeira ação de gente |
resolved | Encerrada |
snoozed | Adiada, volta na data marcada |
GET /conversations
Seção intitulada “GET /conversations”curl "https://app.smart2.com.br/api/v1/accounts/1/conversations?status=open&inbox_id=3" \ -H "api_access_token: SEU_TOKEN"| Parâmetro | Filtra por |
|---|---|
status | open, pending, resolved, snoozed ou all. Padrão: open |
inbox_id | Caixa |
team_id | Equipe |
assignee_type | assigned, unassigned, me |
labels[] | Etiqueta — basta uma bater |
q | Texto dentro das mensagens |
updated_within | Mudou nos últimos N segundos |
sort_by | last_activity_at_desc (padrão), created_at_asc, priority_desc, waiting_since_asc… |
page | Página. São 25 por página |
Ordenação é um parâmetro só, com campo e direção juntos (created_at_desc), não dois. Valor desconhecido cai no padrão sem avisar.
POST /conversations — abrir
Seção intitulada “POST /conversations — abrir”curl -X POST "https://app.smart2.com.br/api/v1/accounts/1/conversations" \ -H "api_access_token: SEU_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "inbox_id": 3, "contact_id": 4521, "status": "open", "source_id": "ERP-8842" }'| Campo | Notas |
|---|---|
inbox_id | Obrigatório |
contact_id | Obrigatório (ou source_id de um contato já ligado àquela caixa) |
status | Padrão open |
source_id | O identificador do seu lado |
additional_attributes | Dados livres seus |
message | Já cria a primeira mensagem junto |
Resolver, reabrir, adiar
Seção intitulada “Resolver, reabrir, adiar”curl -X POST "https://app.smart2.com.br/api/v1/accounts/1/conversations/8821/toggle_status" \ -H "api_access_token: SEU_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "status": "resolved" }'Adiar leva a data de volta:
curl -X POST ".../conversations/8821/toggle_status" \ -d '{ "status": "snoozed", "snoozed_until": "2026-09-25T09:00:00Z" }'Sem status, o endpoint alterna entre aberta e resolvida — daí o nome.
Atribuir
Seção intitulada “Atribuir”# a uma pessoacurl -X POST ".../conversations/8821/assignments" \ -H "Content-Type: application/json" \ -d '{ "assignee_id": 7 }'
# a uma equipecurl -X POST ".../conversations/8821/assignments" \ -d '{ "team_id": 2 }'Para tirar o responsável, mande assignee_id: null.
Os números vêm de GET /agents e GET /teams, os dois sob a conta.
Etiquetas
Seção intitulada “Etiquetas”curl -X POST ".../conversations/8821/labels" \ -H "Content-Type: application/json" \ -d '{ "labels": ["vip", "urgente"] }'Substitui a lista inteira. Para acrescentar sem perder o que já tinha, leia antes:
atuais = requests.get(f"{BASE}/conversations/8821/labels", headers=H).json()["payload"]requests.post(f"{BASE}/conversations/8821/labels", headers=H, json={"labels": list(set(atuais) | {"urgente"})})Prioridade
Seção intitulada “Prioridade”curl -X POST ".../conversations/8821/toggle_priority" \ -d '{ "priority": "urgent" }'Valores: low, medium, high, urgent, ou null para tirar. A resposta vem vazia, com 200.
POST /conversations/filter
Seção intitulada “POST /conversations/filter”curl -X POST "https://app.smart2.com.br/api/v1/accounts/1/conversations/filter" \ -H "api_access_token: SEU_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "payload": [ { "attribute_key": "status", "filter_operator": "equal_to", "values": ["open"] }, { "attribute_key": "labels", "filter_operator": "contains", "values": ["vip"], "query_operator": "and" }, { "attribute_key": "last_activity_at", "filter_operator": "is_less_than", "values": ["2026-09-15T00:00:00Z"], "query_operator": "and" } ] }'“Abertas, com etiqueta VIP, paradas há mais de uma semana.”
Repare no operador: is_less_than, com o prefixo is_. Sem ele a chamada é recusada. A lista completa está em convenções.
Casos de uso
Seção intitulada “Casos de uso”Encerrar o que está parado há mais de 30 dias
Seção intitulada “Encerrar o que está parado há mais de 30 dias”from datetime import datetime, timedelta
corte = (datetime.utcnow() - timedelta(days=30)).isoformat() + "Z"
paradas = requests.post(f"{BASE}/conversations/filter", headers=H, json={ "payload": [ {"attribute_key": "status", "filter_operator": "equal_to", "values": ["open"]}, {"attribute_key": "last_activity_at", "filter_operator": "is_less_than", "values": [corte], "query_operator": "and"}, ]}).json()["payload"] # sem a camada "data" aqui
for c in paradas: requests.post(f"{BASE}/conversations/{c['id']}/toggle_status", headers=H, json={"status": "resolved"})Avisar o atendente sem falar com o cliente
Seção intitulada “Avisar o atendente sem falar com o cliente”O uso mais comum de integração com ERP: nota interna na conversa.
requests.post(f"{BASE}/conversations/8821/messages", headers=H, json={ "content": "Pedido #8842 faturado. NF 1.294 emitida.", "private": True,})Sincronizar tudo para o BI
Seção intitulada “Sincronizar tudo para o BI”pagina, todas = 1, []while True: r = requests.get(f"{BASE}/conversations", headers=H, params={ "status": "all", # sem isto, só vêm as abertas "page": pagina, }).json() lote = r["data"]["payload"] # repare na camada "data" if not lote: break todas.extend(lote) pagina += 1Próximos passos
Seção intitulada “Próximos passos”- Mensagens — ler e enviar dentro da conversa
- Convenções — envelopes, operadores, erros
- Eventos de webhook — ser avisado em vez de perguntar
- Manual — Atribuir conversa