Developers/Documentação da API/Autenticação API v1

Autenticação API v1

Como autenticar requisições na API pública do Bunto ERP usando tokens Bearer.

13/02/202615 min de leitura68 visualizaçõesv3

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

PropriedadeValor
TipoToken de API (Bearer Token)
Prefixobnt_
Tamanho~68 caracteres (bnt_ + 48 bytes urlsafe)
TransporteHeader Authorization: Bearer bnt_xxx
ExpiraçãoConfigurável (ou sem expiração)
CriptografiaFernet (nunca armazenado em texto puro)
GeraçãoPainel 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

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

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

Exemplo com Python

python
import requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = { "Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json", } # Listar 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)

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

javascript
import 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:

  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.

Formato dos Escopos

json
{ "produtos": ["read", "write"], "vendas": ["read"], "clientes": ["read", "write", "delete"] }

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

Módulos Disponíveis

A API v1 suporta 34 módulos:

MóduloDescrição
produtosCadastro de produtos
clientesCadastro de clientes
vendasPedidos de venda
estoqueControle de estoque
servicosCadastro de serviços
categoriasCategorias de produtos
marcadoresMarcadores/tags
embalagensTipos de embalagem
financasContas a pagar e receber
comprasPedidos de compra
nfNotas fiscais
nfseNotas fiscais de serviço (NFS-e)
cobrancas_bancariasBoletos e cobranças
contasContas bancárias
naturezasNaturezas de operação
regras_tributariasRegras e cenários tributários
distribuicao_dfeDistribuição de DF-e
pdvPonto de venda
propostasPropostas comerciais
devolucoesDevoluções de venda
vale_trocaVales-troca
logisticaLogística e frete
expedicaoExpedição de pedidos
crmGestão de relacionamento
marketingCampanhas de marketing
ocorrenciasRegistro de ocorrências
agendaAgenda e compromissos
arquivosGerenciamento de arquivos
configuracoesConfigurações do sistema
usuariosGestão de usuários
empresasDados da empresa
dashboardDados do painel
chat_iaChat 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

FormatoExemploDescrição
IP exato203.0.113.50Permite apenas este IP
CIDR10.0.0.0/8Permite 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)

GET/v1/produtos/
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)

POST/v1/produtos/
json
{ "success": true, "message": "Registro criado com sucesso", "data": { "id": 43, "nome": "Camiseta Premium", "sku": "CAM-002", "preco":89.90 } }

Sucesso - Atualização (200)

PUT/v1/produtos/43/
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)

DELETE/v1/produtos/43/
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âmetroTipoPadrãoMáximoDescrição
paginainteger1-Número da página
por_paginainteger25100Quantidade 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" } } }
python
import 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çãoMétodosLimite
LeituraGET, HEAD, OPTIONS120 requisições/minuto
EscritaPOST, PUT, PATCH30 requisições/minuto
ExclusãoDELETE10 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=100 para 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)

python
import 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çãoValor
Tentativas falhas por IP20 por janela
Janela de bloqueio15 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ódigoSignificadoQuando ocorre
200SucessoConsulta, atualização ou exclusão bem-sucedida
201CriadoRegistro criado com sucesso via POST
400Erro de validaçãoDados enviados são inválidos
401Não autenticadoToken ausente, inválido, expirado ou revogado
403Sem permissãoToken sem escopo para o módulo/ação, ou módulo não está no plano da empresa
404Não encontradoRecurso não existe ou pertence a outra empresa
405Método não permitidoMétodo HTTP não suportado pelo endpoint
429Muitas requisiçõesLimite de rate limiting excedido
500Erro internoErro 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
python
import 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ísticaToken API (v1)JWT (Painel)
UsoIntegrações externas, automaçõesPainel administrativo do ERP
Base URL/v1//api/
Formatobnt_ + 48 charsJWT (eyJ...)
TransporteHeader AuthorizationCookies HttpOnly
ExpiraçãoConfigurável (ou sem expiração)Access: 8h, Refresh: 7d
EscopoPor módulo e ação (read/write/delete)Permissões do usuário
IdentidadeEmpresa (sem usuário específico)Usuário autenticado
GeraçãoPainel Integrações → Tokens de APILogin 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

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") 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)

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

SintomaCausa ProvávelSolução
401 em todas as requisiçõesToken incorreto ou mal formatadoVerifique se o header é Authorization: Bearer bnt_xxx (com espaço após Bearer)
401 intermitenteToken expiradoVerifique a data de expiração no painel e gere um novo token
403 ao criar/atualizarToken sem escopo de escritaVerifique os escopos do token no painel (precisa de write)
403 ao excluirToken sem escopo de exclusãoVerifique os escopos do token no painel (precisa de delete)
403 "Empresa não está ativa"Empresa desativadaEntre em contato com o suporte do Bunto
429 Too Many RequestsRate limit excedidoImplemente retry com backoff exponencial
401 "Acesso temporariamente bloqueado"Proteção brute-force ativadaAguarde 15 minutos e verifique se o token está correto
404 em recurso existenteRecurso pertence a outra empresaCada token só acessa dados da própria empresa

Checklist de Depuração

  1. O token começa com bnt_? Tokens de API sempre têm o prefixo bnt_
  2. O header está correto? Deve ser 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 o token não foi revogado
  5. O token tem os escopos necessários? Verifique se o módulo e a ação estão nos escopos
  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 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:

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

APIAutenticaçãocURLJavaScriptPythonRate LimitingREST
Recursos para IA