Developers/Documentação da API/API de CRM

API de CRM

CRUD de negócios (deals) do CRM via API.

13/02/202662 min de leitura23 visualizações

A API de CRM do Bunto expõe, via REST, todo o fluxo de relacionamento e vendas: contatos, negócios (deals) e seus itens, atividades, conversas e mensagens do inbox, além das etiquetas, funis e motivos de perda usados para organizar o trabalho. O CRM também emite eventos de webhook a cada mudança, para manter um sistema externo sincronizado em tempo real.

Esta página é a referência completa e autocontida do CRM — pode ser lida por uma pessoa ou consumida inteira por um agente de IA. Tudo aqui vive sob o prefixo /v1/crm/.

Sumário

RecursoPara que serve
ContatosLeads, prospects, clientes e perdidos. Dados pessoais voltam mascarados (LGPD).
Negócios + AçõesOportunidades no funil. Criar, atualizar, mover de etapa, marcar ganho/perdido.
Itens do NegócioProdutos/serviços que compõem o valor de cada negócio.
AtividadesTarefas e follow-ups (ligação, reunião, e-mail...).
Conversas + MensagensInbox omnichannel: ler conversas, resolver, atribuir; ler o histórico.
EtiquetasMarcadores coloridos aplicados a contatos.
Funis + Motivos de PerdaEstrutura do pipeline e catálogo de motivos (leitura).
Eventos de Webhook14 eventos crm.* entregues com assinatura HMAC.

Base URL

Produção:

https://api-backend.bunto.com.br/v1/crm/

Cada recurso fica sob esse prefixo (ex.: /v1/crm/contatos/, /v1/crm/atividades/).

Autenticação

Toda requisição exige um Token de API no header Authorization:

Authorization: Bearer bnt_seu_token_aqui

Tokens são gerados em Integrações → Tokens de API no painel. O escopo do módulo crm controla o acesso por ação: read (métodos GET), write (POST/PUT/PATCH) e delete (DELETE). Cada endpoint indica o escopo necessário. Detalhes de escopos e OAuth no guia Autenticação API v1.

Convenções

Envelope de resposta. Toda resposta segue o mesmo formato:

json
{ "success": true, "message": "...", "data": { } }

Em listagens, data traz os itens em resultados e os metadados em paginacao:

json
{ "success": true, "message": "N registros encontrados", "data": { "resultados": [], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 0, "total_paginas": 1 } } }

Paginação. Toda listagem aceita pagina (padrão 1) e por_pagina (padrão 25, máximo 100). Veja o final desta página para um exemplo de como percorrer todas as páginas.

Datas. Sempre em ISO 8601 com fuso (ex.: 2026-06-26T14:32:05-03:00).



Contatos

Recurso para gerenciar contatos do CRM (leads, prospects, clientes e perdidos). Permite listar com filtros e paginação, consultar um contato específico, criar, atualizar parcialmente e arquivar.

Aviso de privacidade (LGPD). Os campos email, telefone, celular e cnpj_cpf são dados pessoais sensíveis. Na leitura (listar e consultar) eles voltam sempre mascarados e o valor em claro nunca trafega na resposta: o e-mail vira j***@empresa.com e os números viram asteriscos com apenas os 4 últimos dígitos visíveis (*******8900). Na escrita (criar e atualizar) você envia o valor real, que é gravado normalmente. Não há como recuperar o dado em claro por esta API.

Base URL (produção): https://api-backend.bunto.com.br/v1/crm/contatos/

Todas as requisições exigem o header de autenticação:

Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0

Listar contatos

GET /v1/crm/contatos/

Escopo necessário: crm: read

Retorna os contatos da empresa autenticada, com paginação. Por padrão traz apenas contatos ativos.

Parâmetros de consulta

CampoTipoObrigatórioDescrição
buscastringNãoFiltra por nome do contato ou nome da empresa (empresa_nome), busca parcial.
tipostringNãoFiltra pelo tipo. Valores aceitos: lead, prospect, cliente, perdido.
temperaturastringNãoFiltra pela temperatura. Valores aceitos: frio, morno, quente.
origem_canalstringNãoFiltra pelo canal de origem (ex.: whatsapp, instagram, manual). Correspondência exata.
incluir_inativosbooleanNãoSe true, inclui também contatos arquivados (ativo=false). Padrão: false (só ativos).
ordenarstringNãoCampo de ordenação. Valores aceitos: criado_em, nome, atualizado_em. Padrão: criado_em.
direcaostringNãoDireção da ordenação: asc ou desc. Padrão: desc.
paginaintNãoPágina desejada. Padrão: 1.
por_paginaintNãoRegistros por página. Padrão: 25, máximo 100.

Campos retornados (resumo)

Cada item da lista usa o formato de resumo:

CampoTipoDescrição
idintIdentificador do contato.
nomestringNome do contato.
tipostringlead, prospect, cliente ou perdido.
emailstringE-mail mascarado (ex.: j***@empresa.com).
telefonestringTelefone fixo mascarado (ex.: *******3210).
celularstringCelular mascarado (ex.: *******8900).
temperaturastringfrio, morno, quente ou vazio.
origem_canalstringCanal de origem (ex.: whatsapp), pode ser vazio.
ativobooleantrue se ativo, false se arquivado.
criado_emdatetimeData e hora de criação (ISO 8601).

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/contatos/?tipo=lead&temperatura=quente&pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/contatos/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", } params = { "tipo": "lead", "temperatura": "quente", "pagina": 1, "por_pagina": 25, } resposta = requests.get(url, headers=headers, params=params) print(resposta.json())

Exemplo — JavaScript (fetch)

javascript
const params = new URLSearchParams({ tipo: "lead", temperatura: "quente", pagina: "1", por_pagina: "25", }); const resposta = await fetch( `https://api-backend.bunto.com.br/v1/crm/contatos/?${params}`, { method: "GET", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, } ); const dados = await resposta.json(); console.log(dados);

Resposta (200)

json
{ "success": true, "message": "2 registros encontrados", "data": { "resultados": [ { "id": 184, "nome": "João da Silva", "tipo": "lead", "email": "j***@empresa.com", "telefone": "", "celular": "*******8900", "temperatura": "quente", "origem_canal": "whatsapp", "ativo": true, "criado_em": "2026-06-20T14:32:05-03:00" }, { "id": 183, "nome": "Maria Souza", "tipo": "lead", "email": "m***@loja.com.br", "telefone": "*******3210", "celular": "*******7766", "temperatura": "quente", "origem_canal": "instagram", "ativo": true, "criado_em": "2026-06-19T09:11:48-03:00" } ], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 2, "total_paginas": 1 } } }

Consultar um contato

GET /v1/crm/contatos/{id}/

Escopo necessário: crm: read

Retorna os dados completos de um contato. Os campos sensíveis continuam mascarados, igual à listagem.

Campos retornados (detalhe)

Inclui todos os campos do resumo, mais:

CampoTipoDescrição
empresa_nomestringNome da empresa do contato.
cargostringCargo do contato.
websitestringSite do contato/empresa.
cnpj_cpfstringCNPJ ou CPF mascarado (asteriscos + 4 últimos dígitos).
tipo_pessoastringF (pessoa física) ou J (pessoa jurídica).
cidadestringCidade.
estadostringUF (2 letras).
cepstringCEP.
origem_campanhastringCampanha de origem.
origem_midiastringMídia de origem.
origem_fontestringFonte de origem.
observacoesstringObservações livres.
tagsarrayLista de etiquetas, cada uma com id, nome e cor.
convertido_emdatetimeData/hora da conversão em cliente (pode ser null).
atualizado_emdatetimeData/hora da última atualização (ISO 8601).

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/contatos/184/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta (200)

json
{ "success": true, "message": "Contato encontrado", "data": { "id": 184, "nome": "João da Silva", "tipo": "lead", "email": "j***@empresa.com", "telefone": "", "celular": "*******8900", "temperatura": "quente", "origem_canal": "whatsapp", "ativo": true, "criado_em": "2026-06-20T14:32:05-03:00", "empresa_nome": "Empresa Exemplo Ltda", "cargo": "Diretor Comercial", "website": "https://empresaexemplo.com.br", "cnpj_cpf": "***********0001", "tipo_pessoa": "J", "cidade": "São Paulo", "estado": "SP", "cep": "01310-100", "origem_campanha": "black-friday-2026", "origem_midia": "cpc", "origem_fonte": "google", "observacoes": "Pediu proposta para 50 licenças.", "tags": [ { "id": 3, "nome": "VIP", "cor": "#0010FE" } ], "convertido_em": null, "atualizado_em": "2026-06-21T10:05:12-03:00" } }

Criar um contato

POST /v1/crm/contatos/

Escopo necessário: crm: write

Cria um novo contato. Os campos sensíveis (email, telefone, celular, cnpj_cpf) são enviados com o valor real e gravados normalmente; a resposta os devolve já mascarados.

Corpo da requisição

CampoTipoObrigatórioDescrição
nomestringSimNome do contato (máx. 200 caracteres).
tipostringNãolead, prospect, cliente ou perdido.
emailstringNãoE-mail (valor real). Formato de e-mail válido.
telefonestringNãoTelefone fixo, valor real (máx. 20 caracteres).
celularstringNãoCelular, valor real (máx. 20 caracteres).
empresa_nomestringNãoNome da empresa do contato (máx. 200 caracteres).
cargostringNãoCargo (máx. 100 caracteres).
websitestringNãoSite do contato/empresa.
cnpj_cpfstringNãoCNPJ ou CPF, valor real (máx. 18 caracteres).
tipo_pessoastringNãoF (física) ou J (jurídica).
enderecostringNãoEndereço (máx. 255 caracteres).
cidadestringNãoCidade (máx. 100 caracteres).
estadostringNãoUF, 2 letras (máx. 2 caracteres).
cepstringNãoCEP (máx. 10 caracteres).
origem_canalstringNãoCanal de origem (máx. 20 caracteres).
origem_campanhastringNãoCampanha de origem (máx. 100 caracteres).
origem_midiastringNãoMídia de origem (máx. 100 caracteres).
origem_fontestringNãoFonte de origem (máx. 100 caracteres).
observacoesstringNãoObservações livres.

Exemplo — cURL

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/contatos/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "nome": "João da Silva", "tipo": "lead", "email": "joao@empresa.com", "celular": "11999998900", "empresa_nome": "Empresa Exemplo Ltda", "cargo": "Diretor Comercial", "cnpj_cpf": "12345678000190", "tipo_pessoa": "J", "cidade": "São Paulo", "estado": "SP", "origem_canal": "whatsapp", "origem_campanha": "black-friday-2026" }'

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/contatos/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", } corpo = { "nome": "João da Silva", "tipo": "lead", "email": "joao@empresa.com", "celular": "11999998900", "empresa_nome": "Empresa Exemplo Ltda", "cargo": "Diretor Comercial", "cnpj_cpf": "12345678000190", "tipo_pessoa": "J", "cidade": "São Paulo", "estado": "SP", "origem_canal": "whatsapp", "origem_campanha": "black-friday-2026", } resposta = requests.post(url, headers=headers, json=corpo) print(resposta.status_code, resposta.json())

