Autenticação API v1
Como autenticar requisições na API pública do Bunto ERP usando tokens Bearer.
Tudo sobre autenticação na API v1: geração de tokens, escopos OAuth, restrição por IP e boas práticas de segurança para suas integrações.
Visão Geral
| Propriedade | Valor |
|---|---|
| Tipo | Token de API (Bearer Token) |
| Prefixo | bnt_ |
| Tamanho | ~68 caracteres (bnt_ + 48 bytes urlsafe) |
| Transporte | Header Authorization: Bearer bnt_xxx |
| Expiração | Configurável (ou sem expiração) |
| Criptografia | Fernet (nunca armazenado em texto puro) |
| Geração | Painel administrativo: Integrações → Tokens de API |
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.
Base URL
https://api-backend.bunto.com.br/v1/
Gerando um Token
- Acesse o painel do Bunto ERP
- Navegue até Integrações → Tokens de API
- Clique em Novo Token
- Defina um nome descritivo (ex: "Integração Loja Virtual", "Webhook Sistema Externo")
- Opcionalmente, defina uma data de expiração e IPs permitidos
- Copie o token gerado imediatamente - ele não será exibido novamente
O token gerado terá o formato:
bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4
Autenticando Requisições
Inclua o token no header Authorization de todas as requisições:
Authorization: Bearer bnt_seu_token_aqui
Exemplo com cURL
bashcurl https://api-backend.bunto.com.br/v1/produtos/ \ -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 produtos 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['nome']}") else: print(f"Erro: {resposta.status_code}")
Exemplo com JavaScript (fetch)
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const headers = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }; // Listar produtos 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.nome}`); }); } else { console.error(`Erro: ${resposta.status}`); }
Exemplo com JavaScript (axios)
javascriptimport axios from "axios"; const api = axios.create({ baseURL: "https://api-backend.bunto.com.br/v1", headers: { Authorization: `Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4`, "Content-Type": "application/json", }, }); // Listar produtos const { data } = await api.get("/produtos/"); if (data.success) { console.log(`Total: ${data.data.paginacao.total_registros}`); }
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.
Formato dos Escopos
json{ "produtos": ["read", "write"], "vendas": ["read"], "clientes": ["read", "write", "delete"] }
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 |
Módulos Disponíveis
A API v1 suporta 34 módulos:
| Módulo | Descrição |
|---|---|
produtos | Cadastro de produtos |
clientes | Cadastro de clientes |
vendas | Pedidos de venda |
estoque | Controle de estoque |
servicos | Cadastro de serviços |
categorias | Categorias de produtos |
marcadores | Marcadores/tags |
embalagens | Tipos de embalagem |
financas | Contas a pagar e receber |
compras | Pedidos de compra |
nf | Notas fiscais |
nfse | Notas fiscais de serviço (NFS-e) |
cobrancas_bancarias | Boletos e cobranças |
contas | Contas bancárias |
naturezas | Naturezas de operação |
regras_tributarias | Regras e cenários tributários |
distribuicao_dfe | Distribuição de DF-e |
pdv | Ponto de venda |
propostas | Propostas comerciais |
devolucoes | Devoluções de venda |
vale_troca | Vales-troca |
logistica | Logística e frete |
expedicao | Expedição de pedidos |
crm | Gestão de relacionamento |
marketing | Campanhas de marketing |
ocorrencias | Registro de ocorrências |
agenda | Agenda e compromissos |
arquivos | Gerenciamento de arquivos |
configuracoes | Configurações do sistema |
usuarios | Gestão de usuários |
empresas | Dados da empresa |
dashboard | Dados do painel |
chat_ia | Chat com inteligência artificial |
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"] } }
Exemplos de Configuração OAuth
Token somente leitura para produtos e estoque:
json{ "produtos": ["read"], "estoque": ["read"] }
Token com acesso completo a vendas e clientes:
json{ "vendas": ["read", "write", "delete"], "clientes": ["read", "write", "delete"] }
Token de integração e-commerce (cenário típico):
json{ "produtos": ["read"], "vendas": ["read", "write"], "clientes": ["read", "write"], "estoque": ["read"] }
Restrição por IP (Allowlist)
Opcionalmente, você pode restringir o uso de um token a uma lista de IPs permitidos. Quando configurado, requisições de IPs fora da lista serão rejeitadas.
Formatos Suportados
| Formato | Exemplo | Descrição |
|---|---|---|
| IP exato | 203.0.113.50 | Permite apenas este IP |
| CIDR | 10.0.0.0/8 | Permite toda a faixa de rede |
Nota: Se a lista de IPs permitidos estiver vazia, o token aceita requisições de qualquer IP.
Formato das Respostas
Todas as respostas da API v1 seguem o envelope padrão do Bunto.
Sucesso - Listagem (200)
json{ "success": true, "message": "42 registros encontrados", "data": { "resultados": [ { "id": 1, "nome": "Camiseta Basica", "sku": "CAM-001", "preco":49.90 } ], "paginacao": { "pagina_atual": 1, "total_paginas": 2, "total_registros": 42, "por_pagina": 25, "proxima": "https://api-backend.bunto.com.br/v1/produtos/?pagina=2", "anterior": null } } }
Sucesso - Criação (201)
json{ "success": true, "message": "Registro criado com sucesso", "data": { "id": 43, "nome": "Camiseta Premium", "sku": "CAM-002", "preco":89.90 } }
Sucesso - Atualização (200)
json{ "success": true, "message": "Registro atualizado com sucesso", "data": { "id": 43, "nome": "Camiseta Premium V2", "sku": "CAM-002", "preco":99.90 } }
Sucesso - Exclusão (200)
json{ "success": true, "message": "Registro excluído com sucesso" }
Erro - Validação (400)
json{ "success": false, "message": "Este campo é obrigatorio.", "errors": { "nome": ["Este campo é obrigatorio."], "preco": ["Informe um numero valido."] } }
Erro - Autenticação (401)
json{ "success": false, "message": "Token invalido", "errors": { "detail": ["Token invalido"] } }
Erro - Permissão (403) -- escopo insuficiente
json{ "success": false, "message": "Permissao negada", "errors": { "detail": ["Token nao tem permissao para este recurso"] } }
Erro - Permissão (403) -- módulo fora do plano
json{ "success": false, "message": "Permissao negada", "errors": { "detail": ["Modulo \"vendas\" nao disponivel no plano da empresa"] } }
Paginação
Endpoints que retornam listas suportam paginação via parâmetros de query string.
Parâmetros
| Parâmetro | Tipo | Padrão | Máximo | Descrição |
|---|---|---|---|---|
pagina | integer | 1 | - | Número da página |
por_pagina | integer | 25 | 100 | Quantidade de registros por página |
Atenção: Os parâmetros são pagina e por_pagina (em português), não page e per_page.
Exemplo
bash# Página 2 com 50 registros por página curl https://api-backend.bunto.com.br/v1/produtos/?pagina=2&por_pagina=50 \ -H 'Authorization: Bearer bnt_seu_token_aqui'
Resposta Paginada
json{ "success": true, "message": "150 registros encontrados", "data": { "resultados": [...], "paginacao": { "pagina_atual": 2, "total_paginas": 3, "total_registros": 150, "por_pagina": 50, "proxima": "https://api-backend.bunto.com.br/v1/produtos/?pagina=3&por_pagina=50", "anterior": "https://api-backend.bunto.com.br/v1/produtos/?pagina=1&por_pagina=50" } } }
Navegação entre Páginas (Python)
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_seu_token_aqui" headers = {"Authorization": f"Bearer {TOKEN}"} todos_os_produtos = [] 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_produtos.extend(dados["data"]["resultados"]) url = dados["data"]["paginacao"]["proxima"] print(f"Total obtido: {len(todos_os_produtos)} produtos")
Rate Limiting (Limites de Requisição)
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 | Limite |
|---|---|---|
| Leitura | GET, HEAD, OPTIONS | 120 requisições/minuto |
| Escrita | POST, PUT, PATCH | 30 requisições/minuto |
| Exclusão | DELETE | 10 requisições/minuto |
Resposta ao Exceder o Limite (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."] } }
Boas Práticas
- Use paginação eficiente: prefira
por_pagina=100para reduzir o número de requisições - Implemente retry com backoff: ao receber 429, aguarde o tempo indicado e tente novamente
- Cache local: armazene dados que não mudam com frequência para evitar requisições desnecessárias
- Operações em lote: quando disponível, use endpoints de criação/atualização em lote ao invés de requisições individuais
Exemplo de Retry com Backoff (Python)
pythonimport time import requests def requisicao_com_retry(url, headers, max_tentativas=3): 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")
Proteção contra Brute-Force
A API implementa proteção automática contra tentativas de força bruta.
| Configuração | Valor |
|---|---|
| Tentativas falhas por IP | 20 por janela |
| Janela de bloqueio | 15 minutos |
| Mensagem de bloqueio | "Acesso temporariamente bloqueado. Tente novamente mais tarde." |
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, independentemente do token utilizado.
Nota: Uma autenticação bem-sucedida reseta o contador de falhas para o IP.
Códigos de Status HTTP
| Código | Significado | Quando ocorre |
|---|---|---|
| 200 | Sucesso | Consulta, atualização ou exclusão bem-sucedida |
| 201 | Criado | Registro criado com sucesso via POST |
| 400 | Erro de validação | Dados enviados são inválidos |
| 401 | Não autenticado | Token ausente, inválido, expirado ou revogado |
| 403 | Sem permissão | Token sem escopo para o módulo/ação, ou módulo não está no plano da empresa |
| 404 | Não encontrado | Recurso não existe ou pertence a outra empresa |
| 405 | Método não permitido | Método HTTP não suportado pelo endpoint |
| 429 | Muitas requisições | Limite de rate limiting excedido |
| 500 | Erro interno | Erro inesperado no servidor |
Segurança - Boas Práticas
Armazenamento do Token
- Nunca inclua o token diretamente no código-fonte
- Use variáveis de ambiente ou um gerenciador de segredos
- Nunca exponha o token em logs, URLs ou repositórios Git
pythonimport os # Correto: variável de ambiente TOKEN = os.environ["BUNTO_API_TOKEN"] # Errado: hardcoded TOKEN = "bnt_A1b2C3d4E5f6G7h8..." # Nao FAÇA ISSO
javascript// Correto: variável de ambiente const TOKEN = process.env.BUNTO_API_TOKEN; // Errado: hardcoded const TOKEN = "bnt_A1b2C3d4E5f6G7h8..."; // Nao FAÇA ISSO
Princípio do Menor Privilégio
Crie tokens com apenas os escopos necessários para a integração. Um token que só precisa ler produtos não deve ter acesso a vendas ou clientes.
Rotação de Tokens
- Revogue tokens que não estão mais em uso
- Crie novos tokens periodicamente para integrações críticas
- Monitore o campo "último uso" no painel para identificar tokens abandonados
Restrição por IP
Sempre que possível, configure a lista de IPs permitidos para o token. Isso garante que, mesmo em caso de vazamento, o token só funcionará a partir dos IPs autorizados.
Diferenças entre Token API e JWT
| Característica | Token API (v1) | JWT (Painel) |
|---|---|---|
| Uso | Integrações externas, automações | Painel administrativo do ERP |
| Base URL | /v1/ | /api/ |
| Formato | bnt_ + 48 chars | JWT (eyJ...) |
| Transporte | Header Authorization | Cookies HttpOnly |
| Expiração | Configurável (ou sem expiração) | Access: 8h, Refresh: 7d |
| Escopo | Por módulo e ação (read/write/delete) | Permissões do usuário |
| Identidade | Empresa (sem usuário específico) | Usuário autenticado |
| Geração | Painel Integrações → Tokens de API | Login com credenciais |
Exemplo Completo - Integração E-commerce
O exemplo abaixo demonstra uma integração típica que sincroniza pedidos de uma loja virtual com o Bunto ERP.
Python
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") def listar_produtos(self, pagina=1, por_pagina=25): """Lista produtos com paginacao.""" resposta = self._requisicao( "GET", "/produtos/", params={"pagina": pagina, "por_pagina": por_pagina}, ) return resposta.json() def buscar_produto(self, produto_id): """Busca um produto pelo ID.""" resposta = self._requisicao("GET", f"/produtos/{produto_id}/") return resposta.json() def criar_pedido(self, dados_pedido): """Cria um novo pedido de venda.""" resposta = self._requisicao("POST", "/vendas/", json=dados_pedido) return resposta.json() def consultar_estoque(self, produto_id): """Consulta o estoque de um produto.""" resposta = self._requisicao("GET", f"/estoque/{produto_id}/") return resposta.json() # Uso api = BuntoAPI() # Listar todos os produtos (com paginacao automatica) pagina = 1 todos_produtos = [] while True: resultado = api.listar_produtos(pagina=pagina, por_pagina=100) if not resultado["success"]: print(f"Erro: {resultado}") break todos_produtos.extend(resultado["data"]["resultados"]) paginacao = resultado["data"]["paginacao"] if paginacao["proxima"] is None: break pagina += 1 print(f"Total de produtos: {len(todos_produtos)}") # Criar um pedido novo_pedido = api.criar_pedido({ "cliente_id": 42, "itens": [ {"produto_id": 1, "quantidade": 2, "preco_unitario": 49.90}, {"produto_id": 5, "quantidade": 1, "preco_unitario": 129.90}, ], }) if novo_pedido["success"]: print(f"Pedido criado: #{novo_pedido['data']['id']}")
JavaScript (Node.js)
javascriptclass BuntoAPI { constructor(token, baseUrl = null) { 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", }; } 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"); } async listarProdutos(pagina = 1, porPagina = 25) { const params = new URLSearchParams({ pagina: String(pagina), por_pagina: String(porPagina), }); return this.requisicao("GET", `/produtos/?${params}`); } async criarPedido(dadosPedido) { return this.requisicao("POST", "/vendas/", { body: JSON.stringify(dadosPedido), }); } } // Uso const api = new BuntoAPI(); const resultado = await api.listarProdutos(1, 50); if (resultado.success) { console.log(`Total: ${resultado.data.paginacao.total_registros} produtos`); }
Resolução de Problemas
Erros Frequentes
| Sintoma | Causa Provável | Solução |
|---|---|---|
| 401 em todas as requisições | Token incorreto ou mal formatado | Verifique se o header é Authorization: Bearer bnt_xxx (com espaço após Bearer) |
| 401 intermitente | Token expirado | Verifique a data de expiração no painel e gere um novo token |
| 403 ao criar/atualizar | Token sem escopo de escrita | Verifique os escopos do token no painel (precisa de write) |
| 403 ao excluir | Token sem escopo de exclusão | Verifique os escopos do token no painel (precisa de delete) |
| 403 "Empresa não está ativa" | Empresa desativada | Entre em contato com o suporte do Bunto |
| 429 Too Many Requests | Rate limit excedido | Implemente retry com backoff exponencial |
| 401 "Acesso temporariamente bloqueado" | Proteção brute-force ativada | Aguarde 15 minutos e verifique se o token está correto |
| 404 em recurso existente | Recurso pertence a outra empresa | Cada token só acessa dados da própria empresa |
Checklist de Depuração
- O token começa com
bnt_? Tokens de API sempre têm o prefixobnt_ - O header está correto? Deve ser
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 o token não foi revogado
- O token tem os escopos necessários? Verifique se o módulo e a ação estão nos escopos
- O IP está na allowlist? Se o token tem restrição de IP, verifique se seu IP está na lista
- O Content-Type está definido? Para POST/PUT/PATCH, inclua
Content-Type: application/json
Testando a Autenticação
Para verificar se seu token está funcionando, faça uma requisição simples de leitura:
bashcurl -v https://api-backend.bunto.com.br/v1/produtos/?pagina=1&por_pagina=1 \ -H 'Authorization: Bearer bnt_seu_token_aqui'
Se a resposta for 200 com "success": true, seu token está configurado corretamente.