Pular para o conteúdo

Convenções da API

Esta página é a referência rápida dos padrões que valem em toda a API. Leia antes dos endpoints: metade dos problemas de integração nasce aqui.

https://app.smart2.com.br/api/v1/accounts/{account_id}/{recurso}[/{id}][/{ação}]
GET /api/v1/accounts/1/contacts Lista contatos
GET /api/v1/accounts/1/contacts/4521 Detalhes
POST /api/v1/accounts/1/contacts Cria
PATCH /api/v1/accounts/1/contacts/4521 Atualiza
DELETE /api/v1/accounts/1/contacts/4521 Remove
GET /api/v1/accounts/1/conversations/8821/messages Mensagens da conversa

Alguns recursos ficam aninhados sob o pai — negócios, etapas e itens vivem sob o funil (/pipelines/{id}/deals/...). A página de cada recurso mostra o caminho certo.

VerboUso
GETLer
POSTCriar, e também ações (/change_status, /filter, /toggle_status)
PATCHAtualização parcial
DELETERemover

PUT quase não aparece. E repare que POST faz dois papéis: criar recurso e executar ação.

Vários endpoints esperam os campos dentro de um objeto com o nome do recurso:

{ "deal": { "title": "...", "priority": "high" } }

Outros aceitam os campos no primeiro nível. Quando a página do recurso mostra o embrulho no exemplo, mande com embrulho — sem ele a chamada é recusada com param is missing.

GET /api/v1/accounts/1/contacts?page=2
ParâmetroDetalhe
pageA partir de 1
per_pageSó alguns endpoints aceitam. Em contatos o tamanho é fixo em 15; em negócios vai até 100; em conversas o padrão é 25
page, todos = 1, []
while True:
r = requests.get(f"{BASE_URL}/contacts", headers=HEADERS,
params={"page": page}).json()
itens = r["payload"]
if not itens:
break
todos.extend(itens)
page += 1
print(f"{len(todos)} contatos")

Há três formatos de lista em uso, e vale conferir qual é o do seu endpoint:

// A — o mais comum (contatos, negócios)
{ "meta": { "count": 4521, "current_page": 2 }, "payload": [ ... ] }
// B — conversas, com uma camada a mais e meta VAZIO
{ "data": { "meta": {}, "payload": [ ... ] } }
// C — lista crua (etapas do funil, estatísticas por etapa)
[ { ... }, { ... } ]

No formato B, o total não vem junto: conversas têm um endpoint separado, GET /conversations/meta, que devolve as contagens.

Para item único, a maioria devolve o objeto direto; alguns embrulham em payload. O exemplo da página do recurso mostra qual é.

Filtros simples vão na query:

GET /api/v1/accounts/1/conversations?status=open&assignee_type=assigned&labels[]=vip

Os parâmetros variam por recurso — a página de cada um lista os seus. Alguns comuns em conversas: status, inbox_id, team_id, assignee_type (assigned, unassigned, me), labels[], q (busca no texto das mensagens).

Contatos e conversas têm um endpoint de filtro que aceita várias condições:

Terminal window
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"],
"query_operator": "and"
},
{
"attribute_key": "labels",
"filter_operator": "contains",
"values": ["vip"]
}
]
}'

Os operadores são estes, com estes nomes exatos:

OperadorUso
equal_to · not_equal_toIgualdade
contains · does_not_containTexto ou etiqueta
starts_withComeça com
is_present · is_not_presentCampo preenchido ou não
is_greater_than · is_less_thanNúmero e data
days_beforeData relativa — “há mais de N dias”

Repare no prefixo is_: não é greater_than, é is_greater_than. E não existe between — para faixa, mande duas condições com and.

Nem todo operador vale para todo campo: status só aceita igualdade, por exemplo. Operador inválido derruba a chamada inteira.

Negócios não têm endpoint de filtro. Ali o filtro é por query na listagem — veja Deals.

Em conversas, ordenação é um parâmetro só, com campo e direção juntos:

GET /api/v1/accounts/1/conversations?sort_by=created_at_desc

Valores aceitos: last_activity_at_asc · last_activity_at_desc (padrão) · created_at_asc · created_at_desc · priority_asc · priority_desc · waiting_since_asc · waiting_since_desc. Valor desconhecido não dá erro — cai no padrão silenciosamente.

