Guia de Início Rápido
Primeiros passos com a API do Bunto ERP - autenticação, exemplos práticos e clientes prontos.
Da geração do token até exemplos práticos em cURL, Python e JavaScript -- tudo o que você precisa para integrar com a API v1 do Bunto ERP.
Índice
- Visão Geral
- Pré-requisitos
- Passo 1: Gerar um Token de API
- Passo 2: Fazer sua Primeira Requisição
- Passo 3: Entender as Respostas
- Paginação, Ordenação e Busca
- Criando Registros
- Atualizando Registros
- Excluindo Registros
- Tratamento de Erros
- Limites de Requisição (Rate Limiting)
- Módulos Disponíveis
- Escopos e Permissões
- Boas Práticas
- Exemplo Completo: Cliente Python
- Exemplo Completo: Cliente JavaScript
- Próximos Passos
Visão Geral
| Propriedade | Valor |
|---|---|
| Base URL | https://api-backend.bunto.com.br/v1/ |
| Autenticação | Token de API (Bearer Token) |
| Prefixo do token | bnt_ |
| Formato de dados | JSON |
| Idioma dos parâmetros | Português (ex: pagina, por_pagina, busca) |
| Encoding | UTF-8 |
A API v1 do Bunto ERP é uma API REST que utiliza tokens de API para autenticação server-to-server. Diferente da autenticação JWT usada pelo painel administrativo, os tokens de API são projetados para integrações externas, webhooks e automações.
Pré-requisitos
- Uma conta ativa no Bunto ERP
- Acesso ao menu Integrações no painel administrativo
- Um cliente HTTP (cURL, Postman, Insomnia, ou uma linguagem de programação)
Passo 1: Gerar um Token de API
- Acesse o painel do Bunto ERP em
erp.bunto.com.br - Navegue até Integrações -> Tokens de API
- Clique em Novo Token
- Defina um nome descritivo (ex: "Integração Loja Virtual", "Sincronização Estoque")
- Opcionalmente, defina uma data de expiração e IPs permitidos
- Clique em Criar e copie o token gerado imediatamente
Importante: O token é exibido apenas uma vez no momento da criação. Não é possível recuperá-lo depois. Copie e armazene-o em local seguro imediatamente.
O token gerado terá o seguinte formato:
bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4
Passo 2: Fazer sua Primeira Requisição
Inclua o token no header Authorization de todas as requisições:
Authorization: Bearer bnt_seu_token_aqui
cURL
bashcurl https://api-backend.bunto.com.br/v1/produtos/ \ -H 'Authorization: Bearer bnt_seu_token_aqui' \ -H 'Content-Type: application/json'
Python (requests)
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_seu_token_aqui" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } resposta = requests.get(f"{BASE_URL}/produtos/", headers=headers) dados = resposta.json() if dados["success"]: produtos = dados["data"]["resultados"] paginacao = dados["data"]["paginacao"] print(f"Total de produtos: {paginacao['total_registros']}") for produto in produtos: print(f" - [{produto['codigo']}] {produto['nome']} - R$ {produto['preco']}") else: print(f"Erro: {resposta.status_code}")
JavaScript (fetch)
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_seu_token_aqui"; const headers = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }; const resposta = await fetch(`${BASE_URL}/produtos/`, { headers }); const dados = await resposta.json(); if (dados.success) { const produtos = dados.data.resultados; const paginacao = dados.data.paginacao; console.log(`Total de produtos: ${paginacao.total_registros}`); produtos.forEach((produto) => { console.log(` - [${produto.codigo}] ${produto.nome} - R$ ${produto.preco}`); }); } else { console.error(`Erro: ${resposta.status}`); }
Se a resposta for 200 com "success": true, seu token está configurado corretamente.
Passo 3: Entender as Respostas
Todas as respostas da API v1 seguem um formato padronizado (envelope pattern).
Sucesso -- Registro Único (200)
Retornado ao consultar um registro específico (GET com ID) ou ao atualizar/excluir.
json{ "success": true, "message": "Registro encontrado", "data": { "id": 1, "nome": "Camiseta Basica Algodão", "codigo":"CAM-001", "preco":"49.9000000000" } }
Sucesso -- Listagem (200)
Retornado ao listar registros (GET sem ID). Inclui paginação.
json{ "success": true, "message": "142 registros encontrados", "data": { "resultados": [ { "id": 1, "nome": "Camiseta Basica Algodão", "codigo":"CAM-001", "preco":"49.9000000000" }, { "id": 2, "nome": "Bermuda Jeans Masculina", "codigo":"BER-002", "preco":"129.9000000000" } ], "paginacao": { "pagina_atual": 1, "total_paginas": 6, "total_registros": 142, "por_pagina": 25, "proxima": "https://api-backend.bunto.com.br/v1/produtos/?pagina=2", "anterior": null } } }
Sucesso -- Criação (201)
Retornado ao criar um registro via POST.
json{ "success": true, "message": "Registro criado com sucesso", "data": { "id": 143, "nome": "Tênis Esportivo Running Pro", "codigo":"TEN-045", "preco":"299.9000000000" } }
Sucesso -- Exclusão (200)
Retornado ao excluir um registro via DELETE.
json{ "success": true, "message": "Registro excluído com sucesso" }
Erro
Retornado quando a requisição falha. O campo success é false, message contém um resumo e errors contém os detalhes por campo.
json{ "success": false, "message": "Nome é obrigatorio", "errors": { "nome": ["Nome é obrigatorio"], "preco": ["Informe um numero valido."] } }
Nota: Todas as respostas de erro (400, 401, 403, 404, 429, 500) seguem o mesmo formato envelope com success, message e errors.
Paginação, Ordenação e Busca
Todos os endpoints de listagem suportam paginação, ordenação e busca via parâmetros de query string.
Parâmetros Disponíveis
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
pagina | integer | 1 | Número da página (começando em 1) |
por_pagina | integer | 25 | Quantidade de registros por página (máximo: 100) |
busca | string | - | Termo de busca textual (campos pesquisáveis variam por módulo) |
ordenar | string | - | Campo pelo qual ordenar os resultados (varia por módulo) |
direcao | string | asc | Direção da ordenação: asc (crescente) ou desc (decrescente) |
Atenção: Os parâmetros são pagina e por_pagina (em português), não page e per_page.
Exemplos
cURL -- Busca com Paginação e Ordenação
bash# Buscar "camiseta", ordenar por preco (maior primeiro), 10 resultados por página curl 'https://api-backend.bunto.com.br/v1/produtos/?busca=camiseta&ordenar=preco&direcao=desc&pagina=1&por_pagina=10' \ -H 'Authorization: Bearer bnt_seu_token_aqui'
Python -- Percorrer Todas as Páginas
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_seu_token_aqui" headers = {"Authorization": f"Bearer {TOKEN}"} todos_os_registros = [] url = f"{BASE_URL}/produtos/?por_pagina=100" while url: resposta = requests.get(url, headers=headers) dados = resposta.json() if not dados["success"]: break todos_os_registros.extend(dados["data"]["resultados"]) url = dados["data"]["paginacao"]["proxima"] print(f"Total obtido: {len(todos_os_registros)} registros")
JavaScript -- Percorrer Todas as Páginas
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_seu_token_aqui"; const headers = { Authorization: `Bearer ${TOKEN}` }; const todosRegistros = []; let pagina = 1; let temProxima = true; while (temProxima) { const params = new URLSearchParams({ pagina: String(pagina), por_pagina: "100", }); const resposta = await fetch(`${BASE_URL}/produtos/?${params}`, { headers }); const dados = await resposta.json(); if (!dados.success) break; todosRegistros.push(...dados.data.resultados); temProxima = dados.data.paginacao.proxima !== null; pagina++; } console.log(`Total obtido: ${todosRegistros.length} registros`);
Criando Registros
Use o método POST para criar novos registros. O corpo da requisição deve ser um JSON com os campos do registro.
cURL
bashcurl -X POST https://api-backend.bunto.com.br/v1/produtos/ \ -H 'Authorization: Bearer bnt_seu_token_aqui' \ -H 'Content-Type: application/json' \ -d '{ "nome": "Tênis Esportivo Running Pro", "codigo":"TEN-045", "preco":299.90, "preco_custo": 145.00, "formato": "S", "situacao":"A", "controlar_estoque": true, "categoria_id": 5 }'
Python
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_seu_token_aqui" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } novo_produto = { "nome": "Tênis Esportivo Running Pro", "codigo":"TEN-045", "preco":299.90, "preco_custo": 145.00, "formato": "S", "situacao":"A", "controlar_estoque": True, "categoria_id": 5, } resposta = requests.post(f"{BASE_URL}/produtos/", headers=headers, json=novo_produto) dados = resposta.json() if dados["success"]: produto = dados["data"] print(f"Produto criado! ID: {produto['id']}, Nome: {produto['nome']}") else: print(f"Erro: {dados['message']}") if "errors" in dados: for campo, erros in dados["errors"].items(): print(f" {campo}: {', '.join(erros)}")
JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_seu_token_aqui"; const novoProduto = { nome: "Tênis Esportivo Running Pro", codigo: "TEN-045", preco: 299.9, preco_custo: 145.0, formato: "S", situacao: "A", controlar_estoque: true, categoria_id: 5, }; const resposta = await fetch(`${BASE_URL}/produtos/`, { method: "POST", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(novoProduto), }); const dados = await resposta.json(); if (dados.success) { console.log(`Produto criado! ID: ${dados.data.id}, Nome: ${dados.data.nome}`); } else { console.error(`Erro: ${dados.message}`); if (dados.errors) { for (const [campo, erros] of Object.entries(dados.errors)) { console.error(` ${campo}: ${erros.join(", ")}`); } } }
Atualizando Registros
Use PUT para atualização completa ou PATCH para atualização parcial (apenas os campos enviados serão alterados). O PATCH é mais recomendado pois consome menos dados e é mais eficiente.
cURL
bash# Atualizacao parcial -- apenas preco e preco promocional curl -X PATCH https://api-backend.bunto.com.br/v1/produtos/143/ \ -H 'Authorization: Bearer bnt_seu_token_aqui' \ -H 'Content-Type: application/json' \ -d '{ "preco":279.90, "preco_promocional": 249.90 }'
Python
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_seu_token_aqui" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } produto_id = 143 atualizacao = { "preco":279.90, "preco_promocional": 249.90, } resposta = requests.patch( f"{BASE_URL}/produtos/{produto_id}/", headers=headers, json=atualizacao, ) dados = resposta.json() if dados["success"]: print(f"Produto atualizado! Novo preco: R$ {dados['data']['preco']}") else: print(f"Erro ao atualizar: {dados}")
JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_seu_token_aqui"; const produtoId = 143; const atualizacao = { preco: 279.9, preco_promocional: 249.9, }; const resposta = await fetch(`${BASE_URL}/produtos/${produtoId}/`, { method: "PATCH", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(atualizacao), }); const dados = await resposta.json(); if (dados.success) { console.log(`Produto atualizado! Novo preco: R$ ${dados.data.preco}`); } else { console.error(`Erro ao atualizar:`, dados); }
Excluindo Registros
Use o método DELETE para excluir registros. Na maioria dos módulos, a exclusão é um soft delete -- o registro não é removido permanentemente, mas marcado como inativo, preservando o histórico.
cURL
bashcurl -X DELETE https://api-backend.bunto.com.br/v1/produtos/143/ \ -H 'Authorization: Bearer bnt_seu_token_aqui'
Python
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_seu_token_aqui" headers = {"Authorization": f"Bearer {TOKEN}"} produto_id = 143 resposta = requests.delete(f"{BASE_URL}/produtos/{produto_id}/", headers=headers) dados = resposta.json() if dados["success"]: print("Registro excluído com sucesso!") else: print(f"Erro ao excluir: {dados}")
JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_seu_token_aqui"; const produtoId = 143; const resposta = await fetch(`${BASE_URL}/produtos/${produtoId}/`, { method: "DELETE", headers: { Authorization: `Bearer ${TOKEN}`, }, }); const dados = await resposta.json(); if (dados.success) { console.log("Registro excluído com sucesso!"); } else { console.error(`Erro ao excluir:`, dados); }
Nota sobre vendas: O endpoint DELETE /v1/vendas/{id}/ cancela o pedido (altera o status para cancelado) ao invés de excluí-lo.
Tratamento de Erros
A API utiliza códigos de status HTTP padrão combinados com um corpo de resposta JSON detalhado.
Tabela de Códigos de Erro
| Código | Significado | Quando Ocorre | Como Resolver |
|---|---|---|---|
| 400 | Erro de validação | Dados enviados são inválidos (campo obrigatório ausente, formato incorreto, etc.) | Verifique o campo errors para saber quais campos estão com problema |
| 401 | Não autenticado | Token ausente, inválido, expirado ou revogado | Verifique se o header é Authorization: Bearer bnt_xxx (com espaço após Bearer). Confirme que o token não foi revogado e não expirou |
| 403 | Sem permissão | Token sem escopo para o módulo/ação, ou módulo não está no plano da empresa | Para OAuth: verifique os escopos do token. Para todos: verifique se o módulo está habilitado no plano |
| 404 | Não encontrado | Recurso não existe ou pertence a outra empresa | Confirme o ID do recurso. Cada token só acessa dados da própria empresa |
| 405 | Método não permitido | Método HTTP não suportado pelo endpoint | Verifique os métodos disponíveis na documentação do endpoint |
| 429 | Muitas requisições | Limite de rate limiting excedido | Implemente retry com backoff exponencial. Aguarde o tempo indicado na resposta |
| 500 | Erro interno | Erro inesperado no servidor | Tente novamente em alguns segundos. Se persistir, entre em contato com o suporte |
Exemplos de Respostas de Erro
Erro de validação (400):
json{ "success": false, "message": "Nome é obrigatorio", "errors": { "nome": ["Nome é obrigatorio"], "preco": ["Informe um numero valido."] } }
Não autenticado (401):
json{ "success": false, "message": "Token invalido", "errors": { "detail": ["Token invalido"] } }
Sem permissão (403) -- escopo insuficiente:
json{ "success": false, "message": "Permissao negada", "errors": { "detail": ["Token nao tem permissao para este recurso"] } }
Sem permissão (403) -- módulo fora do plano:
json{ "success": false, "message": "Permissao negada", "errors": { "detail": ["Modulo \"vendas\" nao disponivel no plano da empresa"] } }
Não encontrado (404):
json{ "success": false, "message": "Recurso nao encontrado", "errors": { "detail": ["O registro solicitado nao existe"] } }
Rate limit excedido (429):
json{ "success": false, "message": "Limite de requisicoes excedido. Tente novamente em 45 segundos.", "errors": { "detail": ["Limite de requisicoes excedido. Tente novamente em 45 segundos."] } }
Tratamento de Erros em Python
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_seu_token_aqui" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } resposta = requests.get(f"{BASE_URL}/produtos/999/", headers=headers) if resposta.status_code == 200: dados = resposta.json() if dados["success"]: print(f"Produto: {dados['data']['nome']}") else: print(f"Erro: {dados['message']}") elif resposta.status_code == 401: print("Token invalido ou expirado. Verifique suas credenciais.") elif resposta.status_code == 403: print("Sem permissao. Verifique os escopos do token.") elif resposta.status_code == 404: print("Produto nao encontrado.") elif resposta.status_code == 429: print("Limite de requisicoes excedido. Aguarde e tente novamente.") else: print(f"Erro inesperado: {resposta.status_code}")
Tratamento de Erros em JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_seu_token_aqui"; const headers = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }; try { const resposta = await fetch(`${BASE_URL}/produtos/999/`, { headers }); const dados = await resposta.json(); switch (resposta.status) { case 200: if (dados.success) { console.log(`Produto: ${dados.data.nome}`); } else { console.error(`Erro: ${dados.message}`); } break; case 401: console.error("Token invalido ou expirado. Verifique suas credenciais."); break; case 403: console.error("Sem permissao. Verifique os escopos do token."); break; case 404: console.error("Produto nao encontrado."); break; case 429: console.error("Limite de requisicoes excedido. Aguarde e tente novamente."); break; default: console.error(`Erro inesperado: ${resposta.status}`); } } catch (erro) { console.error(`Falha na conexao: ${erro.message}`); }
Limites de Requisição (Rate Limiting)
A API aplica limites de requisição por token (não por IP). Cada tipo de operação tem um limite diferente.
Limites por Operação
| 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 |
Proteção contra Brute-Force
Além do rate limiting, a API implementa proteção contra tentativas de força bruta:
| Configuração | Valor |
|---|---|
| Tentativas falhas por IP | 20 por janela |
| Janela de bloqueio | 15 minutos |
Após 20 tentativas falhas de autenticação a partir do mesmo IP, todas as requisições daquele IP serão bloqueadas por 15 minutos.
Exemplo de Retry com Backoff Exponencial (Python)
pythonimport time import requests def requisicao_com_retry(url, headers, max_tentativas=3): """Executa requisicao com retry automatico para rate limit.""" for tentativa in range(max_tentativas): resposta = requests.get(url, headers=headers) if resposta.status_code == 429: tempo_espera = 2 ** tentativa * 15 # 15s, 30s, 60s print(f"Rate limit atingido. Aguardando {tempo_espera}s...") time.sleep(tempo_espera) continue return resposta raise Exception("Limite de tentativas excedido")
Exemplo de Retry com Backoff Exponencial (JavaScript)
javascriptasync function requisicaoComRetry(url, opcoes, maxTentativas = 3) { for (let tentativa = 0; tentativa < maxTentativas; tentativa++) { const resposta = await fetch(url, opcoes); if (resposta.status === 429) { const tempoEspera = 2 ** tentativa * 15 * 1000; // 15s, 30s, 60s console.log(`Rate limit atingido. Aguardando ${tempoEspera / 1000}s...`); await new Promise((resolve) => setTimeout(resolve, tempoEspera)); continue; } return resposta; } throw new Error("Limite de tentativas excedido"); }
Módulos Disponíveis
A API v1 suporta 33 módulos. Cada módulo corresponde a um recurso que pode ser acessado via endpoints REST.
| Módulo | Endpoint | Descrição |
|---|---|---|
produtos | /v1/produtos/ | Cadastro de produtos |
clientes | /v1/clientes/ | Cadastro de clientes |
vendas | /v1/vendas/ | Pedidos de venda e orçamentos |
estoque | /v1/estoque/ | Controle de estoque e lançamentos |
categorias | /v1/categorias/ | Categorias de produtos |
marcadores | /v1/marcadores/ | Marcadores/tags |
embalagens | /v1/embalagens/ | Tipos de embalagem |
servicos | /v1/servicos/ | Cadastro de serviços |
nf | /v1/nf/ | Notas fiscais (geral) |
nfse | /v1/nfse/ | Notas fiscais de serviço (NFS-e) |
financas | /v1/financas/ | Contas a pagar e receber |
compras | /v1/compras/ | Pedidos de compra |
naturezas | /v1/naturezas/ | Naturezas de operação |
regras-tributarias | /v1/regras-tributarias/ | Regras e cenários tributários |
contas | /v1/contas/ | Contas bancárias |
remessas | /v1/remessas/ | Remessas bancárias |
retornos | /v1/retornos/ | Retornos bancários |
distribuicao-dfe | /v1/distribuicao-dfe/ | Distribuição de DF-e |
logistica | /v1/logistica/ | Logística e frete |
expedicao | /v1/expedicao/ | Expedição de pedidos |
crm | /v1/crm/ | Gestão de relacionamento com clientes |
marketing | /v1/marketing/ | Campanhas de marketing |
ocorrencias | /v1/ocorrencias/ | Registro de ocorrências |
agenda | /v1/agenda/ | Agenda e compromissos |
pdv | /v1/pdv/ | Ponto de venda |
propostas | /v1/propostas/ | Propostas comerciais |
devolucoes | /v1/devolucoes/ | Devoluções de venda |
vale-troca | /v1/vale-troca/ | Vales-troca |
arquivos | /v1/arquivos/ | Gerenciamento de arquivos |
configuracoes | /v1/configuracoes/ | Configurações do sistema |
usuarios | /v1/usuarios/ | Gestão de usuários |
empresas | /v1/empresas/ | Dados da empresa |
dashboard | /v1/dashboard/ | Dados do painel |
Nota: Nem todos os módulos suportam todas as operações (GET, POST, PUT, PATCH, DELETE). Consulte a documentação específica de cada módulo para detalhes.
Escopos e Permissões
A API v1 possui duas camadas de permissão:
- Plano da empresa -- define quais módulos estão disponíveis (mesma verificação do painel)
- Escopos do token -- controle granular por módulo e ação (apenas OAuth)
Tipos de Token
| Tipo | Escopos | Comportamento |
|---|---|---|
| Token de API | Nenhum (campo vazio) | Acesso total (read/write/delete) a todos os módulos do plano |
| Token OAuth | Definidos na criação | Acesso restrito aos módulos e ações especificados |
Token de API (sem escopos)
O token de API simples -- gerado no painel em Integrações > Tokens de API -- não possui escopos definidos. Ele tem permissão total (leitura, escrita e exclusão) em todos os módulos habilitados no plano da empresa.
Importante: Mesmo com permissão total, o token só acessa módulos que estão no plano contratado pela empresa. Se um módulo for removido do plano, o acesso via API é bloqueado automaticamente com 403 Forbidden.
Token OAuth (com escopos)
Tokens OAuth possuem escopos granulares que restringem quais módulos e ações o token pode executar.
Ações Disponíveis
| Ação | Métodos HTTP | Descrição |
|---|---|---|
read | GET, HEAD, OPTIONS | Leitura e consulta de dados |
write | POST, PUT, PATCH | Criação e atualização de registros |
delete | DELETE | Exclusão de registros |
Formato dos Escopos
json{ "produtos": ["read", "write"], "vendas": ["read"], "clientes": ["read", "write", "delete"] }
Exemplos de Configuração por Cenário
Integração e-commerce (cenário típico):
json{ "produtos": ["read"], "vendas": ["read", "write"], "clientes": ["read", "write"], "estoque": ["read"] }
Painel de BI / Relatórios (somente leitura):
json{ "produtos": ["read"], "vendas": ["read"], "clientes": ["read"], "estoque": ["read"], "financas": ["read"] }
Sistema de gestão completa:
json{ "produtos": ["read", "write", "delete"], "clientes": ["read", "write", "delete"], "vendas": ["read", "write", "delete"], "estoque": ["read", "write"] }
Verificação de Plano
Independente do tipo de token, a API verifica se o módulo está disponível no plano da empresa. Essa é a mesma verificação usada pelo painel do ERP. Se a empresa não possui o módulo no plano, a resposta será:
json{ "success": false, "message": "Permissao negada", "errors": { "detail": ["Modulo \"vendas\" nao disponivel no plano da empresa"] } }
Boas Práticas
Armazenamento Seguro do Token
Nunca inclua o token diretamente no código-fonte. Use variáveis de ambiente.
pythonimport os # Correto: variável de ambiente TOKEN = os.environ["BUNTO_API_TOKEN"] # Errado: hardcoded no codigo TOKEN = "bnt_A1b2C3d4E5f6G7h8..." # NUNCA faça isso
javascript// Correto: variável de ambiente const TOKEN = process.env.BUNTO_API_TOKEN; // Errado: hardcoded no codigo const TOKEN = "bnt_A1b2C3d4E5f6G7h8..."; // NUNCA faça isso
Outras Recomendações
- Princípio do menor privilégio: Crie tokens com apenas os escopos necessários para cada integração
- Paginação eficiente: Use
por_pagina=100para reduzir o número de requisições ao listar registros - Cache local: Armazene dados que não mudam com frequência (categorias, embalagens, etc.) para evitar requisições desnecessárias
- Atualizações parciais: Use
PATCHcom apenas os campos alterados ao invés dePUTcom todos os campos - Retry com backoff: Implemente retry com backoff exponencial ao receber erros
429(rate limit) - Restrição por IP: Sempre que possível, configure a lista de IPs permitidos para o token
- Rotação de tokens: Revogue tokens que não estão mais em uso e crie novos periodicamente
- Content-Type: Sempre inclua
Content-Type: application/jsonem requisições POST, PUT e PATCH
Checklist de Depuração
Se algo não está funcionando, verifique:
- O token começa com
bnt_? - O header está correto? (
Authorization: Bearer bnt_xxx, com B maiúsculo em Bearer) - A URL está correta? Endpoints da API v1 começam com
/v1/, não/api/ - O token está ativo? Verifique no painel se não foi revogado
- O token tem os escopos necessários para o módulo e ação?
- O IP está na allowlist? Se o token tem restrição de IP, verifique se seu IP está na lista
- O
Content-Type: application/jsonestá definido para POST/PUT/PATCH?
Exemplo Completo: Cliente Python
Uma classe reutilizável que encapsula toda a comunicação com a API, incluindo retry automático e paginação.
pythonimport os import time import requests class BuntoAPI: """Cliente para a API v1 do Bunto ERP.""" def __init__(self, token=None, base_url=None): self.token = token or os.environ["BUNTO_API_TOKEN"] self.base_url = base_url or "https://api-backend.bunto.com.br/v1" self.session = requests.Session() self.session.headers.update({ "Authorization": f"Bearer {self.token}", "Content-Type": "application/json", }) def _requisicao(self, metodo, endpoint, **kwargs): """Executa requisicao com retry automatico para rate limit.""" url = f"{self.base_url}/{endpoint.lstrip('/')}" for tentativa in range(3): resposta = self.session.request(metodo, url, **kwargs) if resposta.status_code == 429: tempo = 2 ** tentativa * 15 print(f"Rate limit. Aguardando {tempo}s...") time.sleep(tempo) continue return resposta raise Exception("Limite de tentativas excedido") # --- Leitura --- def listar(self, modulo, **filtros): """Lista registros de um modulo com filtros opcionais.""" resposta = self._requisicao("GET", f"/{modulo}/", params=filtros) return resposta.json() def listar_todos(self, modulo, **filtros): """Lista todos os registros com paginacao automatica.""" filtros["por_pagina"] = 100 filtros["pagina"] = 1 todos = [] while True: resultado = self.listar(modulo, **filtros) if not resultado["success"]: break todos.extend(resultado["data"]["resultados"]) paginacao = resultado["data"]["paginacao"] if paginacao["proxima"] is None: break filtros["pagina"] += 1 return todos def obter(self, modulo, registro_id): """Obtém um registro pelo ID.""" resposta = self._requisicao("GET", f"/{modulo}/{registro_id}/") return resposta.json() # --- Escrita --- def criar(self, modulo, dados): """Cria um novo registro.""" resposta = self._requisicao("POST", f"/{modulo}/", json=dados) return resposta.json() def atualizar(self, modulo, registro_id, dados): """Atualiza parcialmente um registro (PATCH).""" resposta = self._requisicao("PATCH", f"/{modulo}/{registro_id}/", json=dados) return resposta.json() # --- Exclusao --- def excluir(self, modulo, registro_id): """Exclui (ou cancela) um registro.""" resposta = self._requisicao("DELETE", f"/{modulo}/{registro_id}/") return resposta.json() # --- Exemplos de uso --- api = BuntoAPI() # Listar produtos ativos resultado = api.listar("produtos", situacao="A", ordenar="nome", por_pagina=50) if resultado["success"]: for produto in resultado["data"]["resultados"]: print(f"[{produto['codigo']}] {produto['nome']} - R$ {produto['preco']}") # Obter produto por ID resultado = api.obter("produtos", 1) if resultado["success"]: print(f"Produto: {resultado['data']['nome']}") # Criar novo produto resultado = api.criar("produtos", { "nome": "Novo Produto", "codigo":"NP-001", "preco":59.90, }) if resultado["success"]: print(f"Produto criado! ID: {resultado['data']['id']}") # Atualizar preco resultado = api.atualizar("produtos", 1, {"preco":54.90}) if resultado["success"]: print(f"Preco atualizado para R$ {resultado['data']['preco']}") # Listar todos os clientes (paginacao automatica) todos_clientes = api.listar_todos("clientes") print(f"Total de clientes: {len(todos_clientes)}") # Criar pedido de venda resultado = api.criar("vendas", { "cliente_id": 42, "tipo_pedido": "venda", "itens": [ {"produto_id": 1, "quantidade": 2, "preco_unitario": 49.90}, {"produto_id": 5, "quantidade": 1, "preco_unitario": 129.90}, ], }) if resultado["success"]: print(f"Pedido criado: {resultado['data']['numero_pedido']}")
Exemplo Completo: Cliente JavaScript
Uma classe reutilizável em JavaScript com suporte a retry automático e paginação.
javascriptclass BuntoAPI { /** * Cliente para a API v1 do Bunto ERP. * @param {string} token - Token de API (ou process.env.BUNTO_API_TOKEN) * @param {string} baseUrl - URL base da API */ constructor(token, baseUrl) { this.token = token ?? process.env.BUNTO_API_TOKEN; this.baseUrl = baseUrl ?? "https://api-backend.bunto.com.br/v1"; this.headers = { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json", }; } /** * Executa requisicao com retry automatico para rate limit. */ async _requisicao(metodo, endpoint, opcoes = {}) { const url = `${this.baseUrl}/${endpoint.replace(/^\//, "")}`; for (let tentativa = 0; tentativa < 3; tentativa++) { const resposta = await fetch(url, { method: metodo, headers: this.headers, ...opcoes, }); if (resposta.status === 429) { const tempo = 2 ** tentativa * 15 * 1000; console.log(`Rate limit. Aguardando ${tempo / 1000}s...`); await new Promise((resolve) => setTimeout(resolve, tempo)); continue; } return resposta.json(); } throw new Error("Limite de tentativas excedido"); } // --- Leitura --- async listar(modulo, filtros = {}) { const params = new URLSearchParams( Object.fromEntries( Object.entries(filtros).map(([k, v]) => [k, String(v)]) ) ); return this._requisicao("GET", `/${modulo}/?${params}`); } async listarTodos(modulo, filtros = {}) { filtros.por_pagina = 100; filtros.pagina = 1; const todos = []; while (true) { const resultado = await this.listar(modulo, filtros); if (!resultado.success) break; todos.push(...resultado.data.resultados); if (resultado.data.paginacao.proxima === null) break; filtros.pagina++; } return todos; } async obter(modulo, registroId) { return this._requisicao("GET", `/${modulo}/${registroId}/`); } // --- Escrita --- async criar(modulo, dados) { return this._requisicao("POST", `/${modulo}/`, { body: JSON.stringify(dados), }); } async atualizar(modulo, registroId, dados) { return this._requisicao("PATCH", `/${modulo}/${registroId}/`, { body: JSON.stringify(dados), }); } // --- Exclusao --- async excluir(modulo, registroId) { return this._requisicao("DELETE", `/${modulo}/${registroId}/`); } } // --- Exemplos de uso --- const api = new BuntoAPI(); // Listar produtos ativos const resultado = await api.listar("produtos", { situacao: "A", ordenar: "nome", por_pagina: 50, }); if (resultado.success) { resultado.data.resultados.forEach((produto) => { console.log(`[${produto.codigo}] ${produto.nome} - R$ ${produto.preco}`); }); } // Obter produto por ID const detalhes = await api.obter("produtos", 1); if (detalhes.success) { console.log(`Produto: ${detalhes.data.nome}`); } // Criar novo produto const novoProduto = await api.criar("produtos", { nome: "Novo Produto", codigo: "NP-001", preco: 59.9, }); if (novoProduto.success) { console.log(`Produto criado! ID: ${novoProduto.data.id}`); } // Atualizar preco const atualizado = await api.atualizar("produtos", 1, { preco: 54.9 }); if (atualizado.success) { console.log(`Preco atualizado para R$ ${atualizado.data.preco}`); } // Listar todos os clientes (paginacao automatica) const todosClientes = await api.listarTodos("clientes"); console.log(`Total de clientes: ${todosClientes.length}`); // Criar pedido de venda const pedido = await api.criar("vendas", { cliente_id: 42, tipo_pedido: "venda", itens: [ { produto_id: 1, quantidade: 2, preco_unitario: 49.9 }, { produto_id: 5, quantidade: 1, preco_unitario: 129.9 }, ], }); if (pedido.success) { console.log(`Pedido criado: ${pedido.data.numero_pedido}`); }
Próximos Passos
Agora que você sabe como autenticar, listar, criar, atualizar e excluir registros, consulte a documentação detalhada de cada módulo para conhecer todos os campos e filtros disponíveis:
- Autenticação API v1 -- Detalhes completos sobre tokens, OAuth, escopos e restrição por IP
- API de Produtos -- Todos os campos, filtros e exemplos para o cadastro de produtos
- API de Clientes -- CRUD de clientes com conformidade LGPD (campos criptografados)
- API de Vendas -- Pedidos de venda e orçamentos com itens, descontos e marketplace
- API de Estoque -- Consulta de saldos e lançamentos de movimentação
Suporte
Se encontrar problemas ou tiver dúvidas sobre a API, entre em contato com o suporte técnico do Bunto ERP pelo painel administrativo ou pelo e-mail disponível na sua conta.