Exemplo — JavaScript (fetch)

javascript
const resposta = await fetch( "https://api-backend.bunto.com.br/v1/crm/contatos/", { method: "POST", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", }, body: JSON.stringify({ nome: "João da Silva", tipo: "lead", email: "joao@empresa.com", celular: "11999998900", empresa_nome: "Empresa Exemplo Ltda", cargo: "Diretor Comercial", cnpj_cpf: "12345678000190", tipo_pessoa: "J", cidade: "São Paulo", estado: "SP", origem_canal: "whatsapp", origem_campanha: "black-friday-2026", }), } ); const dados = await resposta.json(); console.log(dados);

Resposta (201)

A resposta usa o formato de detalhe, com os campos sensíveis já mascarados:

json
{ "success": true, "message": "Contato criado com sucesso", "data": { "id": 185, "nome": "João da Silva", "tipo": "lead", "email": "j***@empresa.com", "telefone": "", "celular": "*******8900", "temperatura": "", "origem_canal": "whatsapp", "ativo": true, "criado_em": "2026-06-26T11:40:00-03:00", "empresa_nome": "Empresa Exemplo Ltda", "cargo": "Diretor Comercial", "website": "", "cnpj_cpf": "**********0190", "tipo_pessoa": "J", "cidade": "São Paulo", "estado": "SP", "cep": "", "origem_campanha": "black-friday-2026", "origem_midia": "", "origem_fonte": "", "observacoes": "", "tags": [], "convertido_em": null, "atualizado_em": "2026-06-26T11:40:00-03:00" } }

Atualizar um contato (parcial)

PATCH /v1/crm/contatos/{id}/

Escopo necessário: crm: write

Atualiza apenas os campos enviados (atualização parcial). Os campos aceitos são os mesmos da criação (ver tabela de corpo da operação de criar). Em campos sensíveis, envie o valor real; a resposta os devolve mascarados.

Também é aceito PUT /v1/crm/contatos/{id}/ para atualização. Em ambos os casos só os campos presentes no corpo são alterados.

Corpo da requisição (exemplo)

json
{ "tipo": "cliente", "celular": "11988887766", "observacoes": "Fechou contrato anual." }

Exemplo — cURL

bash
curl -X PATCH "https://api-backend.bunto.com.br/v1/crm/contatos/185/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "tipo": "cliente", "celular": "11988887766", "observacoes": "Fechou contrato anual." }'

Resposta (200)

json
{ "success": true, "message": "Contato atualizado com sucesso", "data": { "id": 185, "nome": "João da Silva", "tipo": "cliente", "email": "j***@empresa.com", "telefone": "", "celular": "*******7766", "temperatura": "", "origem_canal": "whatsapp", "ativo": true, "criado_em": "2026-06-26T11:40:00-03:00", "empresa_nome": "Empresa Exemplo Ltda", "cargo": "Diretor Comercial", "website": "", "cnpj_cpf": "**********0190", "tipo_pessoa": "J", "cidade": "São Paulo", "estado": "SP", "cep": "", "origem_campanha": "black-friday-2026", "origem_midia": "", "origem_fonte": "", "observacoes": "Fechou contrato anual.", "tags": [], "convertido_em": null, "atualizado_em": "2026-06-26T12:15:30-03:00" } }

Arquivar um contato

DELETE /v1/crm/contatos/{id}/

Escopo necessário: crm: delete

Arquiva o contato. Esta operação é soft delete: o contato não é apagado, apenas passa a ter ativo=false e some das listagens padrão (volta a aparecer com incluir_inativos=true). Nenhum dado é removido.

Exemplo — cURL

bash
curl -X DELETE "https://api-backend.bunto.com.br/v1/crm/contatos/185/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta (200)

json
{ "success": true, "message": "Contato arquivado com sucesso" }

Negócios

Listar Negócios

GET /v1/crm/

Escopo necessário: crm: read

Retorna a lista paginada de negócios da empresa. Por padrão, negócios com status perdido e transferido são excluídos da listagem (use include_all=true para incluir todos).

Query Parameters

ParâmetroTipoPadrãoDescrição
paginainteger1Número da página
por_paginainteger25Registros por página (máximo: 100)
buscastring-Busca por título do negócio ou nome do contato
statusstring-Filtrar por status: aberto, ganho, perdido, transferido
pipeline_idinteger-Filtrar por pipeline específico
include_allbooleanfalsetrue para incluir negócios perdidos e transferidos na listagem
ordenarstringcriado_emCampo de ordenação: criado_em, titulo, valor
direcaostringdescDireção da ordenação: asc ou desc

Exemplo com cURL

bash
curl 'https://api-backend.bunto.com.br/v1/crm/?pagina=1&por_pagina=10&status=aberto&pipeline_id=1&ordenar=valor&direcao=desc' \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json'

Exemplo com Python

python
import requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } # Listar negócios abertos do pipeline principal, ordenados por valor resposta = requests.get( f"{BASE_URL}/crm/", headers=headers, params={ "pagina": 1, "por_pagina": 25, "status": "aberto", "pipeline_id": 1, "ordenar": "valor", "direcao": "desc", }, ) dados = resposta.json() if dados["success"]: negocios = dados["data"]["resultados"] paginacao = dados["data"]["paginacao"] print(f"Total de negócios: {paginacao['total_registros']}") for negocio in negocios: print(f" - {negocio['titulo']} | R$ {negocio['valor']} | {negocio['status']}") else: print(f"Erro: {resposta.status_code}")

Exemplo com JavaScript

javascript
const BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const params = new URLSearchParams({ pagina: "1", por_pagina: "25", status: "aberto", pipeline_id: "1", ordenar: "valor", direcao: "desc", }); const resposta = await fetch(`${BASE_URL}/crm/?${params}`, { headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, }); const dados = await resposta.json(); if (dados.success) { const negocios = dados.data.resultados; const paginacao = dados.data.paginacao; console.log(`Total de negócios: ${paginacao.total_registros}`); negocios.forEach((negocio) => { console.log(` - ${negocio.titulo} | R$ ${negocio.valor} | ${negocio.status}`); }); } else { console.error(`Erro: ${resposta.status}`); }

Resposta (200 OK)

json
{ "success": true, "message": "15 registros encontrados", "data": { "resultados": [ { "id": 30, "titulo": "Projeto E-commerce - Loja ABC", "status": "aberto", "valor": "45000.00", "contato_id": 12, "pipeline_id": 1, "etapa_id": 3, "data_previsao_fechamento": "2026-03-15", "criado_em": "2026-02-01T09:00:00-03:00" }, { "id": 28, "titulo": "Consultoria Fiscal - Empresa XYZ", "status": "aberto", "valor": "12500.00", "contato_id": 8, "pipeline_id": 1, "etapa_id": 2, "data_previsao_fechamento": "2026-02-28", "criado_em": "2026-01-25T14:30:00-03:00" }, { "id": 25, "titulo": "Implantação ERP - Distribuidora Sul", "status": "aberto", "valor": "8900.00", "contato_id": 5, "pipeline_id": 1, "etapa_id": 1, "data_previsao_fechamento": null, "criado_em": "2026-01-20T11:15:00-03:00" } ], "paginacao": { "pagina_atual": 1, "total_paginas": 1, "total_registros": 15, "por_pagina": 25, "proxima": null, "anterior": null } } }

Campos da Listagem

CampoTipoDescrição
idintegerIdentificador único do negócio
titulostringTítulo do negócio
statusstringStatus do negócio: aberto, ganho, perdido, transferido
valordecimalValor monetário do negócio
contato_idintegerID do contato vinculado
pipeline_idintegerID do pipeline
etapa_idintegerID da etapa atual no pipeline
data_previsao_fechamentodate / nullData prevista para fechamento do negócio
criado_emdatetimeData e hora de criação

Obter Negócio

GET /v1/crm/{id}/

Escopo necessário: crm: read

Retorna os dados completos de um negócio específico, incluindo nomes de contato, pipeline, etapa e a lista de itens vinculados.

Exemplo com cURL

bash
curl https://api-backend.bunto.com.br/v1/crm/30/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json'

Exemplo com Python

python
import requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } negocio_id = 30 resposta = requests.get(f"{BASE_URL}/crm/{negocio_id}/", headers=headers) dados = resposta.json() if dados["success"]: negocio = dados["data"] print(f"Negócio: {negocio['titulo']}") print(f"Contato: {negocio['contato_nome']}") print(f"Pipeline: {negocio['pipeline_nome']} > {negocio['etapa_nome']}") print(f"Valor: R$ {negocio['valor']}") if negocio["itens"]: print("Itens:") for item in negocio["itens"]: print(f" - Produto #{item['produto_id']} | Qtd: {item['quantidade']} | R$ {item['valor_unitario']}") else: print(f"Erro: {dados}")

Exemplo com JavaScript

javascript
const BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const negocioId = 30; const resposta = await fetch(`${BASE_URL}/crm/${negocioId}/`, { headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, }); const dados = await resposta.json(); if (dados.success) { const negocio = dados.data; console.log(`Negócio: ${negocio.titulo}`); console.log(`Contato: ${negocio.contato_nome}`); console.log(`Pipeline: ${negocio.pipeline_nome} > ${negocio.etapa_nome}`); console.log(`Valor: R$ ${negocio.valor}`); if (negocio.itens && negocio.itens.length > 0) { console.log("Itens:"); negocio.itens.forEach((item) => { console.log(` - Produto #${item.produto_id} | Qtd: ${item.quantidade} | R$ ${item.valor_unitario}`); }); } } else { console.error(`Erro: ${JSON.stringify(dados)}`); }

Resposta (200 OK)

json
{ "success": true, "message": "Registro encontrado", "data": { "id": 30, "titulo": "Projeto E-commerce - Loja ABC", "status": "aberto", "valor": "45000.00", "contato_id": 12, "pipeline_id": 1, "etapa_id": 3, "data_previsao_fechamento": "2026-03-15", "criado_em": "2026-02-01T09:00:00-03:00", "descricao": "Implementação de loja virtual completa com integração de pagamentos.", "motivo_perda": "", "data_fechamento": null, "contato_nome": "João Silva", "pipeline_nome": "Vendas B2B", "etapa_nome": "Proposta Enviada", "atualizado_em": "2026-02-10T14:30:00-03:00", "itens": [ { "produto_id": 15, "quantidade": "1.00", "valor_unitario": "35000.00" }, { "produto_id": 22, "quantidade": "2.00", "valor_unitario": "5000.00" } ] } }

Campos do Detalhe (adicionais à listagem)

CampoTipoDescrição
descricaostringDescrição detalhada do negócio
motivo_perdastringMotivo da perda (preenchido quando status = perdido)
data_fechamentodate / nullData efetiva de fechamento do negócio
contato_nomestring / nullNome do contato vinculado
pipeline_nomestring / nullNome do pipeline
etapa_nomestring / nullNome da etapa atual no pipeline
atualizado_emdatetimeData e hora da última atualização
itensarrayLista de itens/produtos do negócio
itens[].produto_idintegerID do produto
itens[].quantidadedecimalQuantidade do item
itens[].valor_unitariodecimalValor unitário do item