Em negócios são dois parâmetros: sort_by (o campo) e sort_direction (asc ou desc).

Não há ?include=conversations,labels. Cada resposta traz o que traz — algumas já vêm generosas (o funil devolve as etapas embutidas; o negócio devolve itens, notas e histórico), outras exigem uma segunda chamada.

A exceção é ?include_contact_inboxes=true em contatos, que acrescenta as caixas de entrada do contato.

Na entrada, mande ISO 8601: 2026-09-22T14:30:00Z, ou com fuso explícito 2026-09-22T11:30:00-03:00. Filtros de data por dia aceitam 2026-09-22.

Na saída, o formato varia — e essa é a pegadinha que mais dá trabalho:

OndeFormato
Maioria dos camposISO 8601 em UTC — "2026-09-22T14:30:00.000Z"
Payload de conversa (API e webhook)Número — segundos desde 1970: 1758542400
updated_at da conversaNúmero com fração: 1758542400.0

Sempre converta para o fuso local só na hora de exibir.

São inteiros. Duas ressalvas:

  • Conversa tem dois números. O que você usa na URL e vê na tela é o display_id, que conta a partir de 1 dentro da conta. Existe um id interno diferente, que aparece em alguns lugares. Use sempre o da URL.
  • Negócio traz conversation_display_id, não conversation_id — é o mesmo número da URL da conversa.

São dois formatos diferentes, e o seu tratamento precisa aguentar os dois.

Erro comum — autenticação, permissão, não encontrado, parâmetro faltando:

{ "error": "You are not authorized to do this action" }

Erro de validação (422), quando o dado é inválido:

{
"message": "Email is invalid, Name can't be blank",
"attributes": ["email", "name"]
}
def erro(r):
d = r.json()
if "message" in d:
return d["message"], d.get("attributes", [])
return d.get("error", "erro desconhecido"), []
CódigoQuando
200Sucesso — inclusive em vários DELETE
201Criado
401Token inválido ou ausente
403Sem permissão para essa ação
404Não existe — ou existe em outra conta
422Validação falhou, ou faltou parâmetro obrigatório
429Limite de taxa — corpo em texto puro, não JSON. Veja limites
500Erro nosso

O 403 e o 404 se confundem: pedir um recurso de outra conta devolve 404, não 403.

Não existe Idempotency-Key. Se você mandar esse header, ele é ignorado — e um POST repetido cria um segundo registro.

Então a proteção contra duplicata é sua:

  • Guarde do seu lado o identificador do que já enviou, antes de enviar
  • Em contato, use o campo identifier com a sua chave externa — ele permite reconhecer o mesmo contato depois
  • Antes de recriar depois de um erro de rede, busque para ver se já entrou

Para 5xx e 429, repetir vale a pena, com intervalo crescente:

import time, requests
def com_retry(metodo, url, **kwargs):
espera = 2
for _ in range(5):
try:
r = requests.request(metodo, url, **kwargs)
if r.status_code < 500 and r.status_code != 429:
return r
except requests.exceptions.ConnectionError:
pass
time.sleep(espera)
espera = min(espera * 3, 60)
raise RuntimeError("falhou depois de 5 tentativas")

GET, PATCH e DELETE podem ser repetidos à vontade — o resultado é o mesmo. POST, não.

  • Booleano é true / false, nunca 1 / 0 nem "sim"
  • Lista vazia vem [], objeto vazio vem {}
  • Campo sem valor vem null ou simplesmente não vem — trate os dois
  • Corpo em UTF-8; acentos e emojis passam normalmente

A URL leva /api/v1/. Campo ou endpoint novo entra em v1 sem aviso, então o seu código precisa ignorar campo que não conhece em vez de quebrar.

Cacheie o que não muda. Caixas, etiquetas, funis, etapas e atributos personalizados mudam raramente — leia uma vez e guarde.

Peça só o que precisa. Se quer o total de contatos, leia meta.count da primeira página em vez de baixar tudo.

Combine com webhook. API para buscar, webhook para ser avisado. Lembrando que o webhook é entregue uma vez só — para o que não pode faltar, mantenha a leitura periódica.