API de Propostas
CRUD de propostas comerciais com itens via API.
Gerencie as propostas comerciais do Bunto ERP. Este endpoint permite listar, consultar, criar, atualizar e cancelar propostas comerciais via API.
Base URL
Produção:
https://api-backend.bunto.com.br/v1/propostas/
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: propostas com a ação correspondente (read, write ou delete).
Endpoints
Listar Propostas
GET /v1/propostas/
Escopo necessário: propostas: read
Retorna a lista paginada de propostas comerciais 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 número da proposta ou nome do cliente |
status | string | - | Filtrar por status: rascunho, enviada, aprovada, rejeitada, revisao, expirada, cancelada, convertida |
data_inicio | string | - | Filtrar a partir desta data (formato: YYYY-MM-DD) |
data_fim | string | - | Filtrar até esta data (formato: YYYY-MM-DD) |
cliente_id | integer | - | Filtrar por ID do cliente |
ordenar | string | data_proposta | Campo de ordenação: data_proposta, total_proposta, numero_proposta |
direcao | string | desc | Direção da ordenação: asc ou desc |
Exemplo com cURL
bashcurl 'https://api-backend.bunto.com.br/v1/propostas/?pagina=1&por_pagina=10&status=enviada' \ -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 propostas enviadas ordenadas por data resposta = requests.get( f"{BASE_URL}/propostas/", headers=headers, params={ "pagina": 1, "por_pagina": 25, "status": "enviada", "ordenar": "data_proposta", "direcao": "desc", }, ) dados = resposta.json() if dados["success"]: propostas = dados["data"]["resultados"] paginacao = dados["data"]["paginacao"] print(f"Total de propostas: {paginacao['total_registros']}") for proposta in propostas: print(f" - #{proposta['numero_proposta']} | {proposta['cliente_nome']} | R$ {proposta['total_proposta']} | Validade: {proposta['validade_proposta_dias']} dias") 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", status: "enviada", ordenar: "data_proposta", direcao: "desc", }); const resposta = await fetch(`${BASE_URL}/propostas/?${params}`, { headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, }); const dados = await resposta.json(); if (dados.success) { const propostas = dados.data.resultados; const paginacao = dados.data.paginacao; console.log(`Total de propostas: ${paginacao.total_registros}`); propostas.forEach((proposta) => { console.log(` - #${proposta.numero_proposta} | ${proposta.cliente_nome} | R$ ${proposta.total_proposta} | Validade: ${proposta.validade_proposta_dias} dias`); }); } else { console.error(`Erro: ${resposta.status}`); }
Resposta (200 OK)
json{ "success": true, "message": "4 registros encontrados", "data": { "resultados": [ { "id": 78, "numero_proposta": "PROP-2026-078", "data_proposta": "2026-02-10T14:30:00-03:00", "status": "enviada", "cliente_id": 42, "cliente_nome": "Maria Silva Ltda", "vendedor_id": 5, "total_proposta": "8500.00", "validade_proposta_dias": 30 }, { "id": 76, "numero_proposta": "PROP-2026-076", "data_proposta": "2026-02-08T10:00:00-03:00", "status": "enviada", "cliente_id": 18, "cliente_nome": "Comércio ABC ME", "vendedor_id": 3, "total_proposta": "3200.00", "validade_proposta_dias": 15 } ], "paginacao": { "pagina_atual": 1, "total_paginas": 1, "total_registros": 4, "por_pagina": 25, "proxima": null, "anterior": null } } }
Campos da Listagem
| Campo | Tipo | Descrição |
|---|---|---|
id | integer | Identificador único da proposta |
numero_proposta | string / null | Número sequencial da proposta (ex: "PROP-2026-078") |
data_proposta | datetime | Data e hora de criação da proposta |
status | string | Status: rascunho, enviada, aprovada, rejeitada, revisao, expirada, cancelada, convertida |
cliente_id | integer | ID do cliente associado à proposta |
cliente_nome | string / null | Nome do cliente |
vendedor_id | integer / null | ID do vendedor responsável |
total_proposta | string (decimal) | Valor total da proposta (formato: "8500.00") |
validade_proposta_dias | integer / null | Prazo de validade da proposta em dias |
Obter Proposta
GET /v1/propostas/{id}/
Escopo necessário: propostas: read
Retorna os dados completos de uma proposta específica, incluindo itens, introdução, observações, desconto, frete e condições de pagamento.
Exemplo com cURL
bashcurl https://api-backend.bunto.com.br/v1/propostas/78/ \ -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", } proposta_id = 78 resposta = requests.get(f"{BASE_URL}/propostas/{proposta_id}/", headers=headers) dados = resposta.json() if dados["success"]: proposta = dados["data"] print(f"Proposta #{proposta['numero_proposta']} | Status: {proposta['status']}") print(f"Cliente: {proposta['cliente_nome']}") print(f"Total: R$ {proposta['total_proposta']} | Desconto: R$ {proposta['desconto']} | Frete: R$ {proposta['frete']}") print(f"Itens ({len(proposta['itens'])}):") for item in proposta["itens"]: print(f" - {item['descricao']} | {item['quantidade']}x R$ {item['preco_unitario']} = R$ {item['preco_total']}") else: print(f"Erro: {dados}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const propostaId = 78; const resposta = await fetch(`${BASE_URL}/propostas/${propostaId}/`, { headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, }); const dados = await resposta.json(); if (dados.success) { const proposta = dados.data; console.log(`Proposta #${proposta.numero_proposta} | Status: ${proposta.status}`); console.log(`Cliente: ${proposta.cliente_nome}`); console.log(`Total: R$ ${proposta.total_proposta}`); console.log(`Itens (${proposta.itens.length}):`); proposta.itens.forEach((item) => { console.log(` - ${item.descricao} | ${item.quantidade}x R$ ${item.preco_unitario} = R$ ${item.preco_total}`); }); } else { console.error(`Erro: ${JSON.stringify(dados)}`); }
Resposta (200 OK)
json{ "success": true, "message": "Registro encontrado", "data": { "id": 78, "numero_proposta": "PROP-2026-078", "data_proposta": "2026-02-10T14:30:00-03:00", "status": "enviada", "cliente_id": 42, "cliente_nome": "Maria Silva Ltda", "vendedor_id": 5, "total_proposta": "8500.00", "validade_proposta_dias": 30, "introducao": "Prezada Maria, segue nossa proposta comercial conforme solicitado.", "observacoes":"Proposta valida para pagamento à vista ou parcelado em ate 3x.", "desconto": "500.00", "frete": "150.00", "prazo_entrega": "15 dias úteis", "condicao_pagamento": "30/60/90 dias", "marcadores": "prioridade-alta,cliente-vip", "itens": [ { "id": 201, "tipo": "produto", "produto_id": 15, "servico_id": null, "descricao":"Notebook Dell Inspiron 15", "codigo":"NB-DELL-15", "unidade": "UN", "quantidade": "2", "preco_unitario": "3500.00", "preco_total": "7000.00" }, { "id": 202, "tipo": "servico", "produto_id": null, "servico_id": 8, "descricao":"Instalação e configuracao", "codigo":"SVC-INST", "unidade": "SV", "quantidade": "2", "preco_unitario": "250.00", "preco_total": "500.00" } ], "criado_em": "2026-02-10T14:30:00-03:00", "atualizado_em": "2026-02-11T09:00:00-03:00" } }
Campos do Detalhe (adicionais à listagem)
| Campo | Tipo | Descrição |
|---|---|---|
introducao | string / null | Texto de introdução da proposta |
observacoes | string / null | Observações gerais da proposta |
desconto | string (decimal) | Valor do desconto aplicado (formato: "500.00") |
frete | string (decimal) | Valor do frete (formato: "150.00") |
prazo_entrega | string / null | Prazo de entrega informado ao cliente |
condicao_pagamento | string / null | Condição de pagamento (ex: "30/60/90 dias") |
marcadores | string / null | Marcadores/tags separados por vírgula |
itens | array | Lista de itens da proposta (ver tabela abaixo) |
criado_em | datetime | Data e hora de criação |
atualizado_em | datetime | Data e hora da última atualização |
Campos do Item
| Campo | Tipo | Descrição |
|---|---|---|
id | integer | Identificador único do item |
tipo | string | Tipo do item: produto ou servico |
produto_id | integer / null | ID do produto (quando tipo = produto) |
servico_id | integer / null | ID do serviço (quando tipo = serviço) |
descricao | string | Descrição do item |
codigo | string | Código do produto ou serviço |
unidade | string | Unidade de medida (ex: "UN", "SV", "KG") |
quantidade | string (decimal) | Quantidade do item |
preco_unitario | string (decimal) | Preço unitário |
preco_total | string (decimal) | Preço total do item (quantidade x preço unitário) |
Criar Proposta
POST /v1/propostas/
Escopo necessário: propostas: write
Cria uma nova proposta comercial na empresa do token autenticado.
Campos do Request Body
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
cliente_id | integer | Sim | ID do cliente associado à proposta |
numero_proposta | string | Não | Número da proposta (máximo 20 caracteres, gerado automaticamente se omitido) |
status | string | Não | Status inicial: rascunho (padrão), enviada, aprovada, rejeitada, revisao, expirada, cancelada, convertida |
vendedor_id | integer | Não | ID do vendedor responsável |
validade_proposta_dias | integer | Não | Prazo de validade da proposta em dias |
introducao | string | Não | Texto de introdução da proposta |
observacoes | string | Não | Observações gerais |
desconto | decimal | Não | Valor do desconto (padrão: 0) |
frete | decimal | Não | Valor do frete (padrão: 0) |
prazo_entrega | string | Não | Prazo de entrega |
condicao_pagamento | string | Não | Condição de pagamento |
Exemplo com cURL
bashcurl -X POST https://api-backend.bunto.com.br/v1/propostas/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json' \ -d '{ "cliente_id": 42, "vendedor_id": 5, "validade_proposta_dias": 30, "introducao": "Prezado cliente, segue nossa proposta comercial.", "desconto": "200.00", "frete": "50.00", "prazo_entrega": "10 dias úteis", "condicao_pagamento": "À vista ou 2x sem juros" }'
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_proposta = { "cliente_id": 42, "vendedor_id": 5, "validade_proposta_dias": 30, "introducao": "Prezado cliente, segue nossa proposta comercial.", "desconto": "200.00", "frete": "50.00", "prazo_entrega": "10 dias úteis", "condicao_pagamento": "À vista ou 2x sem juros", } resposta = requests.post(f"{BASE_URL}/propostas/", headers=headers, json=nova_proposta) dados = resposta.json() if dados["success"]: proposta = dados["data"] print(f"Proposta criada com sucesso! ID: {proposta['id']}") print(f"Numero: {proposta['numero_proposta']} | Status: {proposta['status']}") print(f"Total: R$ {proposta['total_proposta']}") else: print(f"Erro ao criar proposta: {dados}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const novaProposta = { cliente_id: 42, vendedor_id: 5, validade_proposta_dias: 30, introducao: "Prezado cliente, segue nossa proposta comercial.", desconto: "200.00", frete: "50.00", prazo_entrega: "10 dias úteis", condicao_pagamento: "À vista ou 2x sem juros", }; const resposta = await fetch(`${BASE_URL}/propostas/`, { method: "POST", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(novaProposta), }); const dados = await resposta.json(); if (dados.success) { console.log(`Proposta criada com sucesso! ID: ${dados.data.id}`); console.log(`Numero: ${dados.data.numero_proposta} | Status: ${dados.data.status}`); } else { console.error(`Erro ao criar proposta:`, dados); }
Resposta (201 Created)
json{ "success": true, "message": "Proposta criada com sucesso", "data": { "id": 79, "numero_proposta": "PROP-2026-079", "data_proposta": "2026-02-12T16:00:00-03:00", "status": "rascunho", "cliente_id": 42, "cliente_nome": "Maria Silva Ltda", "vendedor_id": 5, "total_proposta": "0.00", "validade_proposta_dias": 30, "introducao": "Prezado cliente, segue nossa proposta comercial.", "observacoes":null, "desconto": "200.00", "frete": "50.00", "prazo_entrega": "10 dias úteis", "condicao_pagamento": "À vista ou 2x sem juros", "marcadores": null, "itens": [], "criado_em": "2026-02-12T16:00:00-03:00", "atualizado_em": "2026-02-12T16:00:00-03:00" } }
Atualizar Proposta
PUT /v1/propostas/{id}/
PATCH /v1/propostas/{id}/
Escopo necessário: propostas: write
Atualiza uma proposta 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/propostas/79/ \ -H 'Authorization: Bearer bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4' \ -H 'Content-Type: application/json' \ -d '{ "status": "enviada", "observacoes":"Proposta enviada ao cliente por e-mail em 12/02/2026" }'
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", } proposta_id = 79 atualizacao = { "status": "enviada", "observacoes":"Proposta enviada ao cliente por e-mail em 12/02/2026", } resposta = requests.patch( f"{BASE_URL}/propostas/{proposta_id}/", headers=headers, json=atualizacao, ) dados = resposta.json() if dados["success"]: proposta = dados["data"] print(f"Proposta atualizada! Status: {proposta['status']}") 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 propostaId = 79; const atualizacao = { status: "enviada", observacoes: "Proposta enviada ao cliente por e-mail em 12/02/2026", }; const resposta = await fetch(`${BASE_URL}/propostas/${propostaId}/`, { method: "PATCH", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(atualizacao), }); const dados = await resposta.json(); if (dados.success) { console.log(`Proposta atualizada! Status: ${dados.data.status}`); } else { console.error(`Erro ao atualizar:`, dados); }
Resposta (200 OK)
json{ "success": true, "message": "Proposta atualizada com sucesso", "data": { "id": 79, "numero_proposta": "PROP-2026-079", "data_proposta": "2026-02-12T16:00:00-03:00", "status": "enviada", "cliente_id": 42, "cliente_nome": "Maria Silva Ltda", "vendedor_id": 5, "total_proposta": "0.00", "validade_proposta_dias": 30, "introducao": "Prezado cliente, segue nossa proposta comercial.", "observacoes":"Proposta enviada ao cliente por e-mail em 12/02/2026", "desconto": "200.00", "frete": "50.00", "prazo_entrega": "10 dias úteis", "condicao_pagamento": "À vista ou 2x sem juros", "marcadores": null, "itens": [], "criado_em": "2026-02-12T16:00:00-03:00", "atualizado_em": "2026-02-12T17:30:00-03:00" } }
Cancelar Proposta (Soft Delete)
DELETE /v1/propostas/{id}/
Escopo necessário: propostas: delete
Cancela a proposta comercial (soft delete). O registro não é removido permanentemente do banco de dados -- o status é alterado para cancelada, preservando o histórico comercial.
Exemplo com cURL
bashcurl -X DELETE https://api-backend.bunto.com.br/v1/propostas/79/ \ -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}"} proposta_id = 79 resposta = requests.delete(f"{BASE_URL}/propostas/{proposta_id}/", headers=headers) dados = resposta.json() if dados["success"]: print("Proposta cancelada com sucesso!") else: print(f"Erro ao cancelar: {dados}")
Exemplo com JavaScript
javascriptconst BASE_URL = "https://api-backend.bunto.com.br/v1"; const TOKEN = "bnt_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4"; const propostaId = 79; const resposta = await fetch(`${BASE_URL}/propostas/${propostaId}/`, { method: "DELETE", headers: { Authorization: `Bearer ${TOKEN}`, }, }); const dados = await resposta.json(); if (dados.success) { console.log("Proposta cancelada com sucesso!"); } else { console.error(`Erro ao cancelar:`, dados); }
Resposta (200 OK)
json{ "success": true, "message": "Proposta cancelada com sucesso" }
Importante: Propostas canceladas não são removidas do banco de dados. Para visualizar propostas canceladas, filtre por status=cancelada. Os itens e vínculos com clientes e vendedores são preservados para fins de auditoria.
Erros Comuns
| Código | Erro | Causa | Solução |
|---|---|---|---|
| 400 | VALIDATION_ERROR | Dados enviados são inválidos (campo obrigatório ausente, formato incorreto, status inválido, 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 propostas ou a ação necessária (read, write, delete) | Verifique os escopos do token no painel |
| 404 | Nao encontrado | Proposta não existe ou pertence a outra empresa | Confirme o ID e se a proposta 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": { "cliente_id": ["Este campo é obrigatorio."], "status": ["\"pendente\" nao é um escolha valido. Escolha entre: rascunho, enviada, aprovada, rejeitada, revisao, expirada, cancelada, convertida."] } } }
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 propostas - 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 - Filtre por
statusecliente_idpara reduzir o volume de dados retornados
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_propostas = [] url = f"{BASE_URL}/propostas/?por_pagina=100&status=enviada" while url: resposta = requests.get(url, headers=headers) dados = resposta.json() if not dados["success"]: break todas_as_propostas.extend(dados["data"]["resultados"]) url = dados["data"]["paginacao"]["proxima"] print(f"Total obtido: {len(todas_as_propostas)} propostas enviadas")