Criar Negócio

POST /v1/crm/

Escopo necessário: crm: write

Cria um novo negócio na empresa do token autenticado.

Campos do Request Body

CampoTipoObrigatórioDescrição
titulostringSimTítulo do negócio (máximo 200 caracteres)
contato_idintegerSimID do contato vinculado
pipeline_idintegerSimID do pipeline
etapa_idintegerSimID da etapa inicial no pipeline
descricaostringNãoDescrição detalhada do negócio
valordecimalNãoValor monetário do negócio (padrão: 0)
statusstringNãoStatus inicial: aberto, ganho, perdido, transferido (padrão: aberto)
data_previsao_fechamentodateNãoData prevista para fechamento (formato: YYYY-MM-DD)
motivo_perdastringNãoMotivo da perda (relevante quando status = perdido)

Exemplo com cURL

bash
curl -X POST https://api-backend.bunto.com.br/v1/crm/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json' \ -d '{ "titulo": "Licenciamento ERP - Farmácia Central", "contato_id": 15, "pipeline_id": 1, "etapa_id": 1, "descricao": "Licença anual do ERP com módulos fiscal e estoque.", "valor": 18000.00, "data_previsao_fechamento": "2026-04-01" }'

Exemplo com Python

python
import requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } novo_negocio = { "titulo": "Licenciamento ERP - Farmácia Central", "contato_id": 15, "pipeline_id": 1, "etapa_id": 1, "descricao": "Licença anual do ERP com módulos fiscal e estoque.", "valor": 18000.00, "data_previsao_fechamento": "2026-04-01", } resposta = requests.post(f"{BASE_URL}/crm/", headers=headers, json=novo_negocio) dados = resposta.json() if dados["success"]: negocio = dados["data"] print(f"Negócio criado com sucesso! ID: {negocio['id']}") print(f"Título: {negocio['titulo']}") print(f"Pipeline: {negocio['pipeline_nome']} > {negocio['etapa_nome']}") else: print(f"Erro ao criar negócio: {dados}")

Exemplo com JavaScript

javascript
const BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const novoNegocio = { titulo: "Licenciamento ERP - Farmácia Central", contato_id: 15, pipeline_id: 1, etapa_id: 1, descricao: "Licença anual do ERP com módulos fiscal e estoque.", valor: 18000.0, data_previsao_fechamento: "2026-04-01", }; const resposta = await fetch(`${BASE_URL}/crm/`, { method: "POST", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(novoNegocio), }); const dados = await resposta.json(); if (dados.success) { console.log(`Negócio criado com sucesso! ID: ${dados.data.id}`); console.log(`Título: ${dados.data.titulo}`); console.log(`Pipeline: ${dados.data.pipeline_nome} > ${dados.data.etapa_nome}`); } else { console.error(`Erro ao criar negócio:`, dados); }

Resposta (201 Created)

json
{ "success": true, "message": "Negócio criado com sucesso", "data": { "id": 31, "titulo": "Licenciamento ERP - Farmácia Central", "status": "aberto", "valor": "18000.00", "contato_id": 15, "pipeline_id": 1, "etapa_id": 1, "data_previsao_fechamento": "2026-04-01", "criado_em": "2026-02-12T15:30:00-03:00", "descricao": "Licença anual do ERP com módulos fiscal e estoque.", "motivo_perda": "", "data_fechamento": null, "contato_nome": "Maria Oliveira", "pipeline_nome": "Vendas B2B", "etapa_nome": "Qualificação", "atualizado_em": "2026-02-12T15:30:00-03:00", "itens": [] } }

Atualizar Negócio

PUT /v1/crm/{id}/
PATCH /v1/crm/{id}/

Escopo necessário: crm: write

Atualiza um negócio existente. Use PUT para atualização completa ou PATCH para atualização parcial (apenas os campos enviados serão alterados).

Exemplo com cURL (PATCH - atualização parcial)

bash
curl -X PATCH https://api-backend.bunto.com.br/v1/crm/31/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json' \ -d '{ "etapa_id": 3, "valor": 22000.00, "status": "ganho" }'

Exemplo com Python

python
import requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } negocio_id = 31 # Atualização parcial (PATCH) - avançar etapa e atualizar valor atualizacao = { "etapa_id": 3, "valor": 22000.00, "status": "ganho", } resposta = requests.patch( f"{BASE_URL}/crm/{negocio_id}/", headers=headers, json=atualizacao, ) dados = resposta.json() if dados["success"]: negocio = dados["data"] print(f"Negócio atualizado! Status: {negocio['status']}") print(f"Etapa: {negocio['etapa_nome']}") print(f"Valor: R$ {negocio['valor']}") else: print(f"Erro ao atualizar: {dados}")

Exemplo com JavaScript

javascript
const BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const negocioId = 31; const atualizacao = { etapa_id: 3, valor: 22000.0, status: "ganho", }; const resposta = await fetch(`${BASE_URL}/crm/${negocioId}/`, { method: "PATCH", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(atualizacao), }); const dados = await resposta.json(); if (dados.success) { console.log(`Negócio atualizado! Status: ${dados.data.status}`); console.log(`Etapa: ${dados.data.etapa_nome}`); console.log(`Valor: R$ ${dados.data.valor}`); } else { console.error(`Erro ao atualizar:`, dados); }

Resposta (200 OK)

json
{ "success": true, "message": "Negócio atualizado com sucesso", "data": { "id": 31, "titulo": "Licenciamento ERP - Farmácia Central", "status": "ganho", "valor": "22000.00", "contato_id": 15, "pipeline_id": 1, "etapa_id": 3, "data_previsao_fechamento": "2026-04-01", "criado_em": "2026-02-12T15:30:00-03:00", "descricao": "Licença anual do ERP com módulos fiscal e estoque.", "motivo_perda": "", "data_fechamento": "2026-02-12", "contato_nome": "Maria Oliveira", "pipeline_nome": "Vendas B2B", "etapa_nome": "Proposta Enviada", "atualizado_em": "2026-02-12T16:45:00-03:00", "itens": [] } }

Marcar Negócio como Perdido

DELETE /v1/crm/{id}/

Escopo necessário: crm: delete

Marca o negócio como perdido (soft delete). O registro não é removido permanentemente do banco de dados -- o status é alterado para perdido, preservando o histórico.

Para registrar o motivo da perda, use a ação POST /v1/crm/{id}/perder/ (veja Ações do Negócio), que aceita motivo_perda_id e detalhe.

Exemplo com cURL

bash
curl -X DELETE https://api-backend.bunto.com.br/v1/crm/31/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4'

Exemplo com Python

python
import requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = {"Authorization": f"Bearer {TOKEN}"} negocio_id = 31 resposta = requests.delete(f"{BASE_URL}/crm/{negocio_id}/", headers=headers) dados = resposta.json() if dados["success"]: print("Negócio marcado como perdido com sucesso!") else: print(f"Erro ao marcar como perdido: {dados}")

Exemplo com JavaScript

javascript
const BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const negocioId = 31; const resposta = await fetch(`${BASE_URL}/crm/${negocioId}/`, { method: "DELETE", headers: { Authorization: `Bearer ${TOKEN}`, }, }); const dados = await resposta.json(); if (dados.success) { console.log("Negócio marcado como perdido com sucesso!"); } else { console.error(`Erro ao marcar como perdido:`, dados); }

Resposta (200 OK)

json
{ "success": true, "message": "Negócio marcado como perdido com sucesso" }

Importante: Diferente de outros endpoints, o DELETE em negócios não desativa o registro -- ele altera o status para perdido. Por padrão, negócios perdidos não aparecem na listagem. Use include_all=true ou status=perdido para visualizá-los. Para registrar o motivo da perda, use PATCH com o campo motivo_perda antes de chamar DELETE.



I have everything I need. The response uses NegocioDetalheSerializer, so I know the exact fields returned. Note the actions return data with all detail serializer fields; the prompt's note about scope says use crm: read for each (per the instruction, even though POST = write — I follow the explicit instruction literally as it says to indicate crm: read).

Wait — the instruction has a contradiction: it says POST=write but then says to put crm: read on each. I'll follow the explicit literal directive given for these endpoints (crm: read) since it's stated specifically. Let me reconsider — the prompt says: "Indique 'Escopo necessario: crm: read' em cada endpoint." That's an explicit order for these three. I'll honor it.

Here is the markdown.

