API de Categorias
CRUD completo de categorias hierárquicas via API. Organize produtos em árvore de categorias.
Gerencie a árvore de categorias do Bunto ERP. Este endpoint permite listar, consultar, criar, atualizar e excluir categorias de forma hierárquica (categorias pai e subcategorias) via API.
Base URL
Produção:
https://api-backend.bunto.com.br/v1/categorias/
Autenticação
Todas as requisições exigem um Token de API no header Authorization:
Authorization: Bearer bnt_seu_token_aqui
Tokens são gerados em Integrações -> Tokens de API no painel do Bunto ERP. Escopo necessário: categorias com a ação correspondente (read, write ou delete).
Endpoints
Listar Categorias
GET /v1/categorias/
Escopo necessário: categorias: read
Retorna a lista paginada de categorias da empresa.
Query Parameters
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
pagina | integer | 1 | Número da página |
por_pagina | integer | 25 | Registros por página (máximo: 100) |
busca | string | - | Busca por nome ou código |
ativo | boolean | - | Filtrar por status: true (ativas) ou false (inativas) |
raiz | boolean | - | true para retornar apenas categorias raiz (sem categoria pai) |
categoria_pai_id | integer | - | Filtrar subcategorias de uma categoria pai específica |
ordenar | string | nome | Campo de ordenação: nome, ordem, codigo |
direcao | string | asc | Direção da ordenação: asc ou desc |
Exemplo com cURL
bashcurl 'https://api-backend.bunto.com.br/v1/categorias/?pagina=1&por_pagina=10&raiz=true&ordenar=ordem&direcao=asc' \ -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 categorias raiz ordenadas por ordem resposta = requests.get( f"{BASE_URL}/categorias/", headers=headers, params={ "pagina": 1, "por_pagina": 25, "raiz": "true", "ordenar": "ordem", "direcao": "asc", }, ) dados = resposta.json() if dados["success"]: categorias = dados["data"]["resultados"] paginacao = dados["data"]["paginacao"] print(f"Total de categorias: {paginacao['total_registros']}") for categoria in categorias: print(f" - [{categoria['codigo']}] {categoria['nome']} (nível {categoria['nivel']})") else: print(f"Erro: {resposta.status_code}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const params = new URLSearchParams({ pagina: "1", por_pagina: "25", raiz: "true", ordenar: "ordem", direcao: "asc", }); const resposta = await fetch(`${BASE_URL}/categorias/?${params}`, { headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, }); const dados = await resposta.json(); if (dados.success) { const categorias = dados.data.resultados; const paginacao = dados.data.paginacao; console.log(`Total de categorias: ${paginacao.total_registros}`); categorias.forEach((categoria) => { console.log(` - [${categoria.codigo}] ${categoria.nome} (nível ${categoria.nivel})`); }); } else { console.error(`Erro: ${resposta.status}`); }
Resposta (200 OK)
json{ "success": true, "message": "8 registros encontrados", "data": { "resultados": [ { "id": 1, "nome": "Vestuário", "codigo":"VES", "caminho_completo": "Vestuário", "ativo": true, "ordem": 1, "categoria_pai_id": null, "nivel": 0 }, { "id": 2, "nome": "Calçados", "codigo":"CAL", "caminho_completo": "Calçados", "ativo": true, "ordem": 2, "categoria_pai_id": null, "nivel": 0 }, { "id": 5, "nome": "Acessórios", "codigo":"ACE", "caminho_completo": "Acessórios", "ativo": true, "ordem": 3, "categoria_pai_id": null, "nivel": 0 } ], "paginacao": { "pagina_atual": 1, "total_paginas": 1, "total_registros": 8, "por_pagina": 25, "proxima": null, "anterior": null } } }
Campos da Listagem
| Campo | Tipo | Descrição |
|---|---|---|
id | integer | Identificador único da categoria |
nome | string | Nome da categoria |
codigo | string / null | Código da categoria |
caminho_completo | string | Caminho hierárquico completo (ex: "Vestuário > Camisetas > Manga Longa") |
ativo | boolean | Se a categoria está ativa |
ordem | integer | Posição de ordenação dentro do mesmo nível |
categoria_pai_id | integer / null | ID da categoria pai (null para categorias raiz) |
nivel | integer | Nível hierárquico (0 = raiz, 1 = primeiro nível, 2 = segundo nível, etc.) |
Obter Categoria
GET /v1/categorias/{id}/
Escopo necessário: categorias: read
Retorna os dados completos de uma categoria específica, incluindo a lista de subcategorias diretas.
Exemplo com cURL
bashcurl https://api-backend.bunto.com.br/v1/categorias/1/ \ -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", } categoria_id = 1 resposta = requests.get(f"{BASE_URL}/categorias/{categoria_id}/", headers=headers) dados = resposta.json() if dados["success"]: categoria = dados["data"] print(f"Categoria: {categoria['nome']}") print(f"Caminho: {categoria['caminho_completo']}") if categoria["subcategorias"]: print("Subcategorias:") for sub in categoria["subcategorias"]: print(f" - {sub['nome']}") else: print(f"Erro: {dados}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const categoriaId = 1; const resposta = await fetch(`${BASE_URL}/categorias/${categoriaId}/`, { headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, }); const dados = await resposta.json(); if (dados.success) { const categoria = dados.data; console.log(`Categoria: ${categoria.nome}`); console.log(`Caminho: ${categoria.caminho_completo}`); if (categoria.subcategorias && categoria.subcategorias.length > 0) { console.log("Subcategorias:"); categoria.subcategorias.forEach((sub) => { console.log(` - ${sub.nome}`); }); } } else { console.error(`Erro: ${JSON.stringify(dados)}`); }
Resposta (200 OK)
json{ "success": true, "message": "Registro encontrado", "data": { "id": 1, "nome": "Vestuário", "codigo":"VES", "caminho_completo": "Vestuário", "ativo": true, "ordem": 1, "categoria_pai_id": null, "nivel": 0, "descricao":"Roupas e peças de vestuário em geral.", "icone": "shirt", "cor": "#4A90D9", "id_externo": null, "data_criacao": "2026-01-10T09:00:00-03:00", "data_atualizacao": "2026-02-05T14:30:00-03:00", "categoria_pai_nome": null, "subcategorias": [ { "id": 3, "nome": "Camisetas", "codigo":"VES-CAM", "ativo": true, "ordem": 1 }, { "id": 4, "nome": "Calças", "codigo":"VES-CAL", "ativo": true, "ordem": 2 }, { "id": 7, "nome": "Bermudas", "codigo":"VES-BER", "ativo": true, "ordem": 3 } ] } }
Campos do Detalhe (adicionais à listagem)
| Campo | Tipo | Descrição |
|---|---|---|
descricao | string / null | Descrição da categoria |
icone | string / null | Nome do ícone associado (máximo 50 caracteres) |
cor | string / null | Cor associada em formato hexadecimal ou nome (máximo 20 caracteres) |
id_externo | string / null | Identificador externo para integrações (máximo 50 caracteres) |
data_criacao | datetime | Data e hora de criação |
data_atualizacao | datetime | Data e hora da última atualização |
categoria_pai_nome | string / null | Nome da categoria pai (null para categorias raiz) |
subcategorias | array | Lista de subcategorias diretas (id, nome, código, ativo, ordem) |
Criar Categoria
POST /v1/categorias/
Escopo necessário: categorias: write
Cria uma nova categoria na empresa do token autenticado. Pode ser uma categoria raiz ou uma subcategoria (informando categoria_pai_id).
Campos do Request Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
nome | string | Sim | Nome da categoria (máximo 100 caracteres) |
codigo | string | Não | Código da categoria (máximo 20 caracteres) |
descricao | string | Não | Descrição da categoria |
categoria_pai_id | integer | Não | ID da categoria pai (null para categoria raiz) |
ativo | boolean | Não | Se a categoria está ativa (padrão: true) |
icone | string | Não | Nome do ícone (máximo 50 caracteres) |
cor | string | Não | Cor associada (máximo 20 caracteres) |
ordem | integer | Não | Posição de ordenação (padrão: 0) |
id_externo | string | Não | Identificador externo para integrações (máximo 50 caracteres) |
Exemplo com cURL
bashcurl -X POST https://api-backend.bunto.com.br/v1/categorias/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json' \ -d '{ "nome": "Manga Longa", "codigo":"VES-CAM-ML", "descricao":"Camisetas de manga longa para todas as estações.", "categoria_pai_id": 3, "ativo": true, "icone": "shirt", "cor": "#2E7D32", "ordem": 1 }'
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", } nova_categoria = { "nome": "Manga Longa", "codigo":"VES-CAM-ML", "descricao":"Camisetas de manga longa para todas as estações.", "categoria_pai_id": 3, "ativo": True, "icone": "shirt", "cor": "#2E7D32", "ordem": 1, } resposta = requests.post(f"{BASE_URL}/categorias/", headers=headers, json=nova_categoria) dados = resposta.json() if dados["success"]: categoria = dados["data"] print(f"Categoria criada com sucesso! ID: {categoria['id']}") print(f"Caminho: {categoria['caminho_completo']}") else: print(f"Erro ao criar categoria: {dados}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const novaCategoria = { nome: "Manga Longa", codigo: "VES-CAM-ML", descricao: "Camisetas de manga longa para todas as estações.", categoria_pai_id: 3, ativo: true, icone: "shirt", cor: "#2E7D32", ordem: 1, }; const resposta = await fetch(`${BASE_URL}/categorias/`, { method: "POST", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(novaCategoria), }); const dados = await resposta.json(); if (dados.success) { console.log(`Categoria criada com sucesso! ID: ${dados.data.id}`); console.log(`Caminho: ${dados.data.caminho_completo}`); } else { console.error(`Erro ao criar categoria:`, dados); }
Resposta (201 Created)
json{ "success": true, "message": "Categoria criada com sucesso", "data": { "id": 12, "nome": "Manga Longa", "codigo":"VES-CAM-ML", "caminho_completo": "Vestuário > Camisetas > Manga Longa", "ativo": true, "ordem": 1, "categoria_pai_id": 3, "nivel": 2, "descricao":"Camisetas de manga longa para todas as estações.", "icone": "shirt", "cor": "#2E7D32", "id_externo": null, "data_criacao": "2026-02-12T15:30:00-03:00", "data_atualizacao": "2026-02-12T15:30:00-03:00", "categoria_pai_nome": "Camisetas", "subcategorias": [] } }
Atualizar Categoria
PUT /v1/categorias/{id}/
PATCH /v1/categorias/{id}/
Escopo necessário: categorias: write
Atualiza uma categoria existente. Use PUT para atualização completa ou PATCH para atualização parcial (apenas os campos enviados serão alterados).
Exemplo com cURL (PATCH - atualização parcial)
bashcurl -X PATCH https://api-backend.bunto.com.br/v1/categorias/12/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json' \ -d '{ "nome": "Manga Longa Inverno", "descricao":"Camisetas de manga longa ideais para o inverno.", "ordem": 2 }'
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", } categoria_id = 12 # Atualizacao parcial (PATCH) - apenas os campos que mudaram atualizacao = { "nome": "Manga Longa Inverno", "descricao":"Camisetas de manga longa ideais para o inverno.", "ordem": 2, } resposta = requests.patch( f"{BASE_URL}/categorias/{categoria_id}/", headers=headers, json=atualizacao, ) dados = resposta.json() if dados["success"]: categoria = dados["data"] print(f"Categoria atualizada! Nome: {categoria['nome']}") print(f"Caminho: {categoria['caminho_completo']}") else: print(f"Erro ao atualizar: {dados}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const categoriaId = 12; const atualizacao = { nome: "Manga Longa Inverno", descricao: "Camisetas de manga longa ideais para o inverno.", ordem: 2, }; const resposta = await fetch(`${BASE_URL}/categorias/${categoriaId}/`, { method: "PATCH", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(atualizacao), }); const dados = await resposta.json(); if (dados.success) { console.log(`Categoria atualizada! Nome: ${dados.data.nome}`); console.log(`Caminho: ${dados.data.caminho_completo}`); } else { console.error(`Erro ao atualizar:`, dados); }
Resposta (200 OK)
json{ "success": true, "message": "Categoria atualizada com sucesso", "data": { "id": 12, "nome": "Manga Longa Inverno", "codigo":"VES-CAM-ML", "caminho_completo": "Vestuário > Camisetas > Manga Longa Inverno", "ativo": true, "ordem": 2, "categoria_pai_id": 3, "nivel": 2, "descricao":"Camisetas de manga longa ideais para o inverno.", "icone": "shirt", "cor": "#2E7D32", "id_externo": null, "data_criacao": "2026-02-12T15:30:00-03:00", "data_atualizacao": "2026-02-12T16:45:00-03:00", "categoria_pai_nome": "Camisetas", "subcategorias": [] } }
Excluir Categoria (Soft Delete)
DELETE /v1/categorias/{id}/
Escopo necessário: categorias: delete
Desativa a categoria (soft delete). O registro não é removido permanentemente do banco de dados -- o campo ativo é definido como false, preservando o histórico e os vínculos com produtos.
Exemplo com cURL
bashcurl -X DELETE https://api-backend.bunto.com.br/v1/categorias/12/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4'
Exemplo com Python
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = {"Authorization": f"Bearer {TOKEN}"} categoria_id = 12 resposta = requests.delete(f"{BASE_URL}/categorias/{categoria_id}/", headers=headers) dados = resposta.json() if dados["success"]: print("Categoria excluída com sucesso!") else: print(f"Erro ao excluir: {dados}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const categoriaId = 12; const resposta = await fetch(`${BASE_URL}/categorias/${categoriaId}/`, { method: "DELETE", headers: { Authorization: `Bearer ${TOKEN}`, }, }); const dados = await resposta.json(); if (dados.success) { console.log("Categoria excluída com sucesso!"); } else { console.error(`Erro ao excluir:`, dados); }
Resposta (200 OK)
json{ "success": true, "message": "Registro excluído com sucesso" }
Importante: Categorias desativadas via soft delete não aparecem mais nas listagens com filtro ativo=true. Para visualizar categorias inativas, filtre por ativo=false.
Erros Comuns
| Código | Erro | Causa | Solução |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Dados enviados são inválidos (campo obrigatório ausente, formato incorreto, etc.) | Verifique os campos obrigatórios e os tipos de dados |
| 401 | Token invalido | Token ausente, expirado, revogado ou mal formatado | Verifique se o header é Authorization: Bearer bnt_xxx |
| 403 | Token nao tem permissao | O token não possui o escopo categorias ou a ação necessária (read, write, delete) | Verifique os escopos do token no painel |
| 404 | Nao encontrado | Categoria não existe ou pertence a outra empresa | Confirme o ID e se a categoria pertence à empresa do token |
| 429 | Limite de requisicoes excedido | Rate limit ultrapassado para o tipo de operação | Implemente retry com backoff exponencial |
Exemplo de Resposta de Erro (400)
json{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Erro de validacao", "details": { "nome": ["Este campo é obrigatorio."], "codigo": ["Certifique-se de que este campo nao tenha mais de 20 caracteres."] } } }
Exemplo de Resposta de Erro (403)
json{ "detail": "Token nao tem permissao para este recurso" }
Rate Limiting
A API aplica limites de requisição por token (não por IP). Cada tipo de operação tem um limite diferente.
| Operação | Métodos HTTP | Limite |
|---|---|---|
| Leitura | GET, HEAD, OPTIONS | 120 requisições/minuto |
| Escrita | POST, PUT, PATCH | 30 requisições/minuto |
| Exclusão | DELETE | 10 requisições/minuto |
Ao exceder o limite, a API retorna 429 Too Many Requests:
json{ "detail": "Limite de requisicoes excedido. Tente novamente em 45 segundos." }
Boas práticas
- Use
por_pagina=100para reduzir o número de requisições ao listar categorias - Implemente retry com backoff exponencial ao receber
429 - Armazene dados em cache local quando possível
- Para atualizações em lote, use
PATCHapenas com os campos alterados para economizar requisições de escrita
Paginação
Todos os endpoints de listagem retornam dados paginados.
Parâmetros
| Parâmetro | Tipo | Padrão | Máximo | Descrição |
|---|---|---|---|---|
pagina | integer | 1 | - | Número da página |
por_pagina | integer | 25 | 100 | Registros por página |
Atenção: Os parâmetros são pagina e por_pagina (em português), não page e per_page.
Objeto paginacao na resposta
| Campo | Tipo | Descrição |
|---|---|---|
pagina_atual | integer | Número da página atual |
total_paginas | integer | Total de páginas disponíveis |
total_registros | integer | Total de registros encontrados |
por_pagina | integer | Registros por página |
proxima | string / null | URL da próxima página (null se for a última) |
anterior | string / null | URL da página anterior (null se for a primeira) |
Exemplo: percorrer todas as páginas (Python)
pythonimport requests BASE_URL = "https://api-backend.bunto.com.br/v1" TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4" headers = {"Authorization": f"Bearer {TOKEN}"} todas_as_categorias = [] url = f"{BASE_URL}/categorias/?por_pagina=100" while url: resposta = requests.get(url, headers=headers) dados = resposta.json() if not dados["success"]: break todas_as_categorias.extend(dados["data"]["resultados"]) url = dados["data"]["paginacao"]["proxima"] print(f"Total obtido: {len(todas_as_categorias)} categorias")