API de CRM
CRUD de negócios (deals) do CRM via API.
Gerencie os negocios (deals) do CRM do Bunto ERP. Este endpoint permite listar, consultar, criar, atualizar e marcar negocios como perdidos, incluindo dados de contatos, pipelines, etapas e itens vinculados, via API.
Base URL
Produção:
https://api-backend.bunto.com.br/v1/crm/
Autenticação
Todas as requisições exigem 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 do Bunto ERP. Escopo necessário: crm com a ação correspondente (read, write ou delete).
Endpoints
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âmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
pagina | integer | 1 | Número da página |
por_pagina | integer | 25 | Registros por página (máximo: 100) |
busca | string | - | Busca por título do negócio ou nome do contato |
status | string | - | Filtrar por status: aberto, ganho, perdido, transferido |
pipeline_id | integer | - | Filtrar por pipeline específico |
include_all | boolean | false | true para incluir negócios perdidos e transferidos na listagem |
ordenar | string | criado_em | Campo de ordenação: criado_em, titulo, valor |
direcao | string | desc | Direção da ordenação: asc ou desc |
Exemplo com cURL
bashcurl '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
pythonimport 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
javascriptconst 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
| Campo | Tipo | Descrição |
|---|---|---|
id | integer | Identificador único do negócio |
titulo | string | Título do negócio |
status | string | Status do negócio: aberto, ganho, perdido, transferido |
valor | decimal | Valor monetário do negócio |
contato_id | integer | ID do contato vinculado |
pipeline_id | integer | ID do pipeline |
etapa_id | integer | ID da etapa atual no pipeline |
data_previsao_fechamento | date / null | Data prevista para fechamento do negócio |
criado_em | datetime | Data 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
bashcurl https://api-backend.bunto.com.br/v1/crm/30/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json'
Exemplo com Python
pythonimport 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
javascriptconst 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)
| Campo | Tipo | Descrição |
|---|---|---|
descricao | string | Descrição detalhada do negócio |
motivo_perda | string | Motivo da perda (preenchido quando status = perdido) |
data_fechamento | date / null | Data efetiva de fechamento do negócio |
contato_nome | string / null | Nome do contato vinculado |
pipeline_nome | string / null | Nome do pipeline |
etapa_nome | string / null | Nome da etapa atual no pipeline |
atualizado_em | datetime | Data e hora da última atualização |
itens | array | Lista de itens/produtos do negócio |
itens[].produto_id | integer | ID do produto |
itens[].quantidade | decimal | Quantidade do item |
itens[].valor_unitario | decimal | Valor 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
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
titulo | string | Sim | Título do negócio (máximo 200 caracteres) |
contato_id | integer | Sim | ID do contato vinculado |
pipeline_id | integer | Sim | ID do pipeline |
etapa_id | integer | Sim | ID da etapa inicial no pipeline |
descricao | string | Não | Descrição detalhada do negócio |
valor | decimal | Não | Valor monetário do negócio (padrão: 0) |
status | string | Não | Status inicial: aberto, ganho, perdido, transferido (padrão: aberto) |
data_previsao_fechamento | date | Não | Data prevista para fechamento (formato: YYYY-MM-DD) |
motivo_perda | string | Não | Motivo da perda (relevante quando status = perdido) |
Exemplo com cURL
bashcurl -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
pythonimport 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
javascriptconst 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)
bashcurl -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
pythonimport 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
javascriptconst 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.
Exemplo com cURL
bashcurl -X DELETE https://api-backend.bunto.com.br/v1/crm/31/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4'
Exemplo com Python
pythonimport 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
javascriptconst 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.
Erros Comuns
| Código | Erro | Causa | Solução |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Dados 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 |
| 401 | Token inválido | Token ausente, expirado, revogado ou mal formatado | Verifique se o header é Authorization: Bearer bnt_xxx |
| 403 | Token não tem permissão | O token não possui o escopo crm ou a ação necessária (read, write ou delete) | Verifique os escopos do token no painel |
| 404 | Não encontrado | Negócio não existe ou pertence a outra empresa | Confirme o ID e se o negócio pertence à empresa do token |
| 429 | Limite de requisições excedido | Rate limit ultrapassado para o tipo de operação | Implemente 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ção | Métodos HTTP | Limite |
|---|---|---|
| Leitura | GET, HEAD, OPTIONS | 120 requisições/minuto |
| Escrita | POST, PUT, PATCH | 30 requisições/minuto |
| Exclusão | DELETE | 10 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=100para 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_idestatuspara limitar o volume de dados retornados - Para atualizações em lote, use
PATCHapenas com os campos alterados para economizar requisições de escrita
Paginação
Todos os endpoints de listagem retornam dados paginados.
Parâmetros
| Parâmetro | Tipo | Padrão | Máximo | Descrição |
|---|---|---|---|---|
pagina | integer | 1 | - | Número da página |
por_pagina | integer | 25 | 100 | Registros 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
| Campo | Tipo | Descrição |
|---|---|---|
pagina_atual | integer | Número da página atual |
total_paginas | integer | Total de páginas disponíveis |
total_registros | integer | Total de registros encontrados |
por_pagina | integer | Registros por página |
proxima | string / null | URL da próxima página (null se for a última) |
anterior | string / null | URL da página anterior (null se for a primeira) |
Exemplo: percorrer todas as páginas (Python)
pythonimport 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")