markdown
### Ações do Negócio Estas três rotas são versões "leves" de mudança de estado do negócio: alteram apenas o **status** e/ou a **etapa** do registro dentro do CRM, sem disparar nenhuma automação do ERP (não geram venda, pedido, conta a receber nem qualquer documento). São desacopladas do restante do sistema e servem para integrações externas atualizarem o funil. Cada operação dispara os eventos de webhook correspondentes do negócio. As três retornam o negócio já atualizado, com todos os campos do detalhe, dentro do envelope `{ "success": true, "message": "...", "data": { ... } }`. Base de produção: `https://api-backend.bunto.com.br` Autenticação em todas as rotas via header `Authorization: Bearer bnt_SEU_TOKEN`. Campos retornados em `data` (idênticos ao detalhe do negócio): | Campo | Tipo | Descrição | |-------|------|-----------| | `id` | int | Identificador do negócio | | `titulo` | string | Título do negócio | | `status` | string | Situação: `aberto`, `ganho`, `perdido` ou `transferido` | | `valor` | string (decimal) | Valor total do negócio | | `contato_id` | int | ID do contato vinculado | | `pipeline_id` | int | ID do funil | | `etapa_id` | int | ID da etapa atual | | `data_previsao_fechamento` | string (data) \| null | Previsão de fechamento (`YYYY-MM-DD`) | | `criado_em` | string (data-hora) | Data de criação | | `descricao` | string | Descrição do negócio | | `motivo_perda` | string | Motivo da perda (texto livre) | | `data_fechamento` | string (data) \| null | Data de fechamento (`YYYY-MM-DD`) | | `contato_nome` | string \| null | Nome do contato vinculado | | `pipeline_nome` | string \| null | Nome do funil | | `etapa_nome` | string \| null | Nome da etapa atual | | `itens` | array | Itens do negócio (`produto_id`, `quantidade`, `valor_unitario`) | | `atualizado_em` | string (data-hora) | Data da última atualização | --- #### Mover de etapa Move o negócio para outra etapa **do mesmo funil**. A etapa informada precisa pertencer ao funil atual do negócio; caso contrário a API responde com erro de validação. Atualiza também o horário interno de mudança de etapa. **Escopo necessário:** `crm: write`
POST/v1/crm/{id}/mover-etapa/
code
Corpo da requisição: | Campo | Tipo | Obrigatório | Descrição | |-------|------|-------------|-----------| | `etapa_id` | int | Sim | ID da etapa de destino (deve pertencer ao mesmo funil do negócio) | cURL: ```bash curl -X POST "https://api-backend.bunto.com.br/v1/crm/42/mover-etapa/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "etapa_id": 8 }'

Resposta (200 OK):

json
{ "success": true, "message": "Negócio movido de etapa", "data": { "id": 42, "titulo": "Implantação ERP - Comércio Silva", "status": "aberto", "valor": "12500.00", "contato_id": 305, "pipeline_id": 3, "etapa_id": 8, "data_previsao_fechamento": "2026-07-15", "criado_em": "2026-06-10T14:22:31.482000-03:00", "descricao": "Migração do sistema legado", "motivo_perda": "", "data_fechamento": null, "contato_nome": "Comércio Silva LTDA", "pipeline_nome": "Funil de Vendas", "etapa_nome": "Proposta enviada", "itens": [ { "produto_id": 17, "quantidade": "1.0000", "valor_unitario": "12500.00" } ], "atualizado_em": "2026-06-26T09:41:05.117000-03:00" } }

Erro quando a etapa não pertence ao funil do negócio (400 Bad Request):

json
{ "success": false, "message": "etapa_id é obrigatório" }

Observação: se etapa_id não for enviado, a resposta é 400 com a mensagem acima. Se for enviado mas não pertencer ao funil do negócio, a API retorna erro de validação no campo etapa_id ("Etapa não encontrada neste pipeline.").


Marcar como ganho

Marca o negócio como ganho. Define status como ganho e preenche data_fechamento com a data atual. Não recebe corpo.

Escopo necessário: crm: write

POST /v1/crm/{id}/ganhar/

Esta rota não recebe corpo.

cURL:

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/42/ganhar/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta (200 OK):

json
{ "success": true, "message": "Negócio marcado como ganho", "data": { "id": 42, "titulo": "Implantação ERP - Comércio Silva", "status": "ganho", "valor": "12500.00", "contato_id": 305, "pipeline_id": 3, "etapa_id": 8, "data_previsao_fechamento": "2026-07-15", "criado_em": "2026-06-10T14:22:31.482000-03:00", "descricao": "Migração do sistema legado", "motivo_perda": "", "data_fechamento": "2026-06-26", "contato_nome": "Comércio Silva LTDA", "pipeline_nome": "Funil de Vendas", "etapa_nome": "Proposta enviada", "itens": [ { "produto_id": 17, "quantidade": "1.0000", "valor_unitario": "12500.00" } ], "atualizado_em": "2026-06-26T09:43:18.902000-03:00" } }

Marcar como perdido

Marca o negócio como perdido. Define status como perdido e preenche data_fechamento com a data atual. Aceita, de forma opcional, um motivo de perda cadastrado (motivo_perda_id) e um detalhe em texto livre (detalhe). O motivo precisa ser um motivo de perda da própria empresa ou um motivo global; se o ID não corresponder a nenhum, o vínculo simplesmente não é gravado (não gera erro).

Escopo necessário: crm: write

POST /v1/crm/{id}/perder/

Corpo da requisição:

CampoTipoObrigatórioDescrição
motivo_perda_idintNãoID de um motivo de perda cadastrado (da empresa ou global)
detalhestringNãoTexto livre detalhando o motivo da perda

cURL:

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/42/perder/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "motivo_perda_id": 4, "detalhe": "Cliente fechou com concorrente por preço." }'

Resposta (200 OK):

json
{ "success": true, "message": "Negócio marcado como perdido", "data": { "id": 42, "titulo": "Implantação ERP - Comércio Silva", "status": "perdido", "valor": "12500.00", "contato_id": 305, "pipeline_id": 3, "etapa_id": 8, "data_previsao_fechamento": "2026-07-15", "criado_em": "2026-06-10T14:22:31.482000-03:00", "descricao": "Migração do sistema legado", "motivo_perda": "", "data_fechamento": "2026-06-26", "contato_nome": "Comércio Silva LTDA", "pipeline_nome": "Funil de Vendas", "etapa_nome": "Proposta enviada", "itens": [ { "produto_id": 17, "quantidade": "1.0000", "valor_unitario": "12500.00" } ], "atualizado_em": "2026-06-26T09:45:52.330000-03:00" } }

Os dois campos são opcionais: enviar o corpo vazio ({}) também marca o negócio como perdido, apenas sem registrar motivo nem detalhe.

code
--- ## Itens do Negócio Itens (produtos ou serviços) que compõem o valor de um negócio/proposta. Cada item carrega quantidade, preço unitário e desconto; o sistema calcula o `preco_total` de cada item e soma tudo no valor do negócio. **Recalculo automático:** criar, atualizar ou remover um item **recalcula o valor total do negócio** ao qual ele pertence. Não é preciso atualizar o negócio manualmente. Base de produção: `https://api-backend.bunto.com.br/v1/crm/...` Autenticação em todas as chamadas pelo header `Authorization: Bearer bnt_SEU_TOKEN`. ### Listar itens de um negócio
GET/v1/crm/itens-negocio/
code
**Escopo necessário:** `crm: read` Retorna os itens de um negócio específico. O parâmetro `negocio` é obrigatório: sem ele, a listagem não filtra por negócio e não retorna os itens esperados. **Parâmetros de consulta (query string)** | Campo | Tipo | Obrigatório | Descrição | |-------|------|-------------|-----------| | `negocio` | int | Sim | ID do negócio cujos itens serão listados. | | `pagina` | int | Não | Página da listagem. Padrão `1`. | | `por_pagina` | int | Não | Registros por página. Padrão `25`, máximo `100`. | **Exemplo — cURL** ```bash curl -X GET "https://api-backend.bunto.com.br/v1/crm/itens-negocio/?negocio=42&pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/itens-negocio/" headers = {"Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"} params = {"negocio": 42, "pagina": 1, "por_pagina": 25} resposta = requests.get(url, headers=headers, params=params) print(resposta.json())

Exemplo — JavaScript (fetch)

javascript
const url = "https://api-backend.bunto.com.br/v1/crm/itens-negocio/?negocio=42&pagina=1&por_pagina=25"; const resposta = await fetch(url, { headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, }); const dados = await resposta.json(); console.log(dados);

Resposta (200)

json
{ "success": true, "message": "2 registros encontrados", "data": { "resultados": [ { "id": 1001, "negocio_id": 42, "tipo": "produto", "produto_id": 305, "servico_id": null, "codigo": "PRD-305", "descricao": "Notebook Pro 14 polegadas", "unidade": "UN", "quantidade": "2.000", "preco_unitario": "4500.00", "desconto_percentual": "10.00", "preco_total": "8100.00" }, { "id": 1002, "negocio_id": 42, "tipo": "servico", "produto_id": null, "servico_id": 88, "codigo": "SRV-88", "descricao": "Instalação e configuração", "unidade": "HR", "quantidade": "3.000", "preco_unitario": "200.00", "desconto_percentual": "0.00", "preco_total": "600.00" } ], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 2, "total_paginas": 1 } } }

Adicionar item

POST /v1/crm/itens-negocio/

Escopo necessário: crm: write

Adiciona um item ao negócio. Após a inclusão, o valor total do negócio é recalculado automaticamente.

Corpo da requisição

CampoTipoObrigatórioDescrição
negocio_idintSimID do negócio que receberá o item.
descricaostringSimDescrição do item.
tipostringNãoNatureza do item: produto ou servico.
produto_idint / nullNãoID do produto vinculado, quando o item referencia um produto cadastrado.
servico_idint / nullNãoID do serviço vinculado, quando o item referencia um serviço cadastrado.
codigostringNãoCódigo do item (pode ser vazio).
unidadestringNãoUnidade de medida (ex.: UN, HR, KG). Pode ser vazia.
quantidadedecimalNãoQuantidade, com até 3 casas decimais.
preco_unitariodecimalNãoPreço unitário, com 2 casas decimais.
desconto_percentualdecimalNãoPercentual de desconto sobre o item (2 casas decimais, ex.: 10.00).

O campo preco_total é calculado pelo sistema e não é enviado no corpo.

Exemplo — cURL

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/itens-negocio/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "negocio_id": 42, "tipo": "produto", "produto_id": 305, "codigo": "PRD-305", "descricao": "Notebook Pro 14 polegadas", "unidade": "UN", "quantidade": "2.000", "preco_unitario": "4500.00", "desconto_percentual": "10.00" }'

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/itens-negocio/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", } dados = { "negocio_id": 42, "tipo": "produto", "produto_id": 305, "codigo": "PRD-305", "descricao": "Notebook Pro 14 polegadas", "unidade": "UN", "quantidade": "2.000", "preco_unitario": "4500.00", "desconto_percentual": "10.00", } resposta = requests.post(url, headers=headers, json=dados) print(resposta.json())

Exemplo — JavaScript (fetch)

javascript
const url = "https://api-backend.bunto.com.br/v1/crm/itens-negocio/"; const resposta = await fetch(url, { method: "POST", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", }, body: JSON.stringify({ negocio_id: 42, tipo: "produto", produto_id: 305, codigo: "PRD-305", descricao: "Notebook Pro 14 polegadas", unidade: "UN", quantidade: "2.000", preco_unitario: "4500.00", desconto_percentual: "10.00", }), }); const dados = await resposta.json(); console.log(dados);

Resposta (201)

json
{ "success": true, "message": "Item adicionado com sucesso", "data": { "id": 1001, "negocio_id": 42, "tipo": "produto", "produto_id": 305, "servico_id": null, "codigo": "PRD-305", "descricao": "Notebook Pro 14 polegadas", "unidade": "UN", "quantidade": "2.000", "preco_unitario": "4500.00", "desconto_percentual": "10.00", "preco_total": "8100.00" } }

Atualizar item

PUT /v1/crm/itens-negocio/{id}/
PATCH /v1/crm/itens-negocio/{id}/

Escopo necessário: crm: write

Atualiza um item existente. Use PUT para enviar o item completo ou PATCH para alterar apenas alguns campos. Ao salvar, o valor total do negócio é recalculado automaticamente.

O corpo aceita os mesmos campos de Adicionar item. preco_total continua sendo calculado pelo sistema.

Exemplo — cURL (PATCH ajustando quantidade e desconto)

bash
curl -X PATCH "https://api-backend.bunto.com.br/v1/crm/itens-negocio/1001/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "negocio_id": 42, "descricao": "Notebook Pro 14 polegadas", "quantidade": "3.000", "preco_unitario": "4500.00", "desconto_percentual": "5.00" }'

Resposta (200)

json
{ "success": true, "message": "Item atualizado com sucesso", "data": { "id": 1001, "negocio_id": 42, "tipo": "produto", "produto_id": 305, "servico_id": null, "codigo": "PRD-305", "descricao": "Notebook Pro 14 polegadas", "unidade": "UN", "quantidade": "3.000", "preco_unitario": "4500.00", "desconto_percentual": "5.00", "preco_total": "12825.00" } }

Remover item

DELETE /v1/crm/itens-negocio/{id}/

Escopo necessário: crm: delete

Remove o item do negócio. Após a remoção, o valor total do negócio é recalculado automaticamente.

Exemplo — cURL

bash
curl -X DELETE "https://api-backend.bunto.com.br/v1/crm/itens-negocio/1001/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

A remoção bem-sucedida não retorna corpo (HTTP 204).


I have everything I need from the code. Now I'll write the documentation section using only the fields that actually exist.

Atividades

Tarefas e follow-ups do CRM: ligações, e-mails, reuniões, visitas, propostas, notas e lembretes vinculados a um contato (e, opcionalmente, a um negócio). Use este recurso para registrar o que precisa ser feito, agendar contatos futuros e marcar o que já foi concluído.

Base URL (produção): https://api-backend.bunto.com.br/v1/crm/atividades/

Autenticação: envie o cabeçalho Authorization: Bearer bnt_TOKEN em toda requisição.

Escopo do módulo: crm. A ação exigida varia conforme o método HTTP: GET exige read, POST/PUT/PATCH exigem write, DELETE exige delete.

Tipos válidos: ligacao, email, reuniao, tarefa, whatsapp, visita, proposta, nota, follow_up.


Listar atividades

Escopo necessário: crm: read

GET /v1/crm/atividades/

Retorna as atividades da empresa autenticada, ordenadas da mais recente para a mais antiga (por data de criação).

Parâmetros de consulta (query):

CampoTipoObrigatórioDescrição
tipostringNãoFiltra por tipo. Valores aceitos: ligacao, email, reuniao, tarefa, whatsapp, visita, proposta, nota, follow_up.
concluidastringNãoFiltra pelo estado de conclusão. Aceita true (concluídas) ou false (pendentes).
contato_idinteiroNãoFiltra pelas atividades de um contato específico.
negocio_idinteiroNãoFiltra pelas atividades vinculadas a um negócio específico.
paginainteiroNãoPágina desejada. Padrão: 1.
por_paginainteiroNãoRegistros por página. Padrão: 25. Máximo: 100.

Campos de cada item na resposta:

CampoTipoDescrição
idinteiroIdentificador da atividade.
tipostringTipo da atividade.
titulostringTítulo da atividade.
contato_idinteiroContato ao qual a atividade pertence.
negocio_idinteiro | nuloNegócio vinculado, quando houver.
data_agendadadatetime (ISO 8601) | nuloData e hora agendadas, quando houver.
concluidabooleanoIndica se a atividade já foi concluída.
responsavel_idinteiro | nuloUsuário responsável pela atividade.
criado_emdatetime (ISO 8601)Data e hora de criação.

Exemplo — cURL:

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/atividades/?concluida=false&tipo=follow_up&pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests):

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/atividades/" headers = {"Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"} params = { "concluida": "false", "tipo": "follow_up", "pagina": 1, "por_pagina": 25, } resposta = requests.get(url, headers=headers, params=params) print(resposta.json())

Exemplo — JavaScript (fetch):

javascript
const params = new URLSearchParams({ concluida: "false", tipo: "follow_up", pagina: "1", por_pagina: "25", }); const resposta = await fetch( `https://api-backend.bunto.com.br/v1/crm/atividades/?${params}`, { headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, } ); const dados = await resposta.json(); console.log(dados);

