Developers/Documentação/Guia de Início Rápido

Guia de Início Rápido

Primeiros passos com a API do Bunto ERP - autenticação, exemplos práticos e clientes prontos.

13/02/202622 min de leitura128 visualizaçõesv3

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

  1. Visão Geral
  2. Pré-requisitos
  3. Passo 1: Gerar um Token de API
  4. Passo 2: Fazer sua Primeira Requisição
  5. Passo 3: Entender as Respostas
  6. Paginação, Ordenação e Busca
  7. Criando Registros
  8. Atualizando Registros
  9. Excluindo Registros
  10. Tratamento de Erros
  11. Limites de Requisição (Rate Limiting)
  12. Módulos Disponíveis
  13. Escopos e Permissões
  14. Boas Práticas
  15. Exemplo Completo: Cliente Python
  16. Exemplo Completo: Cliente JavaScript
  17. Próximos Passos

Visão Geral

PropriedadeValor
Base URLhttps://api-backend.bunto.com.br/v1/
AutenticaçãoToken de API (Bearer Token)
Prefixo do tokenbnt_
Formato de dadosJSON
Idioma dos parâmetrosPortuguês (ex: pagina, por_pagina, busca)
EncodingUTF-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

  1. Acesse o painel do Bunto ERP em erp.bunto.com.br
  2. Navegue até Integrações -> Tokens de API
  3. Clique em Novo Token
  4. Defina um nome descritivo (ex: "Integração Loja Virtual", "Sincronização Estoque")
  5. Opcionalmente, defina uma data de expiração e IPs permitidos
  6. 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

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

Python (requests)

python
import 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)

javascript
const 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âmetroTipoPadrãoDescrição
paginainteger1Número da página (começando em 1)
por_paginainteger25Quantidade de registros por página (máximo: 100)
buscastring-Termo de busca textual (campos pesquisáveis variam por módulo)
ordenarstring-Campo pelo qual ordenar os resultados (varia por módulo)
direcaostringascDireçã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

python
import 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

javascript
const 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

bash
curl -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

python
import 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

javascript
const 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

python
import 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

javascript
const 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

bash
curl -X DELETE https://api-backend.bunto.com.br/v1/produtos/143/ \ -H 'Authorization: Bearer bnt_seu_token_aqui'

Python

python
import 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

javascript
const 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ódigoSignificadoQuando OcorreComo Resolver
400Erro de validaçãoDados enviados são inválidos (campo obrigatório ausente, formato incorreto, etc.)Verifique o campo errors para saber quais campos estão com problema
401Não autenticadoToken ausente, inválido, expirado ou revogadoVerifique 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
403Sem permissãoToken sem escopo para o módulo/ação, ou módulo não está no plano da empresaPara OAuth: verifique os escopos do token. Para todos: verifique se o módulo está habilitado no plano
404Não encontradoRecurso não existe ou pertence a outra empresaConfirme o ID do recurso. Cada token só acessa dados da própria empresa
405Método não permitidoMétodo HTTP não suportado pelo endpointVerifique os métodos disponíveis na documentação do endpoint
429Muitas requisiçõesLimite de rate limiting excedidoImplemente retry com backoff exponencial. Aguarde o tempo indicado na resposta
500Erro internoErro inesperado no servidorTente 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

python
import 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

javascript
const 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çãoMétodos HTTPLimite
LeituraGET, HEAD, OPTIONS120 requisições/minuto
EscritaPOST, PUT, PATCH30 requisições/minuto
ExclusãoDELETE10 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çãoValor
Tentativas falhas por IP20 por janela
Janela de bloqueio15 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)

python
import 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)

javascript
async 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óduloEndpointDescriçã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:

  1. Plano da empresa -- define quais módulos estão disponíveis (mesma verificação do painel)
  2. Escopos do token -- controle granular por módulo e ação (apenas OAuth)

Tipos de Token

TipoEscoposComportamento
Token de APINenhum (campo vazio)Acesso total (read/write/delete) a todos os módulos do plano
Token OAuthDefinidos na criaçãoAcesso 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çãoMétodos HTTPDescrição
readGET, HEAD, OPTIONSLeitura e consulta de dados
writePOST, PUT, PATCHCriação e atualização de registros
deleteDELETEExclusã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.

python
import 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=100 para 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 PATCH com apenas os campos alterados ao invés de PUT com 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/json em requisições POST, PUT e PATCH

Checklist de Depuração

Se algo não está funcionando, verifique:

  1. O token começa com bnt_?
  2. O header está correto? (Authorization: Bearer bnt_xxx, com B maiúsculo em Bearer)
  3. A URL está correta? Endpoints da API v1 começam com /v1/, não /api/
  4. O token está ativo? Verifique no painel se não foi revogado
  5. O token tem os escopos necessários para o módulo e ação?
  6. O IP está na allowlist? Se o token tem restrição de IP, verifique se seu IP está na lista
  7. O Content-Type: application/json está 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.

python
import 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.

javascript
class 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.

APIAutenticaçãocURLGuiaJavaScriptPythonREST
Recursos para IA