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.
Estrutura de URL
Seção intitulada “Estrutura de URL”https://app.smart2.com.br/api/v1/accounts/{account_id}/{recurso}[/{id}][/{ação}]GET /api/v1/accounts/1/contacts Lista contatosGET /api/v1/accounts/1/contacts/4521 DetalhesPOST /api/v1/accounts/1/contacts CriaPATCH /api/v1/accounts/1/contacts/4521 AtualizaDELETE /api/v1/accounts/1/contacts/4521 RemoveGET /api/v1/accounts/1/conversations/8821/messages Mensagens da conversaAlguns 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.
| Verbo | Uso |
|---|---|
GET | Ler |
POST | Criar, e também ações (/change_status, /filter, /toggle_status) |
PATCH | Atualização parcial |
DELETE | Remover |
PUT quase não aparece. E repare que POST faz dois papéis: criar recurso e executar ação.
Corpo das requisições
Seção intitulada “Corpo das requisições”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.
Paginação
Seção intitulada “Paginação”GET /api/v1/accounts/1/contacts?page=2| Parâmetro | Detalhe |
|---|---|
page | A partir de 1 |
per_page | Só 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")Estrutura das respostas
Seção intitulada “Estrutura das respostas”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
Seção intitulada “Filtros”Filtros simples vão na query:
GET /api/v1/accounts/1/conversations?status=open&assignee_type=assigned&labels[]=vipOs 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).
Filtro combinado
Seção intitulada “Filtro combinado”Contatos e conversas têm um endpoint de filtro que aceita várias condições:
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:
| Operador | Uso |
|---|---|
equal_to · not_equal_to | Igualdade |
contains · does_not_contain | Texto ou etiqueta |
starts_with | Começa com |
is_present · is_not_present | Campo preenchido ou não |
is_greater_than · is_less_than | Número e data |
days_before | Data 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.
Ordenação
Seção intitulada “Ordenação”Em conversas, ordenação é um parâmetro só, com campo e direção juntos:
GET /api/v1/accounts/1/conversations?sort_by=created_at_descValores 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 existe expansão de relações
Seção intitulada “Não existe expansão de relações”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:
| Onde | Formato |
|---|---|
| Maioria dos campos | ISO 8601 em UTC — "2026-09-22T14:30:00.000Z" |
| Payload de conversa (API e webhook) | Número — segundos desde 1970: 1758542400 |
updated_at da conversa | Nú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ãoconversation_id— é o mesmo número da URL da conversa.
Formato de erro
Seção intitulada “Formato de erro”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ódigos de status
Seção intitulada “Códigos de status”| Código | Quando |
|---|---|
200 | Sucesso — inclusive em vários DELETE |
201 | Criado |
401 | Token inválido ou ausente |
403 | Sem permissão para essa ação |
404 | Não existe — ou existe em outra conta |
422 | Validação falhou, ou faltou parâmetro obrigatório |
429 | Limite de taxa — corpo em texto puro, não JSON. Veja limites |
500 | Erro nosso |
O 403 e o 404 se confundem: pedir um recurso de outra conta devolve 404, não 403.
Repetir requisição com segurança
Seção intitulada “Repetir requisição com segurança”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
identifiercom 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, nunca1/0nem"sim" - Lista vazia vem
[], objeto vazio vem{} - Campo sem valor vem
nullou 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.
Boas práticas
Seção intitulada “Boas práticas”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.
Próximos passos
Seção intitulada “Próximos passos”- Limites de requisição
- Autenticação
- Contatos — o primeiro recurso prático