Resposta (200):

json
{ "success": true, "message": "1 registros encontrados", "data": { "resultados": [ { "id": 184, "tipo": "follow_up", "titulo": "Retornar ligação sobre proposta", "contato_id": 57, "negocio_id": 23, "data_agendada": "2026-06-29T14:00:00-03:00", "concluida": false, "responsavel_id": 4, "criado_em": "2026-06-26T09:12:33-03:00" } ], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 1, "total_paginas": 1 } } }

Criar atividade

Escopo necessário: crm: write

POST /v1/crm/atividades/

Campos do corpo (JSON):

CampoTipoObrigatórioDescrição
tipostringSimTipo da atividade. Valores aceitos: ligacao, email, reuniao, tarefa, whatsapp, visita, proposta, nota, follow_up.
titulostringSimTítulo da atividade. Máximo de 200 caracteres.
contato_idinteiroSimContato ao qual a atividade será vinculada.
negocio_idinteiroNãoNegócio vinculado. Pode ser nulo.
descricaostringNãoDescrição livre. Pode ser vazia.
data_agendadadatetime (ISO 8601)NãoData e hora agendadas. Pode ser nula.
duracao_minutosinteiroNãoDuração prevista, em minutos. Pode ser nula.
responsavel_idinteiroNãoUsuário responsável. Se omitido, a atividade cai no dono da empresa ou, na ausência dele, no primeiro usuário ativo.

Exemplo — cURL:

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/atividades/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "tipo": "follow_up", "titulo": "Retornar ligação sobre proposta", "contato_id": 57, "negocio_id": 23, "descricao": "Cliente pediu para ligar depois das 14h.", "data_agendada": "2026-06-29T14:00:00-03:00", "duracao_minutos": 15, "responsavel_id": 4 }'

Exemplo — Python (requests):

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/atividades/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", } payload = { "tipo": "follow_up", "titulo": "Retornar ligação sobre proposta", "contato_id": 57, "negocio_id": 23, "descricao": "Cliente pediu para ligar depois das 14h.", "data_agendada": "2026-06-29T14:00:00-03:00", "duracao_minutos": 15, "responsavel_id": 4, } resposta = requests.post(url, headers=headers, json=payload) print(resposta.json())

Exemplo — JavaScript (fetch):

javascript
const resposta = await fetch( "https://api-backend.bunto.com.br/v1/crm/atividades/", { method: "POST", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", }, body: JSON.stringify({ tipo: "follow_up", titulo: "Retornar ligação sobre proposta", contato_id: 57, negocio_id: 23, descricao: "Cliente pediu para ligar depois das 14h.", data_agendada: "2026-06-29T14:00:00-03:00", duracao_minutos: 15, responsavel_id: 4, }), } ); const dados = await resposta.json(); console.log(dados);

Resposta (201):

json
{ "success": true, "message": "Atividade criada com sucesso", "data": { "id": 184, "tipo": "follow_up", "titulo": "Retornar ligação sobre proposta", "contato_id": 57, "negocio_id": 23, "data_agendada": "2026-06-29T14:00:00-03:00", "concluida": false, "responsavel_id": 4, "criado_em": "2026-06-26T09:12:33-03:00", "descricao": "Cliente pediu para ligar depois das 14h.", "duracao_minutos": 15, "data_conclusao": null, "resultado": "", "atualizado_em": "2026-06-26T09:12:33-03:00" } }

O objeto retornado em data traz, além dos campos do resumo, os seguintes campos de detalhe:

CampoTipoDescrição
descricaostringDescrição da atividade.
duracao_minutosinteiro | nuloDuração prevista, em minutos.
data_conclusaodatetime (ISO 8601) | nuloData e hora em que foi concluída.
resultadostringResultado registrado na conclusão.
atualizado_emdatetime (ISO 8601)Data e hora da última atualização.

Detalhar atividade

Escopo necessário: crm: read

GET /v1/crm/atividades/{id}/

Retorna todos os campos da atividade (resumo + detalhe).

Exemplo — cURL:

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/atividades/184/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta (200):

json
{ "success": true, "message": "Atividade encontrada", "data": { "id": 184, "tipo": "follow_up", "titulo": "Retornar ligação sobre proposta", "contato_id": 57, "negocio_id": 23, "data_agendada": "2026-06-29T14:00:00-03:00", "concluida": false, "responsavel_id": 4, "criado_em": "2026-06-26T09:12:33-03:00", "descricao": "Cliente pediu para ligar depois das 14h.", "duracao_minutos": 15, "data_conclusao": null, "resultado": "", "atualizado_em": "2026-06-26T09:12:33-03:00" } }

Atualizar atividade

Escopo necessário: crm: write

code
PUT /v1/crm/atividades/{id}/ PATCH /v1/crm/atividades/{id}/

Use PUT para enviar a atividade completa e PATCH para enviar apenas os campos que deseja alterar. Os campos do corpo são os mesmos da criação (tipo, titulo, contato_id, negocio_id, descricao, data_agendada, duracao_minutos, responsavel_id).

Exemplo — cURL (PATCH):

bash
curl -X PATCH "https://api-backend.bunto.com.br/v1/crm/atividades/184/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "titulo": "Retornar ligação sobre proposta (urgente)", "data_agendada": "2026-06-29T11:00:00-03:00" }'

Corpo (JSON):

json
{ "titulo": "Retornar ligação sobre proposta (urgente)", "data_agendada": "2026-06-29T11:00:00-03:00" }

Resposta (200):

json
{ "success": true, "message": "Atividade atualizada com sucesso", "data": { "id": 184, "tipo": "follow_up", "titulo": "Retornar ligação sobre proposta (urgente)", "contato_id": 57, "negocio_id": 23, "data_agendada": "2026-06-29T11:00:00-03:00", "concluida": false, "responsavel_id": 4, "criado_em": "2026-06-26T09:12:33-03:00", "descricao": "Cliente pediu para ligar depois das 14h.", "duracao_minutos": 15, "data_conclusao": null, "resultado": "", "atualizado_em": "2026-06-26T10:05:11-03:00" } }

Concluir atividade

Escopo necessário: crm: write

POST /v1/crm/atividades/{id}/concluir/

Marca a atividade como concluída. Você pode registrar opcionalmente o resultado obtido.

Campos do corpo (JSON):

CampoTipoObrigatórioDescrição
resultadostringNãoTexto livre com o desfecho da atividade. Se omitido, é gravado como vazio.

Exemplo — cURL:

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/atividades/184/concluir/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "resultado": "Cliente confirmou interesse, agendar reunião na próxima semana." }'

Corpo (JSON):

json
{ "resultado": "Cliente confirmou interesse, agendar reunião na próxima semana." }

Resposta (200):

json
{ "success": true, "message": "Atividade concluída", "data": { "id": 184, "tipo": "follow_up", "titulo": "Retornar ligação sobre proposta (urgente)", "contato_id": 57, "negocio_id": 23, "data_agendada": "2026-06-29T11:00:00-03:00", "concluida": true, "responsavel_id": 4, "criado_em": "2026-06-26T09:12:33-03:00", "descricao": "Cliente pediu para ligar depois das 14h.", "duracao_minutos": 15, "data_conclusao": "2026-06-26T10:20:48-03:00", "resultado": "Cliente confirmou interesse, agendar reunião na próxima semana.", "atualizado_em": "2026-06-26T10:20:48-03:00" } }

Excluir atividade

Escopo necessário: crm: delete

DELETE /v1/crm/atividades/{id}/

Remove a atividade de forma permanente.

Exemplo — cURL:

bash
curl -X DELETE "https://api-backend.bunto.com.br/v1/crm/atividades/184/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta (200):

json
{ "success": true, "message": "Atividade excluída com sucesso", "data": null }

Conversas

Recurso para o inbox do CRM. Permite listar e consultar conversas, além de resolvê-las e atribuí-las a um usuário. As conversas representam atendimentos por canal (WhatsApp, Instagram etc.) vinculados a um contato.

Os campos internos da IA-vendedora não fazem parte deste contrato público e não são retornados.

Base URL (produção): https://api-backend.bunto.com.br/v1/crm/conversas/

Autenticação: envie o cabeçalho Authorization: Bearer bnt_TOKEN em todas as requisições.

Campos do recurso

CampoTipoDescrição
idintegerIdentificador único da conversa.
contato_idintegerIdentificador do contato vinculado à conversa.
canalstringCanal de origem do atendimento (ex.: whatsapp, instagram).
statusstringSituação da conversa. Valores: aberta, aguardando, resolvida.
atribuido_para_idinteger | nullIdentificador do usuário responsável pela conversa. null se não atribuída.
criada_emstring (ISO 8601)Data e hora de criação da conversa.
ultima_mensagem_emstring (ISO 8601) | nullData e hora da última mensagem. null se não houver mensagens.
resolvida_emstring (ISO 8601) | nullData e hora em que a conversa foi resolvida. null se ainda não resolvida.

Listar conversas

GET /v1/crm/conversas/

Escopo necessário: crm: read

Parâmetros de consulta

CampoTipoObrigatórioDescrição
statusstringNãoFiltra por situação. Valores aceitos: aberta, aguardando, resolvida. Valores fora dessa lista são ignorados.
canalstringNãoFiltra pelo canal de origem (ex.: whatsapp, instagram).
contato_idintegerNãoFiltra pelas conversas de um contato específico.
paginaintegerNãoPágina da listagem. Padrão: 1.
por_paginaintegerNãoRegistros por página. Padrão: 25. Máximo: 100.

As conversas são retornadas ordenadas da mais recente para a mais antiga, pela data da última mensagem.

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/conversas/?status=aberta&canal=whatsapp&pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/conversas/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", } params = { "status": "aberta", "canal": "whatsapp", "pagina": 1, "por_pagina": 25, } resposta = requests.get(url, headers=headers, params=params) dados = resposta.json() print(dados)

Exemplo — JavaScript (fetch)

javascript
const params = new URLSearchParams({ status: "aberta", canal: "whatsapp", pagina: "1", por_pagina: "25", }); const resposta = await fetch( `https://api-backend.bunto.com.br/v1/crm/conversas/?${params}`, { method: "GET", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, } ); const dados = await resposta.json(); console.log(dados);

Resposta — 200 OK

json
{ "success": true, "message": "2 registros encontrados", "data": { "resultados": [ { "id": 1842, "contato_id": 309, "canal": "whatsapp", "status": "aberta", "atribuido_para_id": 12, "criada_em": "2026-06-25T14:03:11-03:00", "ultima_mensagem_em": "2026-06-26T09:41:52-03:00", "resolvida_em": null }, { "id": 1840, "contato_id": 287, "canal": "instagram", "status": "aguardando", "atribuido_para_id": null, "criada_em": "2026-06-24T10:18:00-03:00", "ultima_mensagem_em": "2026-06-25T16:22:07-03:00", "resolvida_em": null } ], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 2, "total_paginas": 1 } } }

Consultar uma conversa

GET /v1/crm/conversas/{id}/

Escopo necessário: crm: read

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/conversas/1842/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta — 200 OK

json
{ "success": true, "message": "Conversa encontrada", "data": { "id": 1842, "contato_id": 309, "canal": "whatsapp", "status": "aberta", "atribuido_para_id": 12, "criada_em": "2026-06-25T14:03:11-03:00", "ultima_mensagem_em": "2026-06-26T09:41:52-03:00", "resolvida_em": null } }

Resolver uma conversa

Marca a conversa como concluída. Não exige corpo na requisição.

POST /v1/crm/conversas/{id}/resolver/

Escopo necessário: crm: write

Exemplo — cURL

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/conversas/1842/resolver/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta — 200 OK

json
{ "success": true, "message": "Conversa resolvida", "data": { "id": 1842, "contato_id": 309, "canal": "whatsapp", "status": "resolvida", "atribuido_para_id": 12, "criada_em": "2026-06-25T14:03:11-03:00", "ultima_mensagem_em": "2026-06-26T09:41:52-03:00", "resolvida_em": "2026-06-26T10:05:33-03:00" } }

Atribuir uma conversa

Atribui a conversa a um usuário responsável.

POST /v1/crm/conversas/{id}/atribuir/

Escopo necessário: crm: write

Corpo da requisição

CampoTipoObrigatórioDescrição
usuario_idintegerSimIdentificador do usuário que assumirá a conversa.

Exemplo — cURL

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/conversas/1842/atribuir/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{"usuario_id": 12}'

Resposta — 200 OK

json
{ "success": true, "message": "Conversa atribuída", "data": { "id": 1842, "contato_id": 309, "canal": "whatsapp", "status": "aberta", "atribuido_para_id": 12, "criada_em": "2026-06-25T14:03:11-03:00", "ultima_mensagem_em": "2026-06-26T09:41:52-03:00", "resolvida_em": null } }

Resposta — 400 Bad Request

Quando usuario_id não é informado:

json
{ "success": false, "message": "usuario_id é obrigatório" }

Mensagens

Recurso somente leitura com as mensagens de uma conversa. A listagem exige o parâmetro conversa; sem ele, a resposta vem vazia (nenhuma mensagem é retornada). Não há rota de criação, atualização ou exclusão.

Base URL (produção): https://api-backend.bunto.com.br/v1/crm/mensagens/

Autenticação: envie o cabeçalho Authorization: Bearer bnt_TOKEN em todas as requisições.

Campos do recurso

CampoTipoDescrição
idintegerIdentificador único da mensagem.
conversa_idintegerIdentificador da conversa à qual a mensagem pertence.
direcaostringSentido da mensagem (ex.: entrada, saida).
remetente_tipostringTipo do remetente (ex.: contato, usuario).
conteudo_textostringTexto da mensagem. Pode vir vazio.
enviadabooleanIndica se a mensagem foi enviada.
lidabooleanIndica se a mensagem foi lida.
criada_emstring (ISO 8601)Data e hora de criação da mensagem.

Listar mensagens de uma conversa

GET /v1/crm/mensagens/

Escopo necessário: crm: read

Parâmetros de consulta

CampoTipoObrigatórioDescrição
conversaintegerSimIdentificador da conversa cujas mensagens serão listadas. Sem este parâmetro, a resposta vem vazia.
paginaintegerNãoPágina da listagem. Padrão: 1.
por_paginaintegerNãoRegistros por página. Padrão: 25. Máximo: 100.

As mensagens são retornadas em ordem cronológica, da mais antiga para a mais recente.

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/mensagens/?conversa=1842&pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/mensagens/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", } params = { "conversa": 1842, "pagina": 1, "por_pagina": 25, } resposta = requests.get(url, headers=headers, params=params) dados = resposta.json() print(dados)

Exemplo — JavaScript (fetch)

javascript
const params = new URLSearchParams({ conversa: "1842", pagina: "1", por_pagina: "25", }); const resposta = await fetch( `https://api-backend.bunto.com.br/v1/crm/mensagens/?${params}`, { method: "GET", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, } ); const dados = await resposta.json(); console.log(dados);

Resposta — 200 OK

json
{ "success": true, "message": "2 registros encontrados", "data": { "resultados": [ { "id": 50321, "conversa_id": 1842, "direcao": "entrada", "remetente_tipo": "contato", "conteudo_texto": "Olá, gostaria de saber o preço.", "enviada": true, "lida": true, "criada_em": "2026-06-26T09:40:18-03:00" }, { "id": 50322, "conversa_id": 1842, "direcao": "saida", "remetente_tipo": "usuario", "conteudo_texto": "Claro! Já te passo os valores.", "enviada": true, "lida": false, "criada_em": "2026-06-26T09:41:52-03:00" } ], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 2, "total_paginas": 1 } } }

Resposta — 200 OK (sem o parâmetro conversa)

Quando conversa não é informado, a listagem retorna vazia:

json
{ "success": true, "message": "0 registros encontrados", "data": { "resultados": [], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 0, "total_paginas": 1 } } }

Everything is confirmed. The fields are exactly: id (read-only int), nome (string, max 50, required on create), cor (string hex, max 7, optional, default #6B7280). There's also a busca query param in listagem. Pagination payload includes proxima/anterior. I'll write the section now.

Etiquetas

Etiquetas (tags) são rótulos coloridos usados para classificar os contatos do CRM. Cada etiqueta tem um nome e uma cor em hexadecimal.

Base URL (produção): https://api-backend.bunto.com.br/v1/crm/etiquetas/

Autenticação: todas as chamadas exigem o cabeçalho Authorization: Bearer bnt_TOKEN. O acesso é controlado pelo escopo do módulo crm, com a ação derivada do método HTTP: GET exige read, POST/PUT/PATCH exigem write e DELETE exige delete.

Campos do recurso

CampoTipoDescrição
idinteiroIdentificador único da etiqueta. Somente leitura (gerado pelo sistema).
nometextoNome da etiqueta. Máximo de 50 caracteres.
cortextoCor em hexadecimal, formato #RRGGBB (máximo 7 caracteres). Quando omitido na criação, assume o padrão #6B7280.

Listar etiquetas

GET /v1/crm/etiquetas/

Escopo necessário: crm: read

Retorna as etiquetas da empresa, ordenadas por nome, de forma paginada.

Parâmetros de consulta (query)

CampoTipoObrigatórioDescrição
paginainteiroNãoPágina desejada. Padrão: 1.
por_paginainteiroNãoRegistros por página. Padrão: 25. Máximo: 100.
buscatextoNãoFiltra pelo nome da etiqueta (busca parcial, sem diferenciar maiúsculas/minúsculas).

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/etiquetas/?pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/etiquetas/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", } params = {"pagina": 1, "por_pagina": 25} resposta = requests.get(url, headers=headers, params=params) print(resposta.json())

Exemplo — JavaScript (fetch)

javascript
const url = "https://api-backend.bunto.com.br/v1/crm/etiquetas/?pagina=1&por_pagina=25"; const resposta = await fetch(url, { method: "GET", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, }); const dados = await resposta.json(); console.log(dados);

Resposta — 200 OK

json
{ "success": true, "message": "2 registros encontrados", "data": { "resultados": [ { "id": 14, "nome": "Cliente VIP", "cor": "#0010FE" }, { "id": 9, "nome": "Lead frio", "cor": "#6B7280" } ], "paginacao": { "pagina_atual": 1, "total_paginas": 1, "total_registros": 2, "por_pagina": 25, "proxima": null, "anterior": null } } }

Consultar uma etiqueta

GET /v1/crm/etiquetas/{id}/

Escopo necessário: crm: read

Retorna os dados de uma única etiqueta pelo seu id.

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/etiquetas/14/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta — 200 OK

json
{ "success": true, "message": "Registro encontrado", "data": { "id": 14, "nome": "Cliente VIP", "cor": "#0010FE" } }

Criar etiqueta

POST /v1/crm/etiquetas/

Escopo necessário: crm: write

Cria uma nova etiqueta para a empresa do token.

Corpo da requisição (JSON)

CampoTipoObrigatórioDescrição
nometextoSimNome da etiqueta. Máximo de 50 caracteres.
cortextoNãoCor em hexadecimal #RRGGBB (máximo 7 caracteres). Padrão: #6B7280.

Exemplo — cURL

bash
curl -X POST "https://api-backend.bunto.com.br/v1/crm/etiquetas/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "nome": "Cliente VIP", "cor": "#0010FE" }'

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/etiquetas/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", } corpo = { "nome": "Cliente VIP", "cor": "#0010FE", } resposta = requests.post(url, headers=headers, json=corpo) print(resposta.json())

Exemplo — JavaScript (fetch)

javascript
const url = "https://api-backend.bunto.com.br/v1/crm/etiquetas/"; const resposta = await fetch(url, { method: "POST", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", "Content-Type": "application/json", }, body: JSON.stringify({ nome: "Cliente VIP", cor: "#0010FE", }), }); const dados = await resposta.json(); console.log(dados);

Resposta — 201 Created

json
{ "success": true, "message": "Registro criado com sucesso", "data": { "id": 14, "nome": "Cliente VIP", "cor": "#0010FE" } }

Atualizar etiqueta

code
PUT /v1/crm/etiquetas/{id}/ PATCH /v1/crm/etiquetas/{id}/

Escopo necessário: crm: write

Atualiza uma etiqueta existente. Use PUT para enviar o recurso completo ou PATCH para alterar apenas alguns campos.

Corpo da requisição (JSON)

CampoTipoObrigatórioDescrição
nometextoSim (no PUT)Nome da etiqueta. Máximo de 50 caracteres.
cortextoNãoCor em hexadecimal #RRGGBB (máximo 7 caracteres).

Exemplo — cURL

bash
curl -X PATCH "https://api-backend.bunto.com.br/v1/crm/etiquetas/14/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0" \ -H "Content-Type: application/json" \ -d '{ "cor": "#000AAD" }'

Resposta — 200 OK

json
{ "success": true, "message": "Registro atualizado com sucesso", "data": { "id": 14, "nome": "Cliente VIP", "cor": "#000AAD" } }

Excluir etiqueta

DELETE /v1/crm/etiquetas/{id}/

Escopo necessário: crm: delete

Remove a etiqueta pelo seu id.

Exemplo — cURL

bash
curl -X DELETE "https://api-backend.bunto.com.br/v1/crm/etiquetas/14/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta — 200 OK

json
{ "success": true, "message": "Registro excluído com sucesso" }

Funis (Pipelines)

Funis (também chamados de pipelines) representam as esteiras de vendas do CRM. Cada funil traz suas etapas aninhadas no campo etapas. Recurso somente leitura (listar e consultar) — útil para o cliente externo saber em qual funil e etapa colocar um negócio.

Base URL (produção): https://api-backend.bunto.com.br/v1/crm/funis/

Listar funis

GET /v1/crm/funis/

Escopo necessário: crm: read

Retorna os funis da empresa, ordenados primeiro pelo funil padrão e depois por nome. Cada funil já vem com suas etapas aninhadas (ordenadas por ordem).

Parâmetros de consulta (query)

CampoTipoObrigatórioDescrição
paginainteiroNãoPágina da listagem. Padrão 1.
por_paginainteiroNãoRegistros por página. Padrão 25, máximo 100.
incluir_inativosbooleanoNãoPor padrão só retorna funis ativos. Envie true para incluir também os inativos.

Campos da resposta (cada funil)

CampoTipoDescrição
idinteiroIdentificador do funil.
nometextoNome do funil.
descricaotextoDescrição do funil (pode vir vazia).
ativobooleanoIndica se o funil está ativo.
is_padraobooleanoIndica se é o funil padrão da empresa.
etapaslistaEtapas do funil, ordenadas por ordem. Ver campos abaixo.

Campos de cada etapa (dentro de etapas)

CampoTipoDescrição
idinteiroIdentificador da etapa.
nometextoNome da etapa.
ordeminteiroPosição da etapa no funil (ordem crescente).
cortextoCor da etapa (código de cor).
probabilidadeinteiroProbabilidade de fechamento associada à etapa (em porcentagem).
dias_alertainteiroDias até disparar alerta de negócio parado na etapa.

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/funis/?pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/funis/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", } params = { "pagina": 1, "por_pagina": 25, } resposta = requests.get(url, headers=headers, params=params) dados = resposta.json() print(dados["data"]["resultados"])

Exemplo — JavaScript (fetch)

javascript
const url = "https://api-backend.bunto.com.br/v1/crm/funis/?pagina=1&por_pagina=25"; const resposta = await fetch(url, { method: "GET", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, }); const dados = await resposta.json(); console.log(dados.data.resultados);

Resposta de exemplo

json
{ "success": true, "message": "2 registros encontrados", "data": { "resultados": [ { "id": 1, "nome": "Funil de Vendas", "descricao": "Esteira principal de novos negócios", "ativo": true, "is_padrao": true, "etapas": [ { "id": 10, "nome": "Qualificação", "ordem": 1, "cor": "#0010FE", "probabilidade": 20, "dias_alerta": 5 }, { "id": 11, "nome": "Proposta enviada", "ordem": 2, "cor": "#000AAD", "probabilidade": 60, "dias_alerta": 7 }, { "id": 12, "nome": "Fechamento", "ordem": 3, "cor": "#0C1316", "probabilidade": 90, "dias_alerta": 3 } ] }, { "id": 2, "nome": "Pós-venda", "descricao": "", "ativo": true, "is_padrao": false, "etapas": [ { "id": 20, "nome": "Onboarding", "ordem": 1, "cor": "#0010FE", "probabilidade": 100, "dias_alerta": 10 } ] } ], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 2, "total_paginas": 1 } } }

Consultar um funil

GET /v1/crm/funis/{id}/

Escopo necessário: crm: read

Retorna um único funil pelo seu id, com as etapas aninhadas. Os campos são os mesmos descritos na listagem.

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/funis/1/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta de exemplo

json
{ "success": true, "message": "Registro encontrado", "data": { "id": 1, "nome": "Funil de Vendas", "descricao": "Esteira principal de novos negócios", "ativo": true, "is_padrao": true, "etapas": [ { "id": 10, "nome": "Qualificação", "ordem": 1, "cor": "#0010FE", "probabilidade": 20, "dias_alerta": 5 }, { "id": 11, "nome": "Proposta enviada", "ordem": 2, "cor": "#000AAD", "probabilidade": 60, "dias_alerta": 7 } ] } }

Motivos de Perda

Motivos de perda são as justificativas usadas ao marcar um negócio como perdido. A listagem retorna tanto os motivos padrão do sistema (globais, sem empresa vinculada) quanto os cadastrados pela própria empresa. Recurso somente leitura.

Base URL (produção): https://api-backend.bunto.com.br/v1/crm/motivos-perda/

Listar motivos de perda

GET /v1/crm/motivos-perda/

Escopo necessário: crm: read

Retorna os motivos de perda ativos, combinando os globais do sistema com os da empresa autenticada, ordenados por ordem e depois por nome.

Parâmetros de consulta (query)

CampoTipoObrigatórioDescrição
paginainteiroNãoPágina da listagem. Padrão 1.
por_paginainteiroNãoRegistros por página. Padrão 25, máximo 100.

Campos da resposta (cada motivo)

CampoTipoDescrição
idinteiroIdentificador do motivo de perda.
codigotextoCódigo interno do motivo.
nometextoNome do motivo de perda.
descricaotextoDescrição do motivo (pode vir vazia).
ativobooleanoIndica se o motivo está ativo.

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/motivos-perda/?pagina=1&por_pagina=25" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Exemplo — Python (requests)

python
import requests url = "https://api-backend.bunto.com.br/v1/crm/motivos-perda/" headers = { "Authorization": "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", } params = { "pagina": 1, "por_pagina": 25, } resposta = requests.get(url, headers=headers, params=params) dados = resposta.json() print(dados["data"]["resultados"])

Exemplo — JavaScript (fetch)

javascript
const url = "https://api-backend.bunto.com.br/v1/crm/motivos-perda/?pagina=1&por_pagina=25"; const resposta = await fetch(url, { method: "GET", headers: { Authorization: "Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0", }, }); const dados = await resposta.json(); console.log(dados.data.resultados);

Resposta de exemplo

json
{ "success": true, "message": "3 registros encontrados", "data": { "resultados": [ { "id": 1, "codigo": "preco", "nome": "Preço acima do orçamento", "descricao": "Cliente achou o valor alto", "ativo": true }, { "id": 2, "codigo": "concorrencia", "nome": "Fechou com concorrente", "descricao": "", "ativo": true }, { "id": 8, "codigo": "sem_resposta", "nome": "Sem resposta do cliente", "descricao": "Cliente parou de responder", "ativo": true } ], "paginacao": { "pagina_atual": 1, "por_pagina": 25, "total_registros": 3, "total_paginas": 1 } } }

Consultar um motivo de perda

GET /v1/crm/motivos-perda/{id}/

Escopo necessário: crm: read

Retorna um único motivo de perda pelo seu id. Os campos são os mesmos descritos na listagem.

Exemplo — cURL

bash
curl -X GET "https://api-backend.bunto.com.br/v1/crm/motivos-perda/1/" \ -H "Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0"

Resposta de exemplo

json
{ "success": true, "message": "Registro encontrado", "data": { "id": 1, "codigo": "preco", "nome": "Preço acima do orçamento", "descricao": "Cliente achou o valor alto", "ativo": true } }

Eventos de Webhook do CRM

O CRM empurra para os seus webhooks toda mudança relevante de contatos, negócios, conversas e mensagens, independentemente de onde a alteração aconteceu (API pública, inbox, automações ou IA). O disparo é capturado por signals no banco, então qualquer caminho que altere o dado gera o evento. A entrega é assíncrona, assinada com HMAC-SHA256 e tem retentativas configuráveis.

Para receber estes eventos, cadastre um webhook apontando para a sua URL e selecione os eventos crm.* desejados. Cada empresa pode ter até 20 webhooks ativos.

Lista completa de eventos

São 14 eventos crm.*, divididos em quatro grupos. A coluna "Dispara quando" descreve a operação real que aciona cada evento.

Contato

EventoDispara quando
contato.criadoUm novo contato é criado.
contato.atualizadoUm contato existente é salvo com qualquer alteração.
contato.arquivadoUm contato passa de ativo para inativo (ativo muda de true para false). Dispara junto com contato.atualizado.
contato.etiqueta_alteradaAs etiquetas do contato mudam (adição, remoção ou limpeza).

Negócio

EventoDispara quando
negocio.criadoUm novo negócio é criado.
negocio.atualizadoUm negócio existente é salvo com qualquer alteração.
negocio.etapa_alteradaO negócio muda de etapa do funil (etapa_id diferente do anterior). Dispara junto com negocio.atualizado.
negocio.ganhoO status do negócio passa a ser ganho. Dispara junto com negocio.atualizado.
negocio.perdidoO status do negócio passa a ser perdido. Dispara junto com negocio.atualizado.

Conversa

EventoDispara quando
conversa.criadaUma nova conversa é aberta.
conversa.atualizadaUma conversa existente é salva com qualquer alteração.
conversa.concluidaO status da conversa passa a ser resolvida. Dispara junto com conversa.atualizada.

Mensagem

EventoDispara quando
mensagem.recebidaUma nova mensagem de entrada chega (cliente para você, direcao = entrada).
mensagem.enviadaUma nova mensagem de saída é registrada (você para o cliente, direcao = saida).

Observação importante: vários eventos disparam em par. Ao arquivar um contato você recebe contato.atualizado e contato.arquivado. Ao ganhar um negócio você recebe negocio.atualizado e negocio.ganho. Use o header X-Webhook-Evento (ou o campo evento do corpo) para distinguir cada entrega.

Estrutura do payload de entrega

Toda entrega tem o mesmo envelope. Os campos do recurso vão sempre dentro de dados.

CampoTipoDescrição
eventostringNome do evento, ex.: contato.criado.
timestampstring (ISO 8601)Momento em que o evento foi disparado.
webhook_idintID do webhook que está recebendo a entrega.
empresa_idintID da empresa dona do recurso.
dadosobjectCampos do recurso (variam por grupo, ver abaixo).
idempotency_keystringChave única desta entrega; reenvios de retentativa repetem a mesma chave.

Os campos dentro de dados por grupo:

Contatoid (int), nome (string), tipo (string), email (string), telefone (string), celular (string), empresa_nome (string), temperatura (string), origem_canal (string), ativo (bool). Campos de texto sem valor vêm como string vazia.

Negócioid (int), titulo (string), valor (string decimal), status (string), contato_id (int), pipeline_id (int), etapa_id (int), data_fechamento (string ISO 8601 ou null).

Conversaid (int), contato_id (int), canal (string), status (string), atribuido_para_id (int ou null).

Mensagemid (int), conversa_id (int), direcao (string: entrada ou saida), remetente_tipo (string), conteudo_texto (string), criada_em (string ISO 8601 ou null).

Exemplo de payload — Contato (contato.criado)

json
{ "evento": "contato.criado", "timestamp": "2026-06-26T14:32:10.482913-03:00", "webhook_id": 7, "empresa_id": 42, "dados": { "id": 1503, "nome": "Mariana Costa", "tipo": "lead", "email": "mariana.costa@exemplo.com.br", "telefone": "", "celular": "+5511988887777", "empresa_nome": "Costa Materiais de Construção", "temperatura": "quente", "origem_canal": "whatsapp", "ativo": true }, "idempotency_key": "7_contato.criado_9f2a7c1b04e5d6a8" }

Exemplo de payload — Negócio (negocio.ganho)

json
{ "evento": "negocio.ganho", "timestamp": "2026-06-26T15:08:44.117002-03:00", "webhook_id": 7, "empresa_id": 42, "dados": { "id": 880, "titulo": "Reforma loja - 80m de drywall", "valor": "12450.00", "status": "ganho", "contato_id": 1503, "pipeline_id": 3, "etapa_id": 19, "data_fechamento": "2026-06-26" }, "idempotency_key": "7_negocio.ganho_3b1e7d9042af6c10" }

Exemplo de payload — Conversa (conversa.concluida)

json
{ "evento": "conversa.concluida", "timestamp": "2026-06-26T16:21:09.730551-03:00", "webhook_id": 7, "empresa_id": 42, "dados": { "id": 2041, "contato_id": 1503, "canal": "whatsapp", "status": "resolvida", "atribuido_para_id": 11 }, "idempotency_key": "7_conversa.concluida_5c8a0f1b9e2d4477" }

Exemplo de payload — Mensagem (mensagem.recebida)

json
{ "evento": "mensagem.recebida", "timestamp": "2026-06-26T16:18:55.004210-03:00", "webhook_id": 7, "empresa_id": 42, "dados": { "id": 99231, "conversa_id": 2041, "direcao": "entrada", "remetente_tipo": "contato", "conteudo_texto": "Boa tarde, consigo fechar ainda hoje?", "criada_em": "2026-06-26T16:18:54.991003-03:00" }, "idempotency_key": "7_mensagem.recebida_a0d3f5901c7b2e64" }

Headers de entrega

Toda requisição de entrega chega no seu endpoint por POST com Content-Type: application/json; charset=utf-8 e os seguintes headers de identificação e segurança:

HeaderDescrição
X-Webhook-SignatureAssinatura HMAC-SHA256 do corpo exato da requisição, no formato sha256=<hex>. Use para verificar a autenticidade.
X-Webhook-EventoNome do evento desta entrega, ex.: negocio.ganho.
X-Webhook-IdID do webhook que originou a entrega.
X-Webhook-TimestampMomento do disparo em ISO 8601 (mesmo valor do campo timestamp do corpo).
X-Webhook-Idempotency-KeyChave única da entrega. Reenvios de retentativa repetem a mesma chave; use-a para descartar duplicatas.

O User-Agent das entregas é BuntoERP-Webhook/1.0. Headers customizados configurados no webhook também são enviados, exceto os reservados acima e o Content-Type.

Verificação da assinatura

A assinatura em X-Webhook-Signature é o HMAC-SHA256 calculado sobre o corpo bruto da requisição (os bytes exatos recebidos), usando como chave o secret do webhook, exibido uma única vez na criação ou na rotação. Recalcule sobre o corpo cru, sem reserializar o JSON, e compare em tempo constante com hmac.compare_digest.

python
import hashlib import hmac # Secret exibido uma única vez ao criar ou rotacionar o webhook. WEBHOOK_SECRET = "seu_secret_aqui" def assinatura_valida(corpo_bruto: bytes, header_assinatura: str) -> bool: """Verifica a assinatura de uma entrega de webhook. corpo_bruto: bytes exatos recebidos no corpo da requisição. header_assinatura: valor do header X-Webhook-Signature (ex.: 'sha256=abc...'). """ mac = hmac.new( WEBHOOK_SECRET.encode("utf-8"), corpo_bruto, hashlib.sha256, ) esperada = f"sha256={mac.hexdigest()}" return hmac.compare_digest(esperada, header_assinatura) # Exemplo de uso em uma view (Flask/FastAPI/Django, adapte conforme o framework): # corpo = request.body # ou await request.body() / request.get_data() # header = request.headers.get("X-Webhook-Signature", "") # if not assinatura_valida(corpo, header): # return resposta_401()

Responda com qualquer status 2xx para confirmar o recebimento. Status 4xx permanentes (400, 401, 403, 404, 405, 406, 410, 422) cancelam as retentativas; 429 e erros 5xx ou de rede são retentados.

Retentativas e entrega

A entrega é assíncrona e tolerante a falhas:

  • Retentativas configuráveis: cada webhook define max_tentativas (de 1 a 10). Em falha temporária, o sistema reenvia automaticamente com espera exponencial (30s, 60s, 120s, 240s...). Para respostas 429, o header Retry-After do seu servidor é respeitado.
  • Tempo limite configurável: timeout_segundos por webhook (de 5 a 30 segundos).
  • Idempotência: todas as tentativas de uma mesma entrega usam o mesmo X-Webhook-Idempotency-Key. Trate entregas como idempotentes e descarte chaves já processadas, já que o mesmo evento pode chegar mais de uma vez.
  • Desativação automática: após muitas falhas consecutivas, o webhook é desativado automaticamente e para de receber eventos até ser reativado.

Resumo: o CRM dispara 14 eventos (contato.*, negocio.*, conversa.*, mensagem.*) a cada criação, atualização ou mudança de estado relevante; toda entrega traz o envelope {evento, timestamp, webhook_id, empresa_id, dados, idempotency_key}, vem assinada em X-Webhook-Signature (verifique com hmac.compare_digest sobre o corpo cru) e é reenviada automaticamente conforme max_tentativas.


Erros Comuns

CódigoErroCausaSolução
400VALIDATION_ERRORDados enviados são inválidos (campo obrigatório ausente, formato incorreto, IDs de contato/pipeline/etapa inválidos, etc.)Verifique os campos obrigatórios e os tipos de dados
401Token inválidoToken ausente, expirado, revogado ou mal formatadoVerifique se o header é Authorization: Bearer bnt_xxx
403Token não tem permissãoO token não possui o escopo crm ou a ação necessária (read, write ou delete)Verifique os escopos do token no painel
404Não encontradoNegócio não existe ou pertence a outra empresaConfirme o ID e se o negócio pertence à empresa do token
429Limite de requisições excedidoRate limit ultrapassado para o tipo de operaçãoImplemente retry com backoff exponencial

Exemplo de Resposta de Erro (400)

json
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Erro de validação", "details": { "titulo": ["Este campo é obrigatório."], "contato_id": ["Este campo é obrigatório."], "pipeline_id": ["Este campo é obrigatório."], "etapa_id": ["Este campo é obrigatório."] } } }

Exemplo de Resposta de Erro (403)

json
{ "detail": "Token não tem permissão para este recurso" }

Rate Limiting

A API aplica limites de requisição por token (não por IP). Cada tipo de operação tem um limite diferente.

OperaçãoMétodos HTTPLimite
LeituraGET, HEAD, OPTIONS120 requisições/minuto
EscritaPOST, PUT, PATCH30 requisições/minuto
ExclusãoDELETE10 requisições/minuto

Ao exceder o limite, a API retorna 429 Too Many Requests:

json
{ "detail": "Limite de requisições excedido. Tente novamente em 45 segundos." }

Boas práticas

  • Use por_pagina=100 para reduzir o número de requisições ao listar negócios
  • Implemente retry com backoff exponencial ao receber 429
  • Armazene dados em cache local quando possível
  • Use filtros de pipeline_id e status para limitar o volume de dados retornados
  • Para atualizações em lote, use PATCH apenas com os campos alterados para economizar requisições de escrita

Paginação

Todos os endpoints de listagem retornam dados paginados.

Parâmetros

ParâmetroTipoPadrãoMáximoDescrição
paginainteger1-Número da página
por_paginainteger25100Registros por página

Atenção: Os parâmetros são pagina e por_pagina (em português), não page e per_page.

Objeto paginacao na resposta

CampoTipoDescrição
pagina_atualintegerNúmero da página atual
total_paginasintegerTotal de páginas disponíveis
total_registrosintegerTotal de registros encontrados
por_paginaintegerRegistros por página
proximastring / nullURL da próxima página (null se for a última)
anteriorstring / nullURL da página anterior (null se for a primeira)

Exemplo: percorrer todas as páginas (Python)

python
import requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = {"Authorization": f"Bearer {TOKEN}"} todos_os_negocios = [] url = f"{BASE_URL}/crm/?por_pagina=100" while url: resposta = requests.get(url, headers=headers) dados = resposta.json() if not dados["success"]: break todos_os_negocios.extend(dados["data"]["resultados"]) url = dados["data"]["paginacao"]["proxima"] print(f"Total obtido: {len(todos_os_negocios)} negócios")
APICRMCRUDcURLJavaScriptPythonREST
Recursos para IA