# bitERP Developer Docs Bem-vindo à documentação do **bitERP** para desenvolvedores e parceiros. O bitERP expõe duas formas de integração: API REST para sistemas externos. Autenticação via API Keys ou OAuth2, endpoints CRUD com paginação por cursor e filtros estilo Stripe. Servidor Model Context Protocol para agentes de IA. Conecte LLMs como Claude ou GPT ao bitERP via protocolo padronizado. Primeiros passos [#primeiros-passos] Se você está começando agora, siga o guia rápido: Conceitos básicos, ambientes e como obter suas credenciais. Faça sua primeira chamada à API em menos de 5 minutos. # Modelos de IA suportados O catálogo abaixo vale para o seletor de modelos do chat do produto (`app.biterp.ai`). **Não** se aplica ao [MCP Server](/docs/mcp) (`mcp.biterp.ai`), onde o modelo é escolhido pelo seu próprio cliente de IA, nem à [API de Integrações](/docs/api), que não usa LLM. Status dos modelos [#status-dos-modelos] | Status | Significado | | ---------------------------- | --------------------------------------------------------------------------------------------------- | | **Disponível** | Selecionável no seletor de modelos do chat | | **Uso interno** | Não selecionável; a plataforma usa em etapas auxiliares do atendimento (consome créditos do tenant) | | **Fim de suporte anunciado** | Ainda selecionável, com data de saída publicada na coluna "Fim do suporte" | | **Descontinuado** | Removido do catálogo; quem estava com ele selecionado já foi migrado para o substituto | Modelos disponíveis [#modelos-disponíveis] Modelos que você pode escolher no seletor do chat. | Modelo | Provedor | Status | Custo relativo | Fim do suporte | Substituto | | --------------- | --------- | ---------- | -------------- | -------------- | ---------- | | GPT-5.6 Sol | OpenAI | Disponível | 5 | — | — | | GPT-5.6 Terra | OpenAI | Disponível | 2 | — | — | | Claude Opus 5 | Anthropic | Disponível | 4 | — | — | | Claude Sonnet 5 | Anthropic | Disponível | 3 | — | — | O **custo relativo** vai de 1 (mais econômico) a 5 (mais caro). É a mesma faixa exibida no seletor do chat — não é preço por token. Como escolher o modelo [#como-escolher-o-modelo] No chat do `app.biterp.ai`, use o seletor de modelos para definir em qual LLM o agente principal vai rodar. A escolha vale para aquela conversa e consome créditos do tenant conforme o uso. Em geral: * Modelos com **custo relativo mais alto** tendem a ter maior capacidade de raciocínio em tarefas complexas. * Modelos com **custo relativo mais baixo** priorizam custo e latência, com capacidade adequada para operações do dia a dia. Não há configuração de modelo na API de Integrações nem no MCP — veja [O que esta página não cobre](#o-que-esta-pagina-nao-cobre). Modelos de uso interno [#modelos-de-uso-interno] Estes modelos **não aparecem no seletor**. A plataforma os usa em etapas auxiliares do atendimento (por exemplo, especialistas que apoiam o agente principal). Mesmo assim, o consumo **é debitado em créditos do tenant**. | Modelo | Provedor | Status | Custo relativo | Fim do suporte | Substituto | | ---------------- | --------- | ----------- | -------------- | -------------- | ---------- | | GPT-5.6 Luna | OpenAI | Uso interno | 1 | — | — | | Claude Haiku 4.5 | Anthropic | Uso interno | 2 | — | — | Política de descontinuação [#política-de-descontinuação] Quando um provedor lança uma versão nova (ou retira a anterior), o bitERP atualiza o catálogo do chat. A troca **não quebra a sua seleção**: * A seleção salva migra automaticamente para o **substituto da mesma família** (mesmo provedor e faixa de capacidade). * Você não precisa reescolher o modelo após a troca. * Quando houver data de saída, ela aparece na coluna **Fim do suporte** no formato `AAAA-MM-DD`. Sem data anunciada, a coluna mostra `—`. A data de fim de suporte depende do provedor e do catálogo que consumimos. Esta página descreve **como** avisamos e migramos — não promete um prazo mínimo fixo de aviso. Modelos descontinuados [#modelos-descontinuados] Histórico de modelos que já saíram do seletor. Quem os tinha selecionados foi migrado para o substituto abaixo. | Modelo descontinuado | Substituto atual | | -------------------- | ---------------- | | GPT-5.5 | GPT-5.6 Sol | | GPT-5.4 | GPT-5.6 Terra | | GPT-5.4 mini | GPT-5.6 Luna | | GPT-5.2 | GPT-5.6 Sol | | GPT-5.2 chat | GPT-5.6 Sol | | GPT-5.1 | GPT-5.6 Terra | | GPT-5 mini | GPT-5.6 Luna | | Claude Opus 4.7 | Claude Opus 5 | | Claude Opus 4.6 Fast | Claude Sonnet 5 | | Claude Sonnet 4.6 | Claude Sonnet 5 | | Claude Sonnet 4.5 | Claude Opus 5 | O que esta página não cobre [#o-que-esta-página-não-cobre] * **[MCP Server](/docs/mcp)** (`mcp.biterp.ai`): o servidor só expõe tools via protocolo MCP. Quem escolhe o modelo (e paga o token) é o **cliente de IA do usuário** — Claude Desktop, Cursor, ChatGPT e similares. * **[API de Integrações](/docs/api)**: API REST para sistemas externos. Não envolve LLM. # Autenticação A API de Integrações suporta dois métodos de autenticação: | Método | Uso | Token | | ----------------------------- | ------------------------------ | -------------------- | | **API Keys** | Integrações machine-to-machine | `sk__` | | **OAuth2 Authorization Code** | Automações (Zapier, Make, n8n) | Bearer JWT / `bit_*` | API Keys [#api-keys] Formato do token [#formato-do-token] ``` sk__ ``` O token é exibido **apenas uma vez** no momento da criação. Armazene-o de forma segura imediatamente. Como usar [#como-usar] Envie o token no header `Authorization`: ```bash # Com prefixo Bearer curl -H "Authorization: Bearer sk_abc123_secretXYZ" \ https://api.biterp.ai/products # Ou diretamente curl -H "Authorization: sk_abc123_secretXYZ" \ https://api.biterp.ai/products ``` Características [#características] | Propriedade | Descrição | | -------------- | -------------------------------------------------------- | | **Escopo** | Cada key pertence a um único tenant | | **Permissões** | Granulares por recurso e ação (imutáveis após criação) | | **Expiração** | Opcional — campo `expires_at` configurável | | **Segurança** | Hash SHA-256 do secret (nunca armazenado em texto) | | **Revogação** | Soft delete — a key pode ser revogada a qualquer momento | Criando uma API Key [#criando-uma-api-key] 1. Acesse **Configurações > API Keys** no painel do bitERP 2. Clique em **Criar API Key** 3. Defina nome, descrição e permissões 4. Copie o token exibido Exemplo de payload de criação: ```json { "name": "ERP Integration", "description": "Integração com sistema ERP externo", "permissions": [ { "resource_code": "products", "can_read": true, "can_create": true }, { "resource_code": "customers", "can_read": true }, { "resource_code": "invoices", "can_read": true, "can_create": true } ], "expires_at": "2026-12-31" } ``` *** OAuth2 Authorization Code [#oauth2-authorization-code] Para integrações via plataformas como Zapier, Make e n8n, a API suporta o fluxo **OAuth2 Authorization Code com PKCE (S256)**. Endpoints OAuth [#endpoints-oauth] | Método | Endpoint | Descrição | | ------ | ----------------------------------------- | ------------------------------------- | | `GET` | `/.well-known/oauth-authorization-server` | Metadata do servidor OAuth (RFC 8414) | | `GET` | `/oauth/authorize` | Inicia o fluxo de autorização | | `POST` | `/oauth/token` | Troca code por access token | | `POST` | `/oauth/revoke` | Revoga um token | | `GET` | `/oauth/userinfo` | Retorna dados do usuário autenticado | Fluxo de autorização [#fluxo-de-autorização] ``` 1. Sua app redireciona o usuário para /oauth/authorize com client_id, redirect_uri, scope, state, code_challenge 2. Usuário autentica e autoriza no painel bitERP 3. bitERP redireciona de volta com authorization code 4. Sua app troca o code em POST /oauth/token com client_id + client_secret 5. Usa o access token (bit_*) para chamar a API ``` Formato dos tokens OAuth [#formato-dos-tokens-oauth] | Token | Prefixo | Descrição | | ------------------ | -------- | --------------------------------------- | | Access token | `bit_` | Token de acesso para chamadas à API | | Refresh token | `bitrf_` | Token para renovar o access token | | Authorization code | `bitac_` | Código temporário para troca por tokens | Rate limits OAuth [#rate-limits-oauth] | Endpoint | Limite | | ---------------------- | -------------------------- | | `GET /oauth/authorize` | 20 req/min por IP | | `POST /oauth/token` | 10 req/min por `client_id` | Níveis de acesso OAuth [#níveis-de-acesso-oauth] | Role | Acesso | | -------- | ---------------------------------------------- | | `admin` | Acesso completo a todos os recursos | | `member` | Acesso limitado conforme permissões granulares | Usuários com role `member` só acessam endpoints para os quais possuem permissão configurada. Recursos marcados como `is_admin_only` são restritos a usuários `admin`. # Códigos de Erro Formato de erro [#formato-de-erro] Todas as respostas de erro seguem o mesmo formato JSON: ```json { "status_code": 400, "message": "Descrição do erro", "error": "Bad Request" } ``` Códigos HTTP [#códigos-http] | Código | Significado | Quando ocorre | | ------ | --------------------- | ----------------------------------------------------- | | `200` | OK | Requisição bem-sucedida (GET, PATCH) | | `201` | Created | Recurso criado com sucesso (POST) | | `204` | No Content | Recurso removido com sucesso (DELETE) | | `400` | Bad Request | Dados inválidos, filtro desconhecido, versão inválida | | `401` | Unauthorized | Token ausente, inválido ou expirado | | `403` | Forbidden | Sem permissão para a operação | | `404` | Not Found | Recurso não encontrado | | `409` | Conflict | Conflito (ex: recurso duplicado) | | `422` | Unprocessable Entity | Payload bem formado, mas semanticamente inválido | | `429` | Too Many Requests | Rate limit excedido | | `500` | Internal Server Error | Erro interno do servidor | Troubleshooting [#troubleshooting] 401 Unauthorized [#401-unauthorized] **Causa**: Token de autenticação ausente, inválido ou expirado. **Soluções**: * Verifique se o header `Authorization` está presente * Confirme que o formato é `Bearer sk__` ou `Bearer bit_` * Verifique se a API Key não foi revogada * Verifique se a API Key não expirou (`expires_at`) 403 Forbidden [#403-forbidden] **Causa**: O token é válido, mas não tem permissão para a operação. **Soluções**: * Verifique as permissões da API Key (recurso + ação) * Para OAuth: confirme que o usuário tem a role necessária * Recursos `admin_only` requerem role `admin` 400 Bad Request [#400-bad-request] **Causa**: Dados enviados são inválidos. **Cenários comuns**: * Campo obrigatório ausente no body * Tipo de dado incorreto (ex: string onde esperava number) * Filtro com campo não suportado pelo recurso * Header `api-version` com versão inválida * Formato de UUID inválido 422 Unprocessable Entity [#422-unprocessable-entity] **Causa**: o corpo da requisição está bem formado e passou na validação de tipos, mas a API não conseguiu processar o conteúdo. **Cenários comuns**: * **Geografia não resolvida**: `country_code`, `state`/`state_id` ou `city`/`city_id` que a API não conseguiu casar com o catálogo interno com segurança. Não há degradação silenciosa — se você enviou o campo, ele precisa resolver. Ver [Clientes](/docs/api/guides/customers) e [Dados de referência](/docs/api/guides/reference-data). * **Snapshot financeiro incompatível** em [notas de compra](/docs/api/guides/purchase-invoices), quando o total muda sem o `payables` correspondente. O corpo do erro traz `details.fields` — um mapa de **campo → lista de motivos** — apontando exatamente o que falhou. As mensagens de `details` vêm em inglês: ```json { "status_code": 422, "message": "Could not resolve provided geography fields", "error": "Unprocessable Entity", "details": { "fields": { "country_code": [ "Could not resolve country from '1112' (tried ISO2, ISO3, numeric code and names)" ], "city": ["Could not resolve city from 'CidadeQueNaoExiste' in country 'BR'"] } } } ``` **Solução**: consulte o catálogo em [Dados de referência](/docs/api/guides/reference-data) e envie os IDs (`state_id`, `city_id`) em vez do texto livre quando precisar de determinismo. 429 Too Many Requests [#429-too-many-requests] **Causa**: Rate limit excedido. **Endpoints com rate limit**: * `GET /oauth/authorize`: 20 req/min por IP * `POST /oauth/token`: 10 req/min por `client_id` **Solução**: Implemente backoff exponencial e respeite os headers de retry. 404 Not Found [#404-not-found] **Causa**: Recurso não existe ou pertence a outro tenant. **Nota**: Por segurança, a API retorna 404 (não 403) quando o recurso existe mas pertence a outro tenant. Health check [#health-check] O endpoint `GET /health` responde `200 OK` quando a API está no ar — use-o para monitoramento. Ele não exige autenticação nem contexto de tenant. # API de Integrações A **API de Integrações** do bitERP permite que sistemas externos realizem operações CRUD em recursos do ERP via endpoints REST. Base URL [#base-url] ``` https://api.biterp.ai ``` Características [#características] * **Autenticação dupla**: API Keys (machine-to-machine) ou OAuth2 Authorization Code (Zapier, Make, n8n) * **Permissões granulares**: controle por recurso e ação (create, read, update, delete) * **Paginação por cursor**: navegação eficiente em listas grandes * **Filtros estilo Stripe**: query params planos com bracket notation para operadores * **Versionamento por header**: header `api-version` para compatibilidade futura * **OpenAPI spec**: especificação gerada automaticamente para referência de endpoints * **Webhooks de saída**: notificações assinadas enviadas ao seu servidor quando um recurso muda, sem *polling* Recursos disponíveis [#recursos-disponíveis] Para cada recurso há um **guia narrativo** (o que é, ciclo de vida, fluxos) e a **referência** endpoint a endpoint — veja, por exemplo, o [guia de Produtos](/docs/api/guides/products). Os recursos estão organizados por domínio: Cadastros [#cadastros] | Recurso | Referência OpenAPI | Guia | | ------------ | --------------------------------------------------- | ------------------------------------------ | | Produtos | [Operações](/docs/api/reference/products/find-all) | [Produtos](/docs/api/guides/products) | | Clientes | [Operações](/docs/api/reference/customers/find-all) | [Clientes](/docs/api/guides/customers) | | Fornecedores | [Operações](/docs/api/reference/suppliers/find-all) | [Fornecedores](/docs/api/guides/suppliers) | Financeiro [#financeiro] | Recurso | Referência OpenAPI | Guia | | ---------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- | | Contas financeiras | [Operações](/docs/api/reference/financial-accounts/find-all) | [Contas financeiras](/docs/api/guides/financial-accounts) | | Categorias financeiras | [Operações](/docs/api/reference/financial-categories/find-all) | [Categorias financeiras](/docs/api/guides/financial-categories) | | Transações e extrato | [Operações](/docs/api/reference/financial-transactions/find-all) | [Transações e extrato](/docs/api/guides/financial-transactions) | | Contas a receber | [Operações](/docs/api/reference/receivables/find-all) | [Contas a receber](/docs/api/guides/receivables) | | Contas a pagar | [Operações](/docs/api/reference/payables/find-all) | [Contas a pagar](/docs/api/guides/payables) | Vendas e faturamento [#vendas-e-faturamento] | Recurso | Referência OpenAPI | Guia | | ---------------- | ------------------------------------------------------ | ------------------------------------------------- | | Orçamentos | [Operações](/docs/api/reference/quotes/find-all) | [Orçamentos](/docs/api/guides/quotes) | | Pedidos de venda | [Operações](/docs/api/reference/sales-orders/find-all) | [Pedidos de venda](/docs/api/guides/sales-orders) | | Notas / faturas | [Operações](/docs/api/reference/invoices/find-all) | [Notas / faturas](/docs/api/guides/invoices) | O guia [Fluxo de vendas](/docs/api/guides/sales-flow) costura orçamento → pedido → nota → contas a receber. Compras [#compras] | Recurso | Referência OpenAPI | Guia | | --------------- | ----------------------------------------------------------- | ----------------------------------------------------- | | Notas de compra | [Operações](/docs/api/reference/purchase-invoices/find-all) | [Notas de compra](/docs/api/guides/purchase-invoices) | Dados de referência (somente leitura) [#dados-de-referência-somente-leitura] Bancos (`/banks`), formas de pagamento (`/payment-methods`), unidades de medida (`/units-of-measurement`), países (`/countries`), estados (`/states`) e cidades (`/cities`) são catálogos consultados nos cadastros. Ver o guia [Dados de referência](/docs/api/guides/reference-data). Integração [#integração] | Recurso | Referência OpenAPI | Guia | | ----------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------- | | Mapeamento de entidades | [Operações](/docs/api/reference/integration-entity-mappings/find-all) | [Mapeamento de entidades](/docs/api/guides/integration-entity-mappings) | Utilitários [#utilitários] Consulte a [referência OpenAPI](/docs/api/reference) para [health](/docs/api/reference/health/get-health), [tenant](/docs/api/reference/tenant/get-current-tenant) e [oauth/token](/docs/api/reference/oauth/token). O fluxo de autenticação está em [Autenticação](/docs/api/authentication). A maioria dos recursos de negócio expõe operações padrão de listagem, busca por ID, criação, atualização e remoção — veja a referência OpenAPI de cada recurso e os guias narrativos para endpoints específicos. > A referência completa de endpoints é gerada automaticamente a partir da especificação OpenAPI. Veja a [Referência da API](/docs/api/reference). `DELETE` é de mão única [#delete-é-de-mão-única] `DELETE /:id` faz **soft delete**: o registro sai das listagens e das buscas, mas continua no banco para preservar histórico e referências. **Não existe endpoint de restauração nesta API** — em nenhum recurso. Não é uma lacuna a ser preenchida: desfazer uma exclusão é uma operação de **usuário**, feita no painel do bitERP por alguém autenticado, e não por um sistema externo. Do ponto de vista da sua integração, trate todo `DELETE` como definitivo e confirme antes de chamar. Webhooks [#webhooks] Além de consultar a API, você pode ser **notificado** quando algo muda no ERP. Um administrador registra uma URL HTTPS no painel do bitERP, escolhe os eventos e passa a receber um `POST` assinado a cada criação, atualização, exclusão ou restauração de recurso. O payload identifica o recurso que mudou; os dados completos você busca com o `GET /:id` correspondente. Ver [Webhooks](/docs/api/webhooks). Formato de resposta [#formato-de-resposta] Todas as respostas usam JSON. Listagens retornam dados paginados, com os metadados de navegação em `pagination`: ```json { "data": [...], "pagination": { "limit": 20, "has_next_page": true, "has_previous_page": false, "next_cursor": "eyJmIjoiY3JlYXRlZF9hdCIsInYiOi...", "previous_cursor": null }, "meta": { "sort": { "field": "created_at", "order": "asc" } } } ``` Recursos individuais retornam o objeto diretamente: ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Notebook Dell", "sale_price": "4500.000000", "is_active": true, "created_at": "2026-01-15T10:30:00Z", "updated_at": "2026-01-15T10:30:00Z" } ``` Valores decimais são strings [#valores-decimais-são-strings] Todo campo **decimal ou monetário** trafega como **string**, no formato `numeric(18,6)` — sempre com **6 casas decimais** —, tanto na **requisição** quanto na **resposta**. O motivo é precisão: o `number` do JSON é um float de ponto flutuante (IEEE 754) e arredonda valores financeiros de forma silenciosa. A string preserva o valor exato. ```json { "amount": "1250.000000", "quantity": "2.000000", "unit_price": "450.000000" } ``` Isso vale para campos como `amount`, `total_amount`, `allocated_amount`, `discount_amount`, `surcharge_amount`, `unit_price`, `sale_price`, `cost_price`, `quantity`, `initial_balance`, `subtotal`, `total`, `base_amount`, entre outros. Campos que aceitam **sinal** (como o `amount` de uma transação financeira e o `initial_balance`) usam o `-` na própria string: `"-250.500000"`. > **Percentuais também são strings.** Alíquotas e taxas (`rate`, `discount_rate`) usam a escala de 0 a 100 e trafegam como decimal `numeric(9,6)` em string — `"18.000000"` é 18%. Isso vale para **todos** os recursos, inclusive [notas](/docs/api/guides/invoices). Já contadores e índices (`installment_count`, `position`, `display_order`, `limit`) são **inteiros** e seguem como `number` — esses, sim, nunca envie como string. Ao consumir a API, **não** converta os decimais para `float` na sua linguagem: use um tipo decimal (`BigDecimal`, `decimal.Decimal`, `Decimal.js`) ou mantenha a string. Próximos passos [#próximos-passos] Configure API Keys ou OAuth2. Entenda o modelo de permissões granulares. Navegue e filtre resultados. Um exemplo de guia narrativo por recurso. Receba notificações quando recursos mudarem, em vez de consultar a API em laço. # Paginação e Filtros Paginação por cursor [#paginação-por-cursor] Endpoints de listagem usam **cursor-based pagination**. Esse modelo é mais eficiente que offset pagination para datasets grandes. Como funciona [#como-funciona] 1. Faça a primeira chamada sem o parâmetro `cursor` 2. Se `pagination.has_next_page` for `true`, use o valor de `pagination.next_cursor` na próxima chamada 3. Repita até `pagination.has_next_page` ser `false` Parâmetros [#parâmetros] | Parâmetro | Tipo | Descrição | | --------- | ------ | ------------------------------------------------------------ | | `cursor` | string | Cursor opaco da página anterior. Omitir na primeira chamada. | | `limit` | number | Itens por página (padrão: 20, máximo: 100) | Exemplo [#exemplo] ```bash # Primeira página curl -H "Authorization: Bearer sk_xxx_yyy" \ "https://api.biterp.ai/products?limit=10" # Próxima página curl -H "Authorization: Bearer sk_xxx_yyy" \ "https://api.biterp.ai/products?limit=10&cursor=eyJmIjoiY3Jl..." ``` Formato da resposta [#formato-da-resposta] Os dados vêm em `data`; os metadados de navegação, em `pagination`; e o eco de filtros/ordenação aplicados, em `meta`: ```json { "data": [...], "pagination": { "limit": 20, "has_next_page": true, "has_previous_page": false, "next_cursor": "eyJmIjoiY3JlYXRlZF9hdCIsInYiOi...", "previous_cursor": null }, "meta": { "sort": { "field": "created_at", "order": "asc" } } } ``` *** Filtros (Flat Query Params) [#filtros-flat-query-params] Os endpoints de listagem aceitam **filtros via query params planos**, no estilo Stripe (bracket notation). Sintaxe [#sintaxe] ```bash # Igualdade (sem operador) GET /products?name=notebook&is_active=true # Operadores via bracket notation GET /products?sale_price[gte]=1000&sale_price[lte]=5000 # Busca parcial (contains) GET /customers?legal_name[contains]=acme # Combinando filtros, ordenação e paginação GET /quotes?created_at[gte]=2026-01-01&sort=created_at:desc&limit=50 ``` Operadores disponíveis [#operadores-disponíveis] | Sintaxe | Operador | Tipos suportados | | --------------------------- | ---------------------- | --------------------------------- | | `field=value` | Igual (`eq`) | string, number, boolean, date | | `field[neq]=value` | Diferente | string, number, boolean, date | | `field[gt]=value` | Maior que | number, date | | `field[gte]=value` | Maior ou igual | number, date | | `field[lt]=value` | Menor que | number, date | | `field[lte]=value` | Menor ou igual | number, date | | `field[contains]=value` | Contém (busca parcial) | string | | `field[not_contains]=value` | Não contém | string | | `field[is_null]=true` | É nulo | todos os tipos (campo `nullable`) | | `field[is_not_null]=true` | Não é nulo | todos os tipos (campo `nullable`) | No payload JSON de `filters`, use `{ "f": "reference_code", "op": "is_null" }` **sem** `v` (ou `"v": null`) — o operador não carrega valor: ```json { "logic": "and", "conditions": [{ "f": "reference_code", "op": "is_null" }] } ``` Equivalente flat: `GET /products?reference_code[is_null]=true` Ordenação [#ordenação] Use o parâmetro `sort` com o formato `campo:direcao`: ```bash # Ordenar por preço decrescente GET /products?sort=sale_price:desc # Ordenar por data de criação crescente GET /products?sort=created_at:asc ``` Ordenação padrão por recurso [#ordenação-padrão-por-recurso] Se você **não** enviar `sort`, cada recurso aplica a ordenação que faz sentido para o seu domínio — não existe um padrão único para toda a API: | Recurso | Ordenação padrão | | ------------------------------------------------------------------------------- | -------------------- | | `/products`, `/customers`, `/suppliers` | `created_at:asc` | | `/quotes`, `/sales-orders` | `record_number:asc` | | `/invoices` | `invoice_date:desc` | | `/purchase-invoices` | `purchase_date:desc` | | `/receivables`, `/payables` | `due_date:asc` | | `/financial-transactions` | `posted_at:desc` | | `/financial-accounts`, `/banks` | `display_order:asc` | | `/financial-categories` | `position:asc` | | `/countries`, `/states`, `/cities`, `/payment-methods`, `/units-of-measurement` | `id:desc` | O bloco `meta.sort` da resposta **sempre ecoa a ordenação efetivamente aplicada**, então você pode confirmar o comportamento sem adivinhar. > A ordenação padrão é **estável** e faz parte do contrato: paginar por cursor sem `sort` percorre a coleção inteira sem repetir nem pular registros. Se a sua integração depende de uma ordem específica, envie `sort` explicitamente em vez de confiar no padrão. Regras [#regras] * Todos os filtros são combinados com **AND** * `is_null` / `is_not_null` só funcionam em campos marcados como nullable no contrato do recurso (ex.: `reference_code` em cadastros que aceitam código opcional) * Campos desconhecidos retornam `400 Bad Request` * Valores inválidos (ex: texto em campo numérico) retornam `400 Bad Request` * Cada recurso tem seus próprios campos filtráveis — consulte a referência de endpoints # Permissões O bitERP usa um modelo de permissões **granular por recurso e ação**. Cada API Key ou usuário OAuth tem permissões específicas que controlam o que pode ser acessado. Modelo de permissões [#modelo-de-permissões] Cada permissão é composta por: | Campo | Descrição | Exemplo | | --------------- | ------------------------------ | ----------------------- | | `resource_code` | Código do recurso | `products`, `customers` | | `can_create` | Permissão para criar | `true` / `false` | | `can_read` | Permissão para listar e buscar | `true` / `false` | | `can_update` | Permissão para atualizar | `true` / `false` | | `can_delete` | Permissão para remover | `true` / `false` | Como funciona [#como-funciona] Com API Keys [#com-api-keys] As permissões são definidas na criação da API Key e são **imutáveis** — não podem ser alteradas depois. Para mudar permissões, crie uma nova key. Exemplo: uma API Key com permissão `products:read` e `products:create` pode listar e criar produtos, mas não pode atualizar ou deletar. Com OAuth2 [#com-oauth2] O comportamento depende da role do usuário: | Role | Comportamento | | -------- | ------------------------------------------------------------------ | | `admin` | Acesso completo (bypass de permissões) | | `member` | Permissões granulares por recurso, idênticas ao modelo de API Keys | Respostas de erro [#respostas-de-erro] Quando uma operação é negada por falta de permissão, a API retorna: ```json { "status_code": 403, "message": "Forbidden", "error": "You do not have permission to perform this action" } ``` Recursos disponíveis [#recursos-disponíveis] Estes são os `resource_code` aceitos na criação de uma API Key, com as ações que cada um expõe na API de Integrações. Conceder uma ação que o recurso não expõe (por exemplo `can_create` em um catálogo de referência) não habilita nada. > **Os códigos usam hífen, não underscore** — é `financial-accounts`, nunca `financial_accounts`. Um código inexistente não gera erro na criação da key: ele simplesmente não concede permissão nenhuma, e as chamadas passam a responder `403`. Cadastros [#cadastros] | Código | Recurso | Ações | | ----------- | ------------ | ------------------------------------ | | `products` | Produtos | `create`, `read`, `update`, `delete` | | `customers` | Clientes | `create`, `read`, `update`, `delete` | | `suppliers` | Fornecedores | `create`, `read`, `update`, `delete` | Financeiro [#financeiro] | Código | Recurso | Ações | | ------------------------ | ---------------------- | ------------------------------------ | | `financial-accounts` | Contas financeiras | `create`, `read`, `update`, `delete` | | `financial-categories` | Categorias financeiras | `create`, `read`, `update`, `delete` | | `financial-transactions` | Transações financeiras | `read`, `delete` | | `receivables` | Contas a receber | `create`, `read`, `update`, `delete` | | `payables` | Contas a pagar | `create`, `read`, `update`, `delete` | Vendas, faturamento e compras [#vendas-faturamento-e-compras] | Código | Recurso | Ações | | ------------------- | ---------------- | ------------------------------------ | | `quotes` | Orçamentos | `create`, `read`, `update`, `delete` | | `sales-orders` | Pedidos de venda | `create`, `read`, `update`, `delete` | | `invoices` | Notas / faturas | `create`, `read`, `update`, `delete` | | `purchase-invoices` | Notas de compra | `create`, `read`, `update`, `delete` | Dados de referência (somente leitura) [#dados-de-referência-somente-leitura] | Código | Recurso | Ações | | ---------------------- | ------------------- | ------ | | `banks` | Bancos | `read` | | `payment-methods` | Formas de pagamento | `read` | | `units-of-measurement` | Unidades de medida | `read` | | `countries` | Países | `read` | | `states` | Estados | `read` | | `cities` | Cidades | `read` | Integração [#integração] | Código | Recurso | Ações | | ---------------------- | ----------------------- | ------------------------------------ | | `integration-mappings` | Mapeamento de entidades | `create`, `read`, `update`, `delete` | > ⚠️ O código de permissão é **`integration-mappings`**, mas o caminho da URL é `/integration-entity-mappings`. É a única divergência entre código e rota na API — ver o [guia do recurso](/docs/api/guides/integration-entity-mappings). Sem permissão específica [#sem-permissão-específica] `GET /tenant` e `GET /health` não exigem permissão de recurso: qualquer credencial válida acessa o próprio tenant, e o health check é público. Boas práticas [#boas-práticas] 1. **Princípio do menor privilégio**: conceda apenas as permissões necessárias para a integração 2. **Keys separadas por integração**: crie uma API Key diferente para cada sistema que integra com o bitERP 3. **Expiração**: configure `expires_at` sempre que possível 4. **Revogação**: revogue keys que não são mais necessárias # Versionamento da API A API de Integrações usa versionamento via **header customizado**, garantindo que integrações existentes continuem funcionando quando novas versões forem lançadas. Como usar [#como-usar] Envie o header `api-version` em suas requisições: ```bash curl -H "Authorization: Bearer " \ -H "api-version: v1" \ https://api.biterp.ai/products ``` Comportamento [#comportamento] | Cenário | Resultado | | --------------------------------------- | ------------------------------------------------ | | Header `api-version` presente e válido | Usa a versão solicitada | | Header `api-version` ausente | Usa a versão atual (`v1`) | | Header `api-version` com valor inválido | Retorna `400 Bad Request` com as versões aceitas | Versões disponíveis [#versões-disponíveis] | Versão | Status | | ------ | ------------------------- | | `v1` | **Atual** — versão padrão | Boas práticas [#boas-práticas] 1. **Sempre envie o header**: mesmo que a versão padrão funcione hoje, enviar explicitamente protege sua integração contra mudanças futuras 2. **Teste antes de migrar**: quando uma nova versão for lançada, teste sua integração antes de atualizar o header 3. **Monitore deprecações**: versões antigas serão depreciadas com antecedência Exemplo de erro [#exemplo-de-erro] Quando uma versão inválida é enviada, a resposta é `400 Bad Request` com as versões aceitas: ```json { "status_code": 400, "message": "Invalid api-version. Accepted: v1. Current: v1", "error": "Bad Request" } ``` Changelog (breaking changes em `v1`) [#changelog-breaking-changes-em-v1] Mudanças de contrato na versão atual (`v1`) são documentadas aqui. Integrações que dependem de campos removidos devem buscar o dado no recurso dedicado (ex.: `GET /customers/{id}`). 2026-08 — `customer` embutido em orçamentos (`GET /quotes`, `GET /quotes/{id}`) [#2026-08--customer-embutido-em-orçamentos-get-quotes-get-quotesid] O objeto `customer` aninhado nas respostas de listagem e detalhe de orçamentos foi **reduzido** para alinhar com [pedidos de venda](/docs/api/guides/sales-orders) e [notas](/docs/api/guides/invoices): apenas identificação mínima do cliente, sem dados bancários, de contato ou de endereço. **Antes** (\~17 campos): ```json { "customer": { "id": "8f2c1e4a-9b3d-4c7e-a1f2-6d5b8e0c3a71", "type": "company", "tax_id": "12345678000195", "tax_id_type": "cnpj", "legal_name": "ACME Comércio Ltda", "trade_name": "ACME", "website": "https://acme.example", "address_line1": "Rua das Flores, 100", "address_line2": "Sala 12", "city": "São Paulo", "state": "SP", "postal_code": "01310100", "country_code": "BR", "is_blocked": false, "metadata": { "erp_code": "C-001" }, "created_at": "2026-01-15T10:00:00.000Z", "updated_at": "2026-06-01T14:30:00.000Z" } } ``` **Depois** (4 campos): ```json { "customer": { "id": "8f2c1e4a-9b3d-4c7e-a1f2-6d5b8e0c3a71", "legal_name": "ACME Comércio Ltda", "trade_name": "ACME", "tax_id": "12345678000195" } } ``` **Campos removidos** do objeto embutido: `type`, `tax_id_type`, `website`, `address_line1`, `address_line2`, `city`, `state`, `postal_code`, `country_code`, `is_blocked`, `metadata`, `created_at`, `updated_at`. O `customer_id` no corpo do orçamento **não muda**. Para o cadastro completo, use [`GET /customers/{id}`](/docs/api/reference/customers/find-by-id). Detalhes no guia de [orçamentos](/docs/api/guides/quotes#cliente-embutido-nas-respostas). # Introdução O que é o bitERP? [#o-que-é-o-biterp] O **bitERP** é um sistema ERP (Enterprise Resource Planning) AI-first, projetado como plataforma SaaS multi-tenant. Ele permite que empresas gerenciem produtos, clientes, fornecedores, pedidos, financeiro e mais — tudo com integração nativa a agentes de IA. Formas de integração [#formas-de-integração] | Canal | Protocolo | Público-alvo | Autenticação | | ---------------------- | --------------------------------- | ------------------------------------------------- | ------------------ | | **API de Integrações** | REST (JSON) | Sistemas externos, automações (Zapier, Make, n8n) | API Keys ou OAuth2 | | **MCP Server** | Model Context Protocol (JSON-RPC) | Agentes de IA (Claude, GPT, etc.) | OAuth2 via Clerk | Ambientes [#ambientes] | Ambiente | Base URL API | Base URL MCP | | ------------ | ----------------------- | ----------------------- | | **Produção** | `https://api.biterp.ai` | `https://mcp.biterp.ai` | Obtendo credenciais [#obtendo-credenciais] API Keys (para API de Integrações) [#api-keys-para-api-de-integrações] 1. Acesse o painel do bitERP em [app.biterp.ai](https://app.biterp.ai) 2. Navegue até **Configurações > API Keys** 3. Clique em **Criar API Key** 4. Configure o nome, descrição e permissões por recurso 5. **Copie o token imediatamente** — ele só é exibido uma vez O token tem o formato `sk__`. OAuth2 (para automações e MCP) [#oauth2-para-automações-e-mcp] Para integrações via OAuth2 (Zapier, Make, n8n) ou para conectar agentes de IA via MCP, a autenticação é feita via fluxo OAuth2 Authorization Code. Consulte a seção de autenticação de cada canal para detalhes. Conceitos básicos [#conceitos-básicos] Multi-tenancy [#multi-tenancy] Cada empresa no bitERP é um **tenant** isolado. Todas as operações são executadas no contexto de um tenant específico: * **API Keys**: cada key pertence a um único tenant — o contexto é automático * **OAuth2/MCP**: o agente deve chamar `list_tenants` primeiro e passar o `tenant_id` nas operações Na API de Integrações, o endpoint `GET /tenant` retorna os dados do tenant atual (aquele a que a sua credencial pertence). Você nunca envia `tenant_id` nos payloads — ele é sempre inferido da credencial. Permissões [#permissões] O bitERP usa um modelo de permissões granular por recurso e ação (create, read, update, delete). Cada API Key ou usuário OAuth tem permissões específicas configuradas pelo administrador. Isolamento de dados [#isolamento-de-dados] Dados de diferentes tenants são completamente isolados via Row-Level Security (RLS) no banco de dados. Não é possível acessar dados de outro tenant, mesmo com credenciais válidas. # Quickstart Pré-requisitos [#pré-requisitos] * Uma conta no bitERP com acesso de administrador * Uma API Key criada (veja [Introdução](/docs/getting-started)) Sua primeira chamada [#sua-primeira-chamada] Substitua `sk_xxx_yyy` pela sua API Key real. curl [#curl] ```bash curl -X GET https://api.biterp.ai/products \ -H "Authorization: Bearer sk_xxx_yyy" \ -H "Content-Type: application/json" ``` JavaScript / TypeScript [#javascript--typescript] ```typescript const response = await fetch("https://api.biterp.ai/products", { headers: { Authorization: "Bearer sk_xxx_yyy", "Content-Type": "application/json", }, }) const data = await response.json() console.log(data) ``` Python [#python] ```python import requests response = requests.get( 'https://api.biterp.ai/products', headers={ 'Authorization': 'Bearer sk_xxx_yyy', 'Content-Type': 'application/json', }, ) data = response.json() print(data) ``` Resposta esperada [#resposta-esperada] ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "Notebook Dell Inspiron", "sale_price": "4500.000000", "is_active": true, "created_at": "2026-01-15T10:30:00Z" } ], "has_next_page": false, "next_cursor": null } ``` > Campos decimais e monetários (como `sale_price`) trafegam como **string** no formato `numeric(18,6)`, tanto na resposta quanto na requisição. Ver [formato de resposta](/docs/api). Próximos passos [#próximos-passos] Entenda os métodos de autenticação em detalhe. Aprenda a paginar resultados e filtrar dados. Conecte um agente de IA ao bitERP. # Autenticação MCP O MCP Server usa **OAuth 2.0 via Clerk** para autenticação. Cada request é autenticado independentemente via JWT. Fluxo de autenticação [#fluxo-de-autenticação] ``` 1. Cliente MCP descobre OAuth metadata GET /.well-known/oauth-authorization-server 2. Redireciona usuário para autenticação Clerk 3. Recebe token OAuth (JWT) 4. POST / sem Mcp-Session-Id (cria sessão) → Servidor valida JWT → Cria sessão vinculada ao usuário → Retorna Mcp-Session-Id 5. Requests seguintes incluem Mcp-Session-Id → JWT validado em cada request → Sessão verificada contra userId ``` OAuth Discovery [#oauth-discovery] O servidor expõe endpoints de discovery conforme os RFCs 9728 e 8414: | Endpoint | RFC | Descrição | | --------------------------------------------- | -------- | ----------------------------------- | | `GET /.well-known/oauth-protected-resource` | RFC 9728 | Metadata do recurso protegido | | `GET /.well-known/oauth-authorization-server` | RFC 8414 | Metadata do servidor de autorização | Clientes MCP compatíveis (Claude Desktop, Cursor, etc.) usam esses endpoints automaticamente para descobrir como autenticar. Contexto do usuário [#contexto-do-usuário] Após autenticação, o servidor resolve o contexto completo: | Campo | Descrição | | -------------- | ----------------------------------------------- | | `userId` | ID do usuário no bitERP | | `email` | Email do usuário | | `tenantId` | ID do tenant (resolvido via `list_tenants`) | | `membershipId` | ID da associação usuário-tenant | | `role` | Role do usuário no tenant (`admin` ou `member`) | O token OAuth contém apenas o `userId`. O contexto de tenant é resolvido quando o agente chama `list_tenants` e passa o `tenant_id` nas operações. Permissões [#permissões] O sistema de permissões é idêntico ao da API de Integrações: * Usuários `admin` têm acesso completo * Usuários `member` têm acesso controlado por permissões granulares por recurso/ação * Permissões são verificadas antes de cada operação Quando o agente tenta uma operação sem permissão, recebe um erro que permite informar o usuário sobre a restrição. Segurança [#segurança] * Cada request é autenticado via JWT (não depende apenas da sessão) * Sessões são vinculadas ao `userId` — tentativas de uso por outro usuário são bloqueadas * Dados são isolados por tenant via Row-Level Security (RLS) * HTTPS obrigatório em produção # Tratamento de Erros O MCP Server distingue dois tipos de erro, conforme a especificação do protocolo: Protocol Errors [#protocol-errors] Erros de estrutura JSON-RPC, tratados automaticamente pelo SDK: ```json { "jsonrpc": "2.0", "error": { "code": -32602, "message": "Unknown tool: invalid_tool_name" }, "id": 1 } ``` Esses erros indicam problemas no formato da mensagem, não na lógica de negócio. Tool Execution Errors [#tool-execution-errors] Erros de negócio retornados no resultado da tool com `isError: true`. O agente de IA recebe feedback acionável e pode se auto-corrigir: ```json { "content": [ { "type": "text", "text": "{\"error\":true,\"type\":\"PermissionError\",\"message\":\"You do not have permission to perform this action\"}" } ], "isError": true } ``` Tipos de erro de negócio [#tipos-de-erro-de-negócio] | Tipo | Descrição | Ação esperada do agente | | ----------------- | ----------------------------- | ----------------------------------------- | | `PermissionError` | Sem permissão para a operação | Informar o usuário sobre a restrição | | `NotFoundError` | Recurso não encontrado | Tentar com outro ID ou informar o usuário | | `ConflictError` | Conflito (ex: duplicata) | Verificar dados e ajustar | | `ValidationError` | Dados inválidos | Corrigir parâmetros e tentar novamente | | `Error` | Erro interno genérico | Reportar erro ao usuário | Exemplos de cenários [#exemplos-de-cenários] Permissão negada [#permissão-negada] O usuário `member` tenta deletar um produto sem permissão `products:delete`: ``` Agente: Vou excluir o produto "Notebook Dell" → delete_product({ tenant_id: "...", id: "..." }) ← PermissionError: You do not have permission to perform this action Agente: Você não tem permissão para excluir produtos. Peça ao administrador para conceder essa permissão. ``` Recurso não encontrado [#recurso-não-encontrado] O agente tenta buscar um produto com ID inválido: ``` Agente: Buscando o produto... → get_product({ tenant_id: "...", id: "id-invalido" }) ← NotFoundError: Product not found Agente: Não encontrei um produto com esse ID. Pode verificar o ID correto? ``` Tenant inválido [#tenant-inválido] O agente passa um `tenant_id` que o usuário não tem acesso: ``` → list_products({ tenant_id: "tenant-sem-acesso" }) ← Error: Membership not found or inactive Agente: Não foi possível acessar essa empresa. Vamos verificar suas empresas disponíveis. → list_tenants() ``` Boas práticas para integradores [#boas-práticas-para-integradores] 1. **Sempre verifique `isError`**: Se `true`, o conteúdo contém uma mensagem de erro, não dados 2. **Use o campo `type`**: Permite tratar cada tipo de erro de forma específica 3. **Erros são acionáveis**: O agente deve usar a mensagem para se auto-corrigir quando possível # MCP Server O **bitERP MCP Server** permite que agentes de IA (como Claude, GPT e outros LLMs) executem operações de negócio no bitERP via o protocolo [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). O que é MCP? [#o-que-é-mcp] O Model Context Protocol é um protocolo padronizado que permite a comunicação entre agentes de IA e serviços externos. Ele define como o agente descobre ferramentas (tools) disponíveis, as invoca e recebe resultados — tudo via JSON-RPC sobre HTTP. Base URL [#base-url] ``` https://mcp.biterp.ai ``` Como conectar [#como-conectar] 1\. Configurar a conexão [#1-configurar-a-conexão] No seu cliente MCP (Claude Desktop, Cursor, etc.), adicione o servidor: ```json { "mcpServers": { "biterp": { "url": "https://mcp.biterp.ai" } } } ``` 2\. Autenticar via OAuth [#2-autenticar-via-oauth] O servidor usa OAuth 2.0 via Clerk. O cliente MCP gerencia o fluxo automaticamente: 1. O cliente descobre os endpoints OAuth via `/.well-known/oauth-authorization-server` 2. Redireciona você para autenticar no bitERP 3. Recebe o token e estabelece a sessão 3\. Selecionar o tenant [#3-selecionar-o-tenant] Após autenticar, o agente deve chamar `list_tenants` para obter os tenants disponíveis. Se houver mais de um, o agente perguntará qual usar. 4\. Usar as ferramentas [#4-usar-as-ferramentas] O agente de IA pode então usar todas as ferramentas disponíveis para executar operações de negócio no contexto do tenant selecionado. Endpoints [#endpoints] | Endpoint | Auth | Descrição | | --------------------------------------------- | ---- | --------------------------------------------- | | `HEAD /` | Não | Health check (verificação de disponibilidade) | | `POST /` | Sim | Mensagens JSON-RPC (cria sessão se nova) | | `GET /` | Sim | Conexão SSE (Server-Sent Events) | | `DELETE /` | Sim | Encerramento de sessão | | `GET /.well-known/oauth-protected-resource` | Não | Metadata do recurso (RFC 9728) | | `GET /.well-known/oauth-authorization-server` | Não | Metadata OAuth (RFC 8414) | | `GET /health` | Não | Health check alternativo | Próximos passos [#próximos-passos] Detalhes do fluxo OAuth 2.0. Lista completa de ferramentas que o agente pode usar. Como funciona o gerenciamento de sessões. # Sessões O MCP Server do bitERP usa **sessões stateful** — cada conexão de agente de IA recebe sua própria instância de servidor MCP com transport dedicado. Ciclo de vida [#ciclo-de-vida] 1\. Criação [#1-criação] Quando o cliente envia `POST /` sem o header `Mcp-Session-Id`, uma nova sessão é criada: * O servidor valida o JWT * Cria uma instância `McpServer` dedicada * Registra todas as tools disponíveis * Conecta o transport HTTP * Retorna o `Mcp-Session-Id` no header da resposta 2\. Uso [#2-uso] Requests subsequentes incluem o header `Mcp-Session-Id`: ``` POST / HTTP/1.1 Authorization: Bearer Mcp-Session-Id: Content-Type: application/json ``` A cada request, o servidor: * Valida o JWT (autenticação independente por request) * Verifica que a sessão pertence ao usuário (`userId`) * Atualiza o timestamp de última atividade * Delega a mensagem ao transport da sessão 3\. Expiração [#3-expiração] Sessões inativas são removidas automaticamente: | Configuração | Valor | | ------------ | ------------------------- | | **TTL** | 30 minutos de inatividade | | **Cleanup** | A cada 5 minutos | 4\. Encerramento [#4-encerramento] O cliente pode encerrar a sessão explicitamente: ``` DELETE / HTTP/1.1 Authorization: Bearer Mcp-Session-Id: ``` Server-Sent Events (SSE) [#server-sent-events-sse] O servidor suporta conexões SSE para streaming de mensagens server-to-client: ``` GET / HTTP/1.1 Authorization: Bearer Mcp-Session-Id: Accept: text/event-stream ``` Proteção anti-hijack [#proteção-anti-hijack] Cada sessão é vinculada ao `userId` que a criou. Se um request com `Mcp-Session-Id` vem de outro usuário: * O servidor retorna **404 Not Found** * Não revela se a sessão existe (previne enumeração) * Registra tentativa de hijack nos logs Graceful shutdown [#graceful-shutdown] Quando o servidor é encerrado (deploy, restart), todas as sessões ativas são fechadas de forma controlada — os transports e servers são encerrados antes do processo finalizar. # Tools Disponíveis O MCP Server expõe ferramentas (tools) que o agente de IA pode invocar para executar operações de negócio. O agente decide quando usar cada tool com base no contexto da conversa. Fluxo padrão [#fluxo-padrão] Toda interação com o MCP Server segue este padrão: 1. **`get_user_data`** — O agente obtém o perfil do usuário 2. **`list_tenants`** — O agente descobre os tenants disponíveis 3. **Operações de negócio** — O agente usa as tools de CRUD passando o `tenant_id` *** Dados do usuário [#dados-do-usuário] get_user_data [#get_user_data] Retorna o perfil do usuário autenticado. | Campo | Tipo | Descrição | | ----- | ---- | --------------------------- | | — | — | Nenhum parâmetro necessário | **Resposta:** ```json { "id": "uuid", "email": "usuario@empresa.com", "name": "Nome do Usuário", "language": "pt-BR", "timezone": "America/Sao_Paulo" } ``` *** Tenants [#tenants] list_tenants [#list_tenants] Lista os tenants (empresas) que o usuário pode acessar. **Deve ser chamada antes de qualquer operação de negócio.** | Campo | Tipo | Descrição | | ----- | ---- | --------------------------- | | — | — | Nenhum parâmetro necessário | **Resposta:** ```json [ { "tenant": "minha-empresa", "company_name": "Minha Empresa Ltda", "role": "admin" } ] ``` Se o usuário tem acesso a apenas um tenant, o agente o usa automaticamente. Se há múltiplos, o agente pergunta qual usar. *** Produtos [#produtos] Todas as tools de produtos requerem o parâmetro `tenant_id` (obtido via `list_tenants`). list_products [#list_products] Lista todos os produtos do tenant. | Parâmetro | Tipo | Obrigatório | Descrição | | ------------- | ------------- | ----------- | --------------------- | | `tenant_id` | string (UUID) | Sim | ID do tenant | | `search` | string | Não | Busca por texto | | `active_only` | boolean | Não | Filtrar apenas ativos | **Permissão necessária:** `products:read` get_product [#get_product] Busca um produto por ID. | Parâmetro | Tipo | Obrigatório | Descrição | | ----------- | ------------- | ----------- | ------------- | | `tenant_id` | string (UUID) | Sim | ID do tenant | | `id` | string (UUID) | Sim | ID do produto | **Permissão necessária:** `products:read` create_product [#create_product] Cria um novo produto. | Parâmetro | Tipo | Obrigatório | Descrição | | ------------- | ------------- | ----------- | -------------------- | | `tenant_id` | string (UUID) | Sim | ID do tenant | | `name` | string | Sim | Nome do produto | | `sale_price` | number | Sim | Preço de venda | | `description` | string | Não | Descrição | | `sku` | string | Não | Código SKU | | `is_active` | boolean | Não | Ativo (padrão: true) | **Permissão necessária:** `products:create` update_product [#update_product] Atualiza um produto existente (partial update). | Parâmetro | Tipo | Obrigatório | Descrição | | ------------- | ------------- | ----------- | ---------------- | | `tenant_id` | string (UUID) | Sim | ID do tenant | | `id` | string (UUID) | Sim | ID do produto | | `name` | string | Não | Novo nome | | `sale_price` | number | Não | Novo preço | | `description` | string | Não | Nova descrição | | `is_active` | boolean | Não | Ativar/desativar | **Permissão necessária:** `products:update` delete_product [#delete_product] Remove um produto (soft delete — o registro é preservado no histórico). O servidor **não expõe tools de restore** em nenhum recurso: para o agente, `delete_*` é de mão única. Desfazer uma exclusão é operação de usuário, feita no app web do bitERP. | Parâmetro | Tipo | Obrigatório | Descrição | | ----------- | ------------- | ----------- | ------------- | | `tenant_id` | string (UUID) | Sim | ID do tenant | | `id` | string (UUID) | Sim | ID do produto | **Permissão necessária:** `products:delete` *** Annotations [#annotations] Cada tool inclui annotations que descrevem seu comportamento: | Tool | Read-only | Destrutiva | | ---------------- | :-------: | :--------: | | `get_user_data` | Sim | — | | `list_tenants` | Sim | — | | `list_products` | Sim | — | | `get_product` | Sim | — | | `create_product` | — | Não | | `update_product` | — | Sim | | `delete_product` | — | Sim | O cliente MCP pode usar essas annotations para pedir confirmação do usuário antes de executar operações destrutivas. *** Paginação por cursor [#paginação-por-cursor] Tools de listagem que retornam muitos registros usam paginação por cursor: | Parâmetro | Tipo | Descrição | | --------- | ------ | ------------------------------------------------------ | | `cursor` | string | Cursor da página anterior (omitir na primeira chamada) | | `limit` | number | Itens por página (padrão: 20, máximo: 100) | O agente itera automaticamente até `has_next_page` ser `false`, agregando os resultados antes de apresentar ao usuário. # Clientes O recurso **Clientes** (`/customers`) mantém o cadastro de clientes do tenant — pessoas físicas (`individual`) ou jurídicas (`company`). Assim como produtos, clientes têm campos universais e extensões por país (`br_data`, `us_data`). Permissão exigida: `customers` (ações `create`, `read`, `update`, `delete`). Conceitos e campos-chave [#conceitos-e-campos-chave] | Campo | Tipo | Obrigatório | Observações | | -------------------- | ------------ | ----------- | --------------------------------------------------------------------------------------- | | `type` | enum | Sim | `individual` ou `company` | | `tax_id` | string | Sim | Documento fiscal (CPF/CNPJ no BR, EIN/SSN nos EUA). Único por tenant | | `legal_name` | string | Sim | Razão social / nome completo | | `tax_id_type` | enum | Não | `CPF`, `CNPJ`, `EIN` ou `SSN`. Gravado como enviado (não é auto-derivado) | | `trade_name` | string | Não | Nome fantasia | | `reference_code` | string | Não | Código externo; auto-gerado (`CUS-…`) se omitido; único por tenant | | `emails` | string\[] | Não | Lista de e-mails | | `phones` | string\[] | Não | Lista de telefones | | `website` | string | Não | | | `address_line1` | string | Não | Endereço (linha 1) | | `city` / `city_id` | string / int | Não | Cidade por nome (`city`) ou por ID do catálogo (`city_id`) | | `state` / `state_id` | string / int | Não | Estado por nome (`state`) ou por ID do catálogo (`state_id`) | | `postal_code` | string | Não | CEP / ZIP | | `country_code` | string | Não | ISO2 (`BR`), ISO3 (`BRA`), numérico (`076`) ou nome. Assume o país do tenant se omitido | | `tags` | string\[] | Não | | | `is_active` | boolean | Não | Padrão `true` | | `metadata` | object | Não | Dados livres da sua integração | | `br_data` | object | Não | Extensão Brasil (`ie`, `ie_exempt`, `im`, `tax_regime`, `pix_key`…) | | `us_data` | object | Não | Extensão EUA (`w9_on_file`, `naics_code`, `routing_number`…) | > Na API de Integrações, **`tax_id` é obrigatório** ao criar um cliente. Diferente de outros contextos internos do bitERP, onde ele é opcional. `tax_id` — CPF/CNPJ e EIN/SSN [#tax_id--cpfcnpj-e-einssn] O `tax_id` é **normalizado** antes de ser gravado e comparado: tudo que não é letra ou dígito é removido e o valor é convertido para maiúsculas. Ou seja, `12.345.678/0001-95` e `12345678000195` representam o mesmo cliente. A API **não valida dígitos verificadores** de CPF/CNPJ nem o formato de EIN/SSN — ela apenas normaliza e garante unicidade por tenant. Envie o `tax_id_type` que se aplica ao documento; ele é gravado como veio. Endereço e geografia [#endereço-e-geografia] Você pode informar cidade e estado por **texto** (`city`, `state`) — que a API resolve para o catálogo interno — ou diretamente pelos **IDs** do catálogo (`city_id`, `state_id`, obtidos via [Dados de referência](/docs/api/guides/reference-data)). Um ID inexistente ou um `country_code` não resolvível retorna `422 Unprocessable Entity`. Na resposta, cidade, estado e país vêm resolvidos como objetos (`city: { id, name }`, `state: { id, state_code, name }`, `country: { id, iso2, iso3, name }`). Criar um cliente [#criar-um-cliente] ```bash curl -X POST https://api.biterp.ai/customers \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "type": "company", "tax_id": "12.345.678/0001-95", "tax_id_type": "CNPJ", "legal_name": "Comercial Exemplo LTDA", "trade_name": "Exemplo Store", "emails": ["contato@exemplo.com.br"], "phones": ["+55 11 4002-8922"], "address_line1": "Av. Paulista, 1000", "city": "São Paulo", "state": "SP", "postal_code": "01310-100", "country_code": "BR", "br_data": { "ie": "123.456.789.012", "ie_exempt": false, "tax_regime": "simples_nacional" } }' ``` Buscar clientes [#buscar-clientes] Além da listagem paginada (`GET /customers`, com [filtros e paginação](/docs/api/pagination)): | Endpoint | Uso | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `GET /customers/search` | Busca full-text por `legal_name`, `trade_name` e `tax_id`. Aceita `q`, `limit` (padrão 50, 1–200) e `activeOnly`. | | `GET /customers/tax-id/{taxId}` | Localiza um cliente pelo `tax_id` (normalizado). Retorna `404` se não existir. | | `GET /customers/count` | Retorna a contagem total de clientes do tenant: `{ "count": 128 }`. | | `GET /customers/{id}` | Busca por UUID. | ```bash # Localizar por documento (pontuação é ignorada) curl "https://api.biterp.ai/customers/tax-id/12345678000195" \ -H "Authorization: Bearer sk_abc123_secretXYZ" ``` Campos filtráveis em `GET /customers`: `legal_name`, `trade_name`, `tax_id`, `type`, `city`, `state`, `country_code`, `is_active`, `reference_code`, `tags`, `created_at`, `updated_at`. Ordenação por `id`, `legal_name`, `trade_name`, `reference_code`, `created_at`, `updated_at`. Remover [#remover] `DELETE /customers/{id}` faz **soft delete** e responde `204 No Content`. Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | --------------------------- | ------------------------------------------------------------------ | | POST | `/customers` | [Criar cliente](/docs/api/reference/customers/create) | | GET | `/customers` | [Listar clientes](/docs/api/reference/customers/find-all) | | GET | `/customers/search` | [Buscar por texto](/docs/api/reference/customers/search) | | GET | `/customers/tax-id/{taxId}` | [Buscar por tax\_id](/docs/api/reference/customers/find-by-tax-id) | | GET | `/customers/count` | [Contar clientes](/docs/api/reference/customers/count) | | GET | `/customers/{id}` | [Buscar por ID](/docs/api/reference/customers/find-by-id) | | PATCH | `/customers/{id}` | [Atualizar cliente](/docs/api/reference/customers/update) | | DELETE | `/customers/{id}` | [Remover cliente](/docs/api/reference/customers/delete) | Veja também [#veja-também] O cadastro espelho de clientes, para o fluxo de compras. Onde os clientes aparecem no financeiro. # Contas financeiras Uma **conta financeira** (`/financial-accounts`) representa onde o dinheiro do tenant entra e sai: uma conta corrente, poupança, caixa ou carteira digital. É a conta que amarra o financeiro — toda [transação](/docs/api/guides/financial-transactions), [conta a receber](/docs/api/guides/receivables) e [conta a pagar](/docs/api/guides/payables) referencia uma conta financeira. Permissão exigida: `financial-accounts` (ações `create`, `read`, `update`, `delete`). Conceitos e campos-chave [#conceitos-e-campos-chave] | Campo | Tipo | Obrigatório | Observações | | ----------------- | ------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Sim | Nome da conta | | `type` | enum | Sim | `checking`, `savings`, `cash`, `digital_wallet` ou `gateway` | | `bank_id` | UUID | Não | Banco associado (ver [Dados de referência](/docs/api/guides/reference-data)) | | `initial_balance` | string | Não | Saldo inicial; base do cálculo de saldo. Decimal `numeric(18,6)` como string, **com sinal** (ex.: `"10000.000000"`, `"-250.500000"`); padrão `"0.000000"` | | `description` | string | Não | | | `is_active` | boolean | Não | | | `display_order` | number | Não | Ordem de exibição | | `metadata` | object | Não | Dados livres da sua integração | Os tipos `cash` e `digital_wallet` são contas **manuais** — é nelas que a liquidação automática de contas a receber/pagar pode gerar transações (ver `auto_generate_transaction` em [Contas a receber](/docs/api/guides/receivables)). O tipo `gateway` representa o saldo retido por um gateway ou adquirente (Stripe, por exemplo) antes do repasse para a conta bancária. Ele **não** é uma conta manual: as transações chegam pela própria integração, então `auto_generate_transaction` não tem efeito nele. Alocar uma transação a um título (`POST /payables/{id}/allocate`, `POST /receivables/{id}/allocate`) continua funcionando em qualquer tipo de conta, inclusive `gateway`. Criar uma conta [#criar-uma-conta] ```bash curl -X POST https://api.biterp.ai/financial-accounts \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "name": "Conta Corrente - Banco do Brasil", "type": "checking", "initial_balance": "10000.000000", "display_order": 1 }' ``` Calcular o saldo [#calcular-o-saldo] `GET /financial-accounts/{id}/balance` calcula o saldo atual da conta: ``` saldo = initial_balance + SOMA(amount de todas as transações da conta) ``` Como créditos têm `amount` positivo e débitos `amount` negativo, a soma já é o valor líquido. O saldo é retornado como **string** com 6 casas decimais: ```json { "balance": "15234.560000" } ``` Para um **saldo histórico** (saldo em uma data específica), envie o query param `up_to_date` (ISO 8601) — só as transações com `posted_at` até essa data são consideradas: ```bash curl "https://api.biterp.ai/financial-accounts/{id}/balance?up_to_date=2026-06-30T23:59:59Z" \ -H "Authorization: Bearer sk_abc123_secretXYZ" ``` Listar e manter [#listar-e-manter] `GET /financial-accounts` retorna a lista paginada (ver [filtros e paginação](/docs/api/pagination)). Campos filtráveis: `name`, `type`, `is_active`, `created_at`, `updated_at`. Ordenação por `id`, `name`, `type`, `initial_balance`, `display_order`, `created_at`. `DELETE /financial-accounts/{id}` faz **soft delete** e responde `204 No Content`. Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ---------------------------------- | -------------------------------------------------------------------- | | POST | `/financial-accounts` | [Criar conta](/docs/api/reference/financial-accounts/create) | | GET | `/financial-accounts` | [Listar contas](/docs/api/reference/financial-accounts/find-all) | | GET | `/financial-accounts/{id}/balance` | [Calcular saldo](/docs/api/reference/financial-accounts/get-balance) | | GET | `/financial-accounts/{id}` | [Buscar por ID](/docs/api/reference/financial-accounts/find-by-id) | | PATCH | `/financial-accounts/{id}` | [Atualizar conta](/docs/api/reference/financial-accounts/update) | | DELETE | `/financial-accounts/{id}` | [Remover conta](/docs/api/reference/financial-accounts/delete) | # Categorias financeiras Uma **categoria financeira** (`/financial-categories`) classifica para onde o dinheiro vai ou de onde ele vem: "Vendas de produtos", "Aluguel", "Folha de pagamento". É o plano de contas do tenant — [contas a receber](/docs/api/guides/receivables) e [contas a pagar](/docs/api/guides/payables) apontam para uma categoria através do campo `financial_category_id`. Permissão exigida: `financial-categories` (ações `create`, `read`, `update`, `delete`). Conceitos e campos-chave [#conceitos-e-campos-chave] | Campo | Tipo | Obrigatório | Observações | | ---------------- | ------- | ----------- | ----------------------------------------------------------------------- | | `name` | string | Sim | Nome da categoria. Único entre irmãs (mesmo `parent_id`) | | `type` | enum | Sim | `revenue` (receita) ou `expense` (despesa) | | `parent_id` | UUID | Não | Categoria-mãe. Ausente/`null` = categoria raiz | | `code` | string | Não | Código contábil da sua estrutura (ex.: `3.01.001`) | | `reference_code` | string | Não | Código do sistema de origem. `null` quando criada direto no bitERP | | `position` | number | Não | Inteiro de ordenação entre irmãs — é a **ordenação padrão** da listagem | | `is_locked` | boolean | Não | Trava a categoria para novos documentos (ver abaixo); padrão `false` | | `description` | string | Não | | | `metadata` | object | Não | Dados livres da sua integração | Hierarquia [#hierarquia] Categorias formam uma árvore, com três regras que a API impõe na criação e na atualização: 1. **Profundidade máxima de 3 níveis.** Uma quarta geração é rejeitada. 2. **O `type` da filha tem que ser igual ao da mãe.** Não existe subcategoria de despesa dentro de uma categoria de receita. 3. **Sem ciclos.** Uma categoria não pode ser mãe de si mesma, nem de uma ancestral sua. Nome duplicado sob a **mesma** mãe retorna `409 Conflict`. Duas categorias com o mesmo nome em mães diferentes são permitidas. Quais categorias podem ser usadas em documentos [#quais-categorias-podem-ser-usadas-em-documentos] Nem toda categoria aceita vínculo com um documento financeiro. Ao informar `financial_category_id` em uma conta a pagar ou a receber, a API exige que a categoria seja: * **Folha** — categorias que têm filhas servem para agrupar, não para lançar. Vincular uma categoria com filhas é rejeitado. * **Destravada** — uma categoria com `is_locked: true` continua existindo e mantém o histórico dos documentos que já a usavam, mas é recusada em **novos** documentos. É como aposentar uma categoria sem apagar o passado. Criar uma categoria [#criar-uma-categoria] ```bash curl -X POST https://api.biterp.ai/financial-categories \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "name": "Vendas de produtos", "type": "revenue", "code": "3.01.001", "position": 1 }' ``` Para criar uma subcategoria, informe o `parent_id` e repita o `type` da mãe: ```bash curl -X POST https://api.biterp.ai/financial-categories \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "name": "Vendas no varejo", "type": "revenue", "parent_id": "7b3e9d1c-4a2f-4e8b-9c0d-5f1a3b7e2d64", "position": 1 }' ``` Ler a árvore inteira [#ler-a-árvore-inteira] `GET /financial-categories/tree` devolve a hierarquia já montada — cada categoria traz suas filhas em `children[]`, recursivamente. É a chamada indicada para popular um seletor na sua interface, porque evita reconstruir a árvore a partir da listagem plana. ```bash # Só a árvore de despesas curl "https://api.biterp.ai/financial-categories/tree?type=expense" \ -H "Authorization: Bearer sk_abc123_secretXYZ" ``` O query param `type` é opcional e aceita `revenue` ou `expense`; sem ele, a resposta traz as duas árvores. Diferente de `GET /financial-categories`, este endpoint **não é paginado**. Listar e manter [#listar-e-manter] `GET /financial-categories` retorna a lista plana e paginada (ver [filtros e paginação](/docs/api/pagination)). Campos filtráveis: `type`, `parent_id`, `reference_code`, `name`, `code`, `position`, `is_locked`, `created_at`, `updated_at`. Ordenação por `id`, `type`, `parent_id`, `reference_code`, `name`, `code`, `position`, `is_locked`, `created_at`, `updated_at` — o padrão é `position:asc`. No contrato de filtros deste recurso, apenas `reference_code` é anulável — então só ele aceita os operadores `is_null` / `is_not_null`. É o caminho para achar o que ainda não foi mapeado a partir do seu sistema: ```bash GET /financial-categories?type=expense&reference_code[is_null]=true ``` `DELETE /financial-categories/{id}` faz **soft delete** e responde `204 No Content`. **Não há endpoint de restauração** — ver [`DELETE` é de mão única](/docs/api#delete-é-de-mão-única). Para tirar uma categoria de circulação sem perdê-la, prefira `is_locked: true`. Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ---------------------------- | ---------------------------------------------------------------------- | | POST | `/financial-categories` | [Criar categoria](/docs/api/reference/financial-categories/create) | | GET | `/financial-categories` | [Listar categorias](/docs/api/reference/financial-categories/find-all) | | GET | `/financial-categories/tree` | [Listar árvore](/docs/api/reference/financial-categories/find-tree) | | GET | `/financial-categories/{id}` | [Buscar por ID](/docs/api/reference/financial-categories/find-by-id) | | PATCH | `/financial-categories/{id}` | [Atualizar categoria](/docs/api/reference/financial-categories/update) | | DELETE | `/financial-categories/{id}` | [Remover categoria](/docs/api/reference/financial-categories/delete) | Veja também [#veja-também] Onde as categorias de receita são aplicadas. Onde as categorias de despesa são aplicadas. # Transações e extrato As **transações financeiras** (`/financial-transactions`) são o **ledger** (livro-razão) do tenant: cada linha é um crédito ou débito lançado em uma [conta financeira](/docs/api/guides/financial-accounts). Na API de Integrações você **não cria nem edita** transações — elas nascem de fluxos de negócio (veja abaixo). O que você faz por aqui é **consultar** (transações, extrato e saldo) e **remover** lançamentos via exclusão lógica (soft delete). Permissão exigida: `financial-transactions` (ações `read`, `delete`). Como as transações são criadas [#como-as-transações-são-criadas] Você não cria transações diretamente pela API de Integrações. Elas são geradas pelo bitERP em fluxos de negócio: * **Alocação/liquidação** de uma [conta a receber](/docs/api/guides/receivables) ou [a pagar](/docs/api/guides/payables) (o pagamento em si é uma transação). * **Liquidação automática** ao criar um recebível/pagável com `auto_generate_transaction` em uma conta manual (`cash`/`digital_wallet`). * **Importação OFX**, **transferências** entre contas e integrações bancárias. O `source` de cada transação indica sua origem: `manual`, `ofx`, `integration`, `migration` ou `transfer`. Conceitos e campos-chave [#conceitos-e-campos-chave] O sinal do `amount` define a natureza do lançamento: **crédito** é positivo, **débito** é negativo. | Campo | Tipo | Observações | | --------------- | --------- | ---------------------------------------------------------------------------------------------------------------- | | `account_id` | UUID | Conta financeira do lançamento | | `amount` | string | Decimal `numeric(18,6)` **com sinal**: positivo = crédito (`"1500.000000"`), negativo = débito (`"-250.500000"`) | | `posted_at` | date-time | Data/hora contábil do lançamento | | `source` | enum | `manual`, `ofx`, `integration`, `migration`, `transfer` | | `trn_type` | string | Tipo do lançamento (ex.: `CREDIT`, `DEBIT`, `XFER`) | | `is_reconciled` | boolean | Se a transação já foi conciliada | | `transfer_id` | UUID | Preenchido quando faz parte de uma transferência | | `memo`, `name` | string | Descrição / contraparte | | `metadata` | object | | Extrato de uma conta [#extrato-de-uma-conta] `GET /financial-transactions/account/{accountId}` retorna o **extrato** — todas as transações da conta, ordenadas da mais recente para a mais antiga (`posted_at` decrescente). O retorno é um array simples (não paginado). ```bash curl "https://api.biterp.ai/financial-transactions/account/9c8b7a6d-5e4f-4321-8b0a-1d2c3e4f5a6b" \ -H "Authorization: Bearer sk_abc123_secretXYZ" ``` Saldo [#saldo] `GET /financial-transactions/balance/{accountId}` retorna o saldo da conta (`initial_balance + SOMA(amount)`), no mesmo formato do endpoint de saldo de [contas financeiras](/docs/api/guides/financial-accounts): ```json { "balance": "15234.560000" } ``` Listar e remover [#listar-e-remover] `GET /financial-transactions` lista transações de forma paginada (ver [filtros e paginação](/docs/api/pagination)); a ordenação padrão é `posted_at` decrescente. `DELETE /financial-transactions/{id}` faz **soft delete** e responde `204 No Content`. Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | --------------------------------------------- | ------------------------------------------------------------------------------ | | GET | `/financial-transactions` | [Listar transações](/docs/api/reference/financial-transactions/find-all) | | GET | `/financial-transactions/account/{accountId}` | [Extrato da conta](/docs/api/reference/financial-transactions/find-by-account) | | GET | `/financial-transactions/balance/{accountId}` | [Saldo da conta](/docs/api/reference/financial-transactions/get-balance) | | GET | `/financial-transactions/{id}` | [Buscar por ID](/docs/api/reference/financial-transactions/find-by-id) | | DELETE | `/financial-transactions/{id}` | [Remover transação](/docs/api/reference/financial-transactions/delete) | # Mapeamento de entidades Os **mapeamentos de entidade** (`/integration-entity-mappings`) resolvem um problema clássico de integração: **relacionar o ID de um registro no seu sistema com o ID correspondente no bitERP**. Com esse vínculo, sua sincronização fica **idempotente** — você sabe se um cliente/produto/nota já foi criado e evita duplicar. > **Permissão:** o código de permissão deste recurso é **`integration-mappings`** — diferente do caminho da URL (`integration-entity-mappings`). Configure a permissão da API Key com o código `integration-mappings` (ações `read`, `create`, `update`, `delete`). Como funciona [#como-funciona] Cada mapeamento liga um `internal_entity_id` (o UUID do registro no bitERP) a um `external_entity_id` (o identificador no seu sistema), dentro de um `entity_type` (ex.: `customer`, `product`, `invoice`) e de uma integração (`tenant_integration_id`). A unicidade é garantida por duas chaves compostas: `(tenant_integration_id, entity_type, internal_entity_id)` e `(tenant_integration_id, entity_type, external_entity_id)`. Na prática, dentro de uma mesma integração e `entity_type`, um mesmo ID **interno** não pode mapear para dois externos, nem um mesmo ID **externo** para dois internos. Tentar criar um mapeamento que viola qualquer uma dessas chaves retorna `409 Conflict` — é esse comportamento que torna a operação idempotente. Manual vs. automático [#manual-vs-automático] O campo `mapping_origin` indica a origem do vínculo: * **`manual`** — criado por você via `POST /integration-entity-mappings`. * **`automatic`** — criado pelo próprio bitERP durante fluxos de sincronização. Esse caminho **não** é exposto pela API; ele aparece apenas na leitura. `mapping_origin`, `entity_type` e `internal_entity_id` são **imutáveis** após a criação. Campos de criação (POST) [#campos-de-criação-post] | Campo | Tipo | Obrigatório | Observações | | ----------------------- | ------ | ----------- | ----------------------------------------------------------------- | | `tenant_integration_id` | UUID | Sim | A integração à qual o mapeamento pertence | | `entity_type` | string | Sim | Tipo da entidade (texto livre, ≤ 100; ex.: `customer`, `product`) | | `internal_entity_id` | UUID | Sim | ID do registro no bitERP | | `external_entity_id` | string | Sim | ID no sistema externo (≤ 255) | | `sync_status` | enum | Não | `synced`, `pending` ou `error` (padrão `synced`) | | `metadata` | object | Não | Dados livres da sua integração | > `entity_type` é **texto livre** (não um enum fechado). Padronize seus próprios valores (`customer`, `product`, `invoice`…) e mantenha-os consistentes. Criar um mapeamento [#criar-um-mapeamento] ```bash curl -X POST https://api.biterp.ai/integration-entity-mappings \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "tenant_integration_id": "0193f000-1111-7222-8333-444455556666", "entity_type": "customer", "internal_entity_id": "0194aaaa-bbbb-7ccc-8ddd-eeeeffff0000", "external_entity_id": "cus_QabC123XyZ", "sync_status": "synced", "metadata": { "source_label": "Cliente no Stripe" } }' ``` A resposta traz `id`, `mapping_origin: "manual"`, `last_synced_at` e os campos de auditoria. Atualizar [#atualizar] `PATCH /integration-entity-mappings/{id}` altera apenas `external_entity_id`, `sync_status` e `metadata`. Definir `sync_status: "synced"` atualiza `last_synced_at`. Listar e remover [#listar-e-remover] `GET /integration-entity-mappings` lista de forma paginada. Campos filtráveis: `tenant_integration_id`, `entity_type`, `sync_status`, `mapping_origin`, `internal_entity_id`, `external_entity_id`, `created_at`, `last_synced_at` — úteis para localizar um mapeamento pelo ID externo antes de decidir criar ou atualizar um registro. `DELETE /integration-entity-mappings/{id}` faz **soft delete** (`204 No Content`). Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ----------------------------------- | --------------------------------------------------------------------------- | | POST | `/integration-entity-mappings` | [Criar mapeamento](/docs/api/reference/integration-entity-mappings/create) | | GET | `/integration-entity-mappings` | [Listar](/docs/api/reference/integration-entity-mappings/find-all) | | GET | `/integration-entity-mappings/{id}` | [Buscar por ID](/docs/api/reference/integration-entity-mappings/find-by-id) | | PATCH | `/integration-entity-mappings/{id}` | [Atualizar](/docs/api/reference/integration-entity-mappings/update) | | DELETE | `/integration-entity-mappings/{id}` | [Remover](/docs/api/reference/integration-entity-mappings/delete) | # Notas / faturas Uma **nota / fatura** (`/invoices`) é o documento de faturamento de uma venda. É a terceira etapa do [fluxo de vendas](/docs/api/guides/sales-flow): pode ser ligada a um [pedido](/docs/api/guides/sales-orders), carregar impostos por item e gerar [contas a receber](/docs/api/guides/receivables). Permissão exigida: `invoices` (ações `create`, `read`, `update`, `delete`). > Esta é a nota **comercial**. A API de Integrações oferece o CRUD; ela **não** transmite documentos fiscais (NFe/NFSe) nem faz "emissão" — esses fluxos ficam fora deste canal. Conceitos e campos-chave [#conceitos-e-campos-chave] | Campo | Tipo | Obrigatório | Observações | | -------------------------------- | ------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `reference_code` | string | Não | Opcional e anulável: texto livre seu de até 100 caracteres. **Não** é autogerado e **não** é o número fiscal. Envie `""` para limpar (vira `null`) | | `customer_id` | UUID | Sim\* | \*Ou o objeto `customer` inline — exatamente **um** dos dois | | `product_type` | enum | Sim | `goods`, `service` ou `mixed` | | `invoice_date` | date | Sim | `YYYY-MM-DD` | | `items` | array | Sim | 1 a 200 itens | | `operation_type` | string | Não | Padrão `sale` | | `sales_order_id` | UUID | Não | Pedido de origem (ver validações abaixo) | | `customer_tax_type` | enum | Não | `individual` ou `company` | | `discount_amount` | string | Não | Desconto em valor (≥ 0) — decimal `numeric(18,6)` como string | | `discount_rate` | string | Não | Percentual do subtotal (0 a 100) — decimal `numeric(9,6)` como string | | `fiscal_notes`, `internal_notes` | string | Não | | | `taxes` | array | Não | Impostos manuais por item (ver abaixo) | | `receivables` | array | Não | Gera contas a receber (ver abaixo) | | `metadata`, `br_data`, `us_data` | — | Não | | > **`discount_rate` usa a escala percentual (0 a 100) e trafega como string decimal** — `"15.000000"` significa 15%, igual a [orçamentos](/docs/api/guides/quotes) e [pedidos](/docs/api/guides/sales-orders). Itens da nota [#itens-da-nota] Cada item referencia um `product_id` **ou** traz um `product` inline (exatamente um dos dois). Campos: `item_description`, `quantity` (**string** decimal ≥ `"0.000001"`), `unit_price` (**string** decimal ≥ `"0"`), `discount_amount` (**string** decimal). Para rastrear a origem no pedido, informe `sales_order_item_id` no item. Impostos (`taxes[]`) [#impostos-taxes] Impostos são informados por item, com `tax_code`, `tax_name`, `base_amount` (string decimal), `rate` (**string** decimal, 0 a 100), `amount` (string decimal), `position` (inteiro) e flags como `is_withheld` (retido) e `is_included`. Códigos aceitos: `ISS`, `PIS`, `COFINS`, `IR`, `CSLL`, `INSS`, `IBS_STATE`, `IBS_CITY`, `CBS`. Vínculo com o pedido [#vínculo-com-o-pedido] Ao enviar `sales_order_id`, a API valida que `customer_id`, `operation_type` e `product_type` da nota **casam** com os do pedido. Só pode existir **uma nota ativa por pedido** — uma segunda tentativa retorna erro. Ver o [fluxo de vendas](/docs/api/guides/sales-flow). Gerando contas a receber [#gerando-contas-a-receber] Inclua um array `receivables[]` (`amount` — decimal como string —, `due_date` date-time, `financial_account_id`; opcionais `payment_method_id`, `description`). A **soma** de `receivables[].amount` deve ser **exatamente igual** ao total líquido da nota (`net_receivable_total` = total − impostos retidos), senão a criação é rejeitada. Se houver mais de um, viram parcelas. Os recebíveis criados nascem `pending` e ficam ligados por `origin_type = "invoice"`; eles **não** aparecem no corpo da nota — consulte-os no recurso [`/receivables`](/docs/api/guides/receivables). Criar uma nota (ligada ao pedido, com recebível) [#criar-uma-nota-ligada-ao-pedido-com-recebível] ```bash curl -X POST https://api.biterp.ai/invoices \ -H "Authorization: Bearer sk_xxx_yyy" \ -H "Content-Type: application/json" \ -d '{ "reference_code": "NF-2026-0042", "customer_id": "8f2c1e4a-9b3d-4c7e-a1f2-6d5b8e0c3a71", "sales_order_id": "7e6d5c4b-3a2f-4109-8e7d-6c5b4a3f2e1d", "product_type": "goods", "invoice_date": "2026-07-06", "operation_type": "sale", "items": [ { "product_id": "3a7b9c2d-1e4f-4a6b-8c0d-2f5e7a9b1c3d", "item_description": "Cadeira ergonômica", "quantity": "2.000000", "unit_price": "450.000000", "sales_order_item_id": "d4c3b2a1-9f8e-4706-a5b4-c3d2e1f0a9b8" }, { "product_id": "5d8e1f3a-6b2c-4d7e-9a0f-1c3b5e7d9a2f", "item_description": "Mesa de escritório", "quantity": "1.000000", "unit_price": "1200.000000" } ], "receivables": [ { "amount": "2100.000000", "due_date": "2026-08-06T00:00:00.000Z", "financial_account_id": "9c8b7a6d-5e4f-4321-8b0a-1d2c3e4f5a6b" } ] }' ``` > Sem impostos retidos, `net_receivable_total` é igual ao total (2 × 450 + 1 × 1200 = 2100), então `receivables[].amount` soma `"2100.000000"`. Status e atualização [#status-e-atualização] Pela API de Integrações, toda nota nasce em `status: draft` (os estados comerciais são `draft`, `finalized`, `cancelled`). No `PATCH`, os campos `status` e `operation_type` **não** são editáveis. O que pode ser alterado, por estado [#o-que-pode-ser-alterado-por-estado] A edição depende do estado de transmissão fiscal (BR) e do vínculo com um pedido de venda: | Estado da nota | Campos aceitos no `PATCH` | | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | | Rascunho (`draft`) | Todos, sujeitos às restrições de vínculo com pedido abaixo | | Finalizada (`finalized`) e ainda **não** transmitida (`transmission_status` ausente) | Todos, sujeitos às restrições de vínculo com pedido abaixo | | Finalizada e transmitida com erro (`transmission_error`, fluxo de retransmissão) | Todos, sujeitos às restrições de vínculo com pedido abaixo | | Na fila de transmissão fiscal (`queued`) | Nenhum — a nota já foi entregue ao provedor fiscal; nem anotações internas | | Já transmitida e autorizada (`issued`) | Somente `reference_code`, `internal_notes` e `metadata` | | Finalizada no Brasil (`finalized`), sem linha em `invoices_br_data` | Somente `reference_code`, `internal_notes` e `metadata` (fail secure — mesmo tratamento de `issued`) | | Cancelamento em andamento, ou nota cancelada | Nenhum | Fora do Brasil não existe eixo fiscal: `transmission_status` é sempre ausente, então `draft` e `finalized` aceitam todos os campos. Se a nota tiver `sales_order_id`, mesmo fora dos casos acima ficam bloqueados `discount_amount`, `discount_rate`, `product_type`, `salesperson_name` e `fiscal_notes` no cabeçalho e, nos itens, adicionar/remover/reordenar linhas ou alterar `product_id`, `quantity`, `unit_price`, `discount_amount` ou `sales_order_item_id` de uma linha existente — esses dados vêm do pedido. `item_description`, `po_number`, dados fiscais de linha e `taxes` continuam editáveis normalmente. Um `PATCH` com algum campo não permitido para o estado atual retorna `422` nomeando o(s) campo(s) rejeitado(s). Antes, uma nota já transmitida (`issued`) aceitava alterações em `taxes`, `discount_amount`, `discount_rate`, `br_data`, `items` e `product_type` — divergindo do que já havia sido autorizado no provedor fiscal. Agora esses campos são rejeitados com `422` nesse estado (e também com a nota na fila de transmissão, `queued`, onde nenhum campo é aceito). No Brasil, uma nota `finalized` sem registro em `invoices_br_data` recebe o mesmo tratamento restrito (`internal_notes` e `metadata` apenas) — qualquer outro campo no corpo do `PATCH` retorna `422` nomeando o(s) campo(s) rejeitado(s). Se a sua integração corrigia dados fiscais depois da transmissão, mova a correção para **antes** de transmitir, ou trate o `422` e ajuste o fluxo. Enviar `items` **substitui** a lista inteira: linhas com `id` são mantidas, omitidas são removidas. Já `taxes` tem três comportamentos distintos no `PATCH`: | `taxes` no corpo | Efeito | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Ausente** | Não altera impostos. Enviando `items`, cada linha mantida preserva os impostos que já tinha. | | **`[]` (array vazio)** | **Remove** todos os impostos que seriam preservados. | | **Preenchido** | Com `items`: substituição **por item**, endereçada pelo `item_index` — só as linhas citadas trocam de impostos; as não citadas preservam os que já tinham. Sem `items`: substitui o conjunto inteiro de impostos da nota. | Um item que traga `tax_resolution_id` é sempre re-resolvido pelo motor fiscal, mesmo com `taxes: []` — o array vazio limpa apenas os impostos preservados, não uma re-resolução pedida no mesmo corpo. Até então, enviar `items` junto de `taxes: []` era tratado como "nenhuma substituição por item" e **preservava** os impostos existentes. Agora esse par **remove** os impostos. Se a sua integração enviava `taxes: []` só para preencher o campo, **omita `taxes`** do corpo para manter o comportamento anterior. `DELETE /invoices/{id}` remove notas `draft`/`finalized`; recebíveis já pagos bloqueiam a exclusão. Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ---------------- | -------------------------------------------------------- | | POST | `/invoices` | [Criar nota](/docs/api/reference/invoices/create) | | GET | `/invoices` | [Listar notas](/docs/api/reference/invoices/find-all) | | GET | `/invoices/{id}` | [Buscar por ID](/docs/api/reference/invoices/find-by-id) | | PATCH | `/invoices/{id}` | [Atualizar nota](/docs/api/reference/invoices/update) | | DELETE | `/invoices/{id}` | [Remover nota](/docs/api/reference/invoices/delete) | # Contas a pagar Uma **conta a pagar** (`/payables`) é um valor que o tenant deve a um [fornecedor](/docs/api/guides/suppliers), vinculado a uma [conta financeira](/docs/api/guides/financial-accounts). É o **espelho de [Contas a receber](/docs/api/guides/receivables)**: mesma máquina de estado, alocação, parcelamento, vencidas e resumo — trocando "receber" por "pagar". Permissão exigida: `payables` (ações `create`, `read`, `update`, `delete`). Ciclo de vida [#ciclo-de-vida] Os status são os mesmos das contas a receber: `pending`, `partial`, `paid`, `overdue`, `cancelled`. Um pagável nasce em `pending` e caminha para `paid` conforme você **aloca transações de débito** (paga). A diferença relevante frente aos recebíveis é que o **`overdue` é aplicado automaticamente** ao recalcular: se restar saldo e o `due_date` já passou, o status vai para `overdue` (inclusive ao alterar a data de vencimento via `PATCH`). > Assim como em recebíveis, `status` é um campo bloqueado no `PATCH` — mude o estado com as ações (`allocate`, `cancel`). Criar uma conta a pagar [#criar-uma-conta-a-pagar] | Campo | Tipo | Obrigatório | Observações | | --------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------ | | `supplier_id` | UUID | Sim | Fornecedor credor | | `financial_account_id` | UUID | Sim | Conta financeira (deve estar ativa) | | `amount` | string | Sim | Valor a pagar (> 0) — decimal `numeric(18,6)` como string (ex.: `"1250.000000"`) | | `issue_date` | date | Sim | `YYYY-MM-DD` | | `due_date` | date | Sim | `YYYY-MM-DD` | | `payment_method_id` | UUID | Não | | | `financial_category_id` | UUID | Não | | | `reference_code` | string | Não | Auto-gerado (`PAY-…`) se omitido; único por tenant | | `discount_amount` | string | Não | (≥ 0) — decimal como string | | `surcharge_amount` | string | Não | (≥ 0) — decimal como string | | `description` / `notes` | string | Não | | | `auto_generate_transaction` | boolean | Não | Em conta manual (`cash`/`digital_wallet`), gera a transação de débito, aloca e marca como `paid` | | `metadata` | object | Não | | > Diferente de recebíveis, contas a pagar **não** têm os campos `origin_type`, `origin_id`, `sales_order_id` nem `invoice_id`. O vínculo com o fluxo de compras vem por [notas de compra](/docs/api/guides/purchase-invoices) (que geram os pagáveis). ```bash curl -X POST https://api.biterp.ai/payables \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "supplier_id": "1a2b3c4d-5e6f-4708-9a0b-1c2d3e4f5a6b", "financial_account_id": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6071", "amount": "1250.000000", "issue_date": "2026-07-06", "due_date": "2026-08-05", "description": "Compra de matéria-prima" }' ``` Pagar: alocar uma transação [#pagar-alocar-uma-transação] Pagar é **alocar uma [transação](/docs/api/guides/financial-transactions) de débito** ao pagável. `POST /payables/{id}/allocate`: | Campo | Tipo | Obrigatório | Observações | | -------------------------- | ------ | ----------- | ------------------------------------------ | | `financial_transaction_id` | UUID | Sim | Transação de **débito** (amount negativo) | | `allocated_amount` | string | Sim | Valor a alocar (> 0) — decimal como string | | `notes` | string | Não | | As regras espelham as de recebíveis: o pagável não pode estar `paid`/`cancelled`; a transação deve ser da **mesma** conta e ser um débito (negativa); `allocated_amount` respeita o saldo restante do pagável e da transação. Use `DELETE /payables/{id}/allocate/{allocationId}` para desfazer. ```bash curl -X POST https://api.biterp.ai/payables/{id}/allocate \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "financial_transaction_id": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "allocated_amount": "500.000000", "notes": "Pagamento parcial ao fornecedor" }' ``` O que pode ser alterado, por estado [#o-que-pode-ser-alterado-por-estado] O `PATCH` aceita campos diferentes conforme o `status` do pagável. Depois que o dinheiro se moveu — total (`paid`) ou parcialmente (`partial`) — o título descreve um fato consumado, e só continua editável o que **não** altera esse fato: | Status do pagável | Campos aceitos no `PATCH` | | -------------------- | --------------------------------------------------------------- | | `pending`, `overdue` | Todos os do corpo do `PATCH`, sujeitos às validações do recurso | | `partial` | Somente `notes`, `financial_category_id` e `metadata` | | `paid` | Somente `notes`, `financial_category_id` e `metadata` | | `cancelled` | Nenhum — nem mesmo um corpo vazio | Um `PATCH` com algum campo não permitido para o estado atual retorna `422` **nomeando o(s) campo(s) rejeitado(s)**. E se o status mudar enquanto o `PATCH` estiver em voo — uma baixa ou um cancelamento concorrente —, a atualização é recusada com `409` em vez de ser aplicada com as regras do estado antigo: releia o registro e repita. Para alterar qualquer outro campo de um título já baixado, **desfaça, estorne ou desvincule o pagamento** (`DELETE /payables/{id}/allocate/{allocationId}`, ou as ações de estorno). A edição total volta quando o status **recalculado** for `pending` ou `overdue` — e quem decide entre os dois é o **vencimento**, não a ausência de valor baixado: com saldo em aberto e vencimento já passado o título fica `overdue`, que mantém a edição total mesmo com baixa parcial; ainda dentro do prazo e com valor baixado remanescente — várias baixas, ou um estorno parcial —, ele fica `partial` e continua restrito. Não há beco sem saída — só a obrigação de passar pela operação que corresponde ao que de fato aconteceu. `financial_category_id` continua editável depois da baixa de propósito: a categoria contábil existe **apenas** no título (a transação financeira não tem esse campo), então corrigir uma classificação errada não teria outro caminho a não ser desfazer e refazer o pagamento. Antes, um pagável `partial` aceitava alterações em `amount`, `due_date`, `issue_date`, `financial_account_id`, `payment_method_id`, descontos e acréscimos mesmo com o pagamento já registrado — e um pagável `paid` rejeitava o `PATCH` inteiro, inclusive anotações. Agora `partial` e `paid` seguem a mesma regra: só `notes`, `financial_category_id` e `metadata`. Se a sua integração ajustava valores ou datas depois da baixa, mova o ajuste para **antes** de baixar, ou desfaça a baixa, corrija e refaça. Cancelar [#cancelar] `POST /payables/{id}/cancel` cancela o pagável. É bloqueado se ele já estiver `paid` ou `cancelled`. Parcelar [#parcelar] `POST /payables/installments` funciona como em recebíveis (`total_amount` — decimal como string —, `installment_count` de 2 a 120, `issue_date`, `first_due_date`, `supplier_id`, `financial_account_id`), gerando N pagáveis com `installment_group_id` comum e vencimentos mensais. A sobra de arredondamento vai na **última** parcela. Vencidas e resumo [#vencidas-e-resumo] * `GET /payables/overdue` — pagáveis com status `pending`/`partial` e `due_date` no passado (paginado). * `GET /payables/summary` — agregado por status (`status`, `total_count`, `total_amount`), mesmo formato do resumo de [recebíveis](/docs/api/guides/receivables). Filtros [#filtros] Campos filtráveis em `GET /payables`: `reference_code`, `status`, `amount`, `due_date`, `issue_date`, `supplier_id`, `financial_account_id`, `financial_category_id`, `payment_method_id`, `installment_group_id`, `created_at`, `updated_at`. Ver [paginação e filtros](/docs/api/pagination). Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ---------------------------------------- | --------------------------------------------------------------------- | | POST | `/payables` | [Criar](/docs/api/reference/payables/create) | | GET | `/payables` | [Listar](/docs/api/reference/payables/find-all) | | POST | `/payables/installments` | [Criar parcelas](/docs/api/reference/payables/create-installments) | | GET | `/payables/overdue` | [Listar vencidas](/docs/api/reference/payables/find-overdue) | | GET | `/payables/summary` | [Resumo por status](/docs/api/reference/payables/get-summary) | | GET | `/payables/{id}` | [Buscar por ID](/docs/api/reference/payables/find-by-id) | | PATCH | `/payables/{id}` | [Atualizar](/docs/api/reference/payables/update) | | DELETE | `/payables/{id}` | [Remover](/docs/api/reference/payables/delete) | | POST | `/payables/{id}/cancel` | [Cancelar](/docs/api/reference/payables/cancel) | | POST | `/payables/{id}/allocate` | [Alocar transação](/docs/api/reference/payables/allocate-transaction) | | DELETE | `/payables/{id}/allocate/{allocationId}` | [Remover alocação](/docs/api/reference/payables/remove-allocation) | # Produtos O recurso **Produtos** (`/products`) mantém o catálogo de bens e serviços do tenant. Cada produto tem campos universais (nome, preço, unidade de medida) e, opcionalmente, uma extensão fiscal por país (`br_data` no Brasil, `us_data` nos Estados Unidos) — o **Extension Table Pattern** do bitERP. Permissão exigida: `products` (ações `create`, `read`, `update`, `delete`). Conceitos e campos-chave [#conceitos-e-campos-chave] Um produto sempre tem um `type` — `goods` (bem) ou `service` (serviço) — definido na criação. O `reference_code` é o código do produto no seu catálogo (SKU): você pode enviá-lo ou deixar a API gerar um automaticamente com o prefixo `PRD-`. Ele é único por tenant e serve para sincronizar com sistemas externos. | Campo | Tipo | Obrigatório | Observações | | ------------------------ | --------- | ----------- | ----------------------------------------------------------------------------------------------------------------- | | `name` | string | Sim | Nome do produto | | `type` | enum | Sim | `goods` ou `service` | | `reference_code` | string | Não | SKU único por tenant; auto-gerado (`PRD-…`) se omitido | | `sale_price` | string | Não | Preço de venda (≥ 0) — decimal `numeric(18,6)` como string (ex.: `"3499.990000"`); assume `"0.000000"` se omitido | | `unit_of_measurement_id` | UUID | Não | Unidade de medida (ver [Dados de referência](/docs/api/guides/reference-data)) | | `description` | string | Não | Descrição livre | | `tags` | string\[] | Não | Etiquetas de organização | | `image_url` | string | Não | URL da imagem | | `is_blocked` | boolean | Não | Bloqueia o produto para uso; padrão `false` | | `metadata` | object | Não | Dados livres da sua integração | | `br_data` | object | Não | Extensão fiscal Brasil (ver abaixo) | | `us_data` | object | Não | Extensão fiscal Estados Unidos | > **`is_active` está depreciado.** É apenas um alias invertido de `is_blocked` (`is_active = !is_blocked`), mantido por compatibilidade. Prefira `is_blocked`. Se você enviar os dois com valores contraditórios, a API retorna erro de validação. Unidade de medida [#unidade-de-medida] Use `unit_of_measurement_id` (UUID) no topo do payload — é a forma canônica. O campo legado `br_data.unit_of_measure` ainda é aceito por compatibilidade e resolve tanto um UUID quanto um código de unidade (`UN`, `CX`, `KG`…) pelo país do tenant, mas está **depreciado**. Se você enviar os dois e eles apontarem para unidades diferentes, a API retorna erro de validação; se forem iguais, `unit_of_measurement_id` prevalece. Na resposta, a unidade vem resolvida como objeto: `"unit_of_measurement": { "id": "…", "code": "UN", "symbol": "un" }`. Extensão fiscal Brasil (`br_data`) [#extensão-fiscal-brasil-br_data] Enviada apenas para tenants do Brasil. Campos aceitos incluem `ncm_code` (8 dígitos), `cest_code` (7 dígitos), `origin` (1 dígito), `extipi`, `nbs_code`, `taxation_type`, códigos da Reforma Tributária (`ibs_cbs_*`) e outros. Enviar `br_data` em um tenant de outro país (ou `us_data` num tenant BR) é rejeitado. Criar um produto [#criar-um-produto] ```bash curl -X POST https://api.biterp.ai/products \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "reference_code": "EXT-SKU-1001", "name": "Notebook Pro 14", "description": "Notebook 14 polegadas, 16GB RAM", "type": "goods", "sale_price": "3499.990000", "unit_of_measurement_id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11", "tags": ["eletronicos", "informatica"], "is_blocked": false, "br_data": { "ncm_code": "84713012", "cest_code": "0100100", "origin": "0" } }' ``` Buscar produtos [#buscar-produtos] Há três formas de localizar produtos, além da listagem paginada padrão (`GET /products` com [filtros e paginação](/docs/api/pagination)): | Endpoint | Uso | | -------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `GET /products/search` | Busca full-text por `name` e `reference_code`. Aceita `q`, `limit` (padrão 50) e `activeOnly` (padrão `false`). | | `GET /products/reference/{code}` | Localiza um produto pelo `reference_code` exato. O query param `type` (`goods`/`service`) é **obrigatório**. | | `GET /products/{id}` | Busca por UUID. | O endpoint por `reference_code` é o ideal para sincronizar por SKU: se o produto existir mas o `type` informado não bater, a API retorna `404` — o par (`reference_code`, `type`) identifica o produto. ```bash # Buscar por texto (apenas ativos) curl -G https://api.biterp.ai/products/search \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ --data-urlencode "q=notebook" \ --data-urlencode "activeOnly=true" # Localizar por SKU externo curl "https://api.biterp.ai/products/reference/EXT-SKU-1001?type=goods" \ -H "Authorization: Bearer sk_abc123_secretXYZ" ``` Campos filtráveis em `GET /products`: `name`, `reference_code`, `is_blocked`, `type`, `created_at`, `updated_at`. Ordenação por `id`, `name`, `reference_code`, `created_at`, `updated_at`. Remover [#remover] `DELETE /products/{id}` faz **soft delete** (o produto pode ser recuperado) e responde `204 No Content`. Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ---------------------------- | --------------------------------------------------------------------------------- | | POST | `/products` | [Criar produto](/docs/api/reference/products/create) | | GET | `/products` | [Listar produtos](/docs/api/reference/products/find-all) | | GET | `/products/search` | [Buscar por texto](/docs/api/reference/products/search) | | GET | `/products/reference/{code}` | [Buscar por reference\_code](/docs/api/reference/products/find-by-reference-code) | | GET | `/products/{id}` | [Buscar por ID](/docs/api/reference/products/find-by-id) | | PATCH | `/products/{id}` | [Atualizar produto](/docs/api/reference/products/update) | | DELETE | `/products/{id}` | [Remover produto](/docs/api/reference/products/delete) | # Notas de compra Uma **nota de compra** (`/purchase-invoices`) registra um documento recebido de um [fornecedor](/docs/api/guides/suppliers). É o espelho, no lado das compras, da [nota de venda](/docs/api/guides/invoices): tem itens, impostos e pode gerar **[contas a pagar](/docs/api/guides/payables)** após a finalização. Permissão exigida: `purchase-invoices` (ações `create`, `read`, `update`, `delete`). Conceitos e campos-chave [#conceitos-e-campos-chave] | Campo | Tipo | Obrigatório | Observações | | -------------------------------------------------------- | ------ | ----------- | ------------------------------------------------------------------------- | | `supplier_id` | UUID | Sim | Fornecedor — **precisa existir** (não há cadastro inline) | | `invoice_number` | string | Sim | Número do documento do fornecedor (≤ 60) | | `product_type` | enum | Sim | `goods`, `service` ou `mixed` | | `purchase_date` | date | Sim | | | `items` | array | Sim | 1+ itens | | `reference_code` | string | Não | Autogerado (`PINV-…`) se omitido | | `invoice_series` | string | Não | Série do documento | | `operation_type` | string | Não | Padrão `purchase` (≤ 50 caracteres) | | `received_date`, `due_date` | date | Não | | | `discount_amount` | string | Não | Desconto em valor (≥ 0) — decimal `numeric(18,6)` como string | | `discount_rate` | string | Não | Desconto em **percentual** (0 a 100) — decimal `numeric(9,6)` como string | | `taxes` | array | Não | Impostos por item | | `payables` | array | Não | Planejamento financeiro (ver abaixo) | | `description`, `notes`, `metadata`, `br_data`, `us_data` | — | Não | | Itens da nota de compra [#itens-da-nota-de-compra] Cada item tem `item_description` (obrigatório), `quantity` (**string** decimal ≥ `"0.000001"`) e `unit_price` (**string** decimal ≥ `"0"`); `product_id` é **opcional** (permite lançar itens sem vínculo a um produto do catálogo). Em atualizações, informe `id` no item para reconciliação. > **Unicidade:** não pode haver nota de compra ativa com o mesmo fornecedor, série e número de documento — a segunda tentativa é rejeitada. Planejamento e materialização de contas a pagar [#planejamento-e-materialização-de-contas-a-pagar] O fluxo financeiro segue o mesmo princípio do [pedido de venda](/docs/api/guides/sales-orders): o `create` **não** gera títulos reais. 1. **`POST /purchase-invoices`** — inclua `payables[]` opcional (`amount`, `due_date`, `financial_account_id` obrigatório; opcionais `financial_category_id`, `payment_method_id`, `description`). A soma deve bater com `net_payable_total`. A nota nasce `draft` com `pending_payables` no response (somente leitura). 2. **`POST /purchase-invoices/{id}/finalize`** — materializa os payables (`purchase-invoices:update`) e limpa o snapshot. Só então os títulos aparecem em [`/payables`](/docs/api/guides/payables). Cada parcela carrega a **própria** `financial_category_id` (opcional). Não envie `financial_account_id` nem `financial_category_id` no cabeçalho da nota. Criar uma nota de compra (com parcelas planejadas) [#criar-uma-nota-de-compra-com-parcelas-planejadas] ```bash curl -X POST https://api.biterp.ai/purchase-invoices \ -H "Authorization: Bearer sk_xxx_yyy" \ -H "Content-Type: application/json" \ -d '{ "supplier_id": "1a2b3c4d-5e6f-4708-9a0b-1c2d3e4f5a6b", "invoice_number": "12345", "invoice_series": "1", "product_type": "goods", "purchase_date": "2026-07-06", "due_date": "2026-08-05", "items": [ { "item_description": "Matéria-prima X", "quantity": "100.000000", "unit_price": "12.500000" } ], "payables": [ { "amount": "1250.000000", "due_date": "2026-08-05", "financial_account_id": "9c8b7a6d-5e4f-4321-8b0a-1d2c3e4f5a6b" } ] }' ``` Depois, finalize: ```bash curl -X POST https://api.biterp.ai/purchase-invoices/{id}/finalize \ -H "Authorization: Bearer sk_xxx_yyy" \ -H "Content-Type: application/json" \ -d '{}' ``` Status e atualização [#status-e-atualização] A nota nasce em `status: draft` (estados comerciais: `draft`, `finalized`). `finalized` é **terminal** — não há `cancel` nem `reopen`; para corrigir, exclua e recrie. No `PATCH` em `draft`, `payables` substitui o snapshot por completo (`[]` limpa). Se o total mudar sem `payables`, o snapshot só é preservado quando a soma continuar compatível; caso contrário a API retorna `422`. Em `finalized`, alterações financeiras são bloqueadas. `DELETE /purchase-invoices/{id}` remove notas `draft`/`finalized` e, quando finalizada, exclui em cascata os payables vinculados (bloqueado se algum já estiver parcial ou pago). Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ---------------------------------- | ------------------------------------------------------------------------ | | POST | `/purchase-invoices` | [Criar nota de compra](/docs/api/reference/purchase-invoices/create) | | GET | `/purchase-invoices` | [Listar notas de compra](/docs/api/reference/purchase-invoices/find-all) | | GET | `/purchase-invoices/{id}` | [Buscar por ID](/docs/api/reference/purchase-invoices/find-by-id) | | PATCH | `/purchase-invoices/{id}` | [Atualizar](/docs/api/reference/purchase-invoices/update) | | DELETE | `/purchase-invoices/{id}` | [Remover](/docs/api/reference/purchase-invoices/delete) | | POST | `/purchase-invoices/{id}/finalize` | [Finalizar](/docs/api/reference/purchase-invoices/finalize) | # Orçamentos Um **orçamento** (`/quotes`) é uma proposta comercial: um [cliente](/docs/api/guides/customers), uma lista de itens e valores, com validade. É a primeira etapa do [fluxo de vendas](/docs/api/guides/sales-flow) — sem compromisso financeiro até virar um [pedido](/docs/api/guides/sales-orders). Permissão exigida: `quotes` (ações `create`, `read`, `update`, `delete`). Conceitos e campos-chave [#conceitos-e-campos-chave] | Campo | Tipo | Obrigatório | Observações | | ------------------------------------------- | ------ | ----------- | ------------------------------------------------------------------------- | | `customer_id` | UUID | Sim | Cliente da proposta | | `product_type` | enum | Sim | `goods`, `service` ou `mixed` | | `items` | array | Sim | 1 a 200 itens (ver abaixo) | | `status` | enum | Não | `pending`, `approved`, `rejected`, `expired` | | `reference_code` | string | Não | Auto-gerado (`QTE-…`) se omitido | | `quote_date` | date | Não | | | `valid_until` | date | Não | Validade da proposta | | `salesperson_name` | string | Não | | | `discount_amount` | string | Não | Desconto em valor (≥ 0) — decimal `numeric(18,6)` como string | | `discount_rate` | string | Não | Desconto em **percentual** (0 a 100) — decimal `numeric(9,6)` como string | | `description`, `tax_notes`, `payment_notes` | string | Não | | | `metadata` | object | Não | | Itens do orçamento [#itens-do-orçamento] > `quantity` e `unit_price` são **strings decimais** (`numeric(18,6)`), como em pedidos e notas — o mesmo payload de itens vale para os três recursos. | Campo | Tipo | Obrigatório | Observações | | ------------------ | ------ | ----------- | -------------------------------------------------- | | `product_id` | UUID | Sim | Produto do catálogo (não há produto inline) | | `item_description` | string | Sim | Máx. 256 caracteres | | `quantity` | string | Sim | Decimal positivo (ex.: `"2.000000"`, `"0.500000"`) | | `unit_price` | string | Sim | Decimal ≥ 0 (ex.: `"450.000000"`) | | `discount_amount` | string | Não | Decimal ≥ 0 | Os totais (`subtotal`, `total_discount`, `total`) são calculados pela API e retornados como **strings decimais** na resposta (ex.: `"2100.000000"`). Cliente embutido nas respostas [#cliente-embutido-nas-respostas] Em `GET /quotes` e `GET /quotes/{id}`, cada orçamento pode incluir um objeto `customer` com dados resumidos do cliente vinculado (`customer_id`). O shape é o **mesmo** em listagem e detalhe — igual ao de [pedidos de venda](/docs/api/guides/sales-orders) e [notas](/docs/api/guides/invoices): | Campo | Tipo | Descrição | | ------------ | ------ | -------------------------------------------------- | | `id` | UUID | Identificador do cliente | | `legal_name` | string | Razão social ou nome completo | | `trade_name` | string | Nome fantasia (pode ser `null`) | | `tax_id` | string | Documento fiscal (CPF/CNPJ/EIN…) (pode ser `null`) | ```json "customer": { "id": "8f2c1e4a-9b3d-4c7e-a1f2-6d5b8e0c3a71", "legal_name": "ACME Comércio Ltda", "trade_name": "ACME", "tax_id": "12345678000195" } ``` > **Breaking change (2026-08):** versões anteriores devolviam \~17 campos no `customer` embutido (endereço, `tax_id_type`, `metadata`, `type`, `website`, `is_blocked`, timestamps, etc.). Esses campos foram removidos do payload de orçamentos. O cadastro completo continua em [`GET /customers/{id}`](/docs/api/reference/customers/find-by-id). Ver o [changelog em versionamento](/docs/api/versioning#changelog-breaking-changes-em-v1). Criar um orçamento [#criar-um-orçamento] ```bash curl -X POST https://api.biterp.ai/quotes \ -H "Authorization: Bearer sk_xxx_yyy" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "8f2c1e4a-9b3d-4c7e-a1f2-6d5b8e0c3a71", "product_type": "goods", "quote_date": "2026-07-06", "valid_until": "2026-07-20", "salesperson_name": "Ana Souza", "discount_rate": "5.000000", "items": [ { "product_id": "3a7b9c2d-1e4f-4a6b-8c0d-2f5e7a9b1c3d", "item_description": "Cadeira ergonômica", "quantity": "2.000000", "unit_price": "450.000000" }, { "product_id": "5d8e1f3a-6b2c-4d7e-9a0f-1c3b5e7d9a2f", "item_description": "Mesa de escritório", "quantity": "1.000000", "unit_price": "1200.000000" } ] }' ``` Atualizar [#atualizar] A operação [Atualizar orçamento](/docs/api/reference/quotes/update) aceita os mesmos campos (todos opcionais). Para editar itens, envie o array `items`: o `id` de cada item é opcional e determina a reconciliação — quando informado, a linha existente é atualizada; quando ausente, uma nova linha é criada. `position` é apenas uma dica opcional de ordenação e **não** define se o item é criado ou atualizado. Converter em pedido [#converter-em-pedido] Não há endpoint de conversão. Para transformar um orçamento em venda, crie um [pedido de venda](/docs/api/guides/sales-orders) com a operação [Criar pedido](/docs/api/reference/sales-orders/create), enviando o `quote_id` — a API valida que o `product_type` do pedido casa com o do orçamento. Ver o [fluxo de vendas](/docs/api/guides/sales-flow). Buscar pelo número do orçamento [#buscar-pelo-número-do-orçamento] Além do UUID, cada orçamento recebe um `record_number` — um **inteiro sequencial por tenant**, visível para o usuário e distinto do `reference_code`. É também o critério de [ordenação padrão](/docs/api/pagination) da listagem. ```bash curl "https://api.biterp.ai/quotes/by-record-number/1042" \ -H "Authorization: Bearer sk_xxx_yyy" ``` Retorna `404` se não existir no tenant. Remover [#remover] A operação [Remover orçamento](/docs/api/reference/quotes/delete) faz **soft delete** e responde `204 No Content`. **Não há endpoint de restauração** — ver [`DELETE` é de mão única](/docs/api#delete-é-de-mão-única). Consulte a [referência OpenAPI de orçamentos](/docs/api/reference/quotes/find-all) para todos os endpoints disponíveis. # Contas a receber Uma **conta a receber** (`/receivables`) é um valor que um [cliente](/docs/api/guides/customers) deve ao tenant, vinculado a uma [conta financeira](/docs/api/guides/financial-accounts) e com data de vencimento. Este recurso é mais do que um CRUD: ele tem uma **máquina de estado** e o recebimento acontece por **alocação de transações**. Permissão exigida: `receivables` (ações `create`, `read`, `update`, `delete`). Ciclo de vida [#ciclo-de-vida] O campo `status` reflete a situação do recebível: | Status | Significado | | ----------- | -------------------------------------------------------------- | | `pending` | Criado, nada recebido ainda | | `partial` | Parcialmente recebido (há alocação, mas ainda resta valor) | | `paid` | Totalmente recebido | | `overdue` | Vencido e não quitado (marcado pelo sistema após o vencimento) | | `cancelled` | Cancelado | Um recebível nasce em `pending`. Conforme você **aloca transações** (recebe), ele recalcula o status automaticamente: se o valor restante chega a zero, vira `paid`; se ainda resta algo, vira `partial`. Remover uma alocação recalcula no sentido inverso (`paid` → `partial` → `pending`). O `cancel` leva a `cancelled`. ``` aloca (quita) aloca (parcial) pending ──────────────────► paid pending ──────────► partial ──► paid │ │ │ │ cancel │ remove alocação │ remove alocação ▼ ▼ ▼ cancelled pending partial/pending ``` > O status `overdue` **não** é definido nas suas chamadas — ele é aplicado por uma rotina do sistema quando o vencimento passa e a conta ainda está `pending`/`partial`. Você não pode alterar `status` diretamente (é um campo bloqueado no `PATCH`); mude o estado usando as ações (`allocate`, `cancel`). Criar uma conta a receber [#criar-uma-conta-a-receber] | Campo | Tipo | Obrigatório | Observações | | --------------------------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------- | | `customer_id` | UUID | Sim | Cliente devedor | | `financial_account_id` | UUID | Sim | Conta financeira (deve estar ativa) | | `amount` | string | Sim | Valor a receber (> 0) — decimal `numeric(18,6)` como string (ex.: `"1500.000000"`) | | `issue_date` | date | Sim | Data de emissão (`YYYY-MM-DD`) | | `due_date` | date | Sim | Data de vencimento | | `payment_method_id` | UUID | Não | Forma de pagamento (do país do tenant) | | `financial_category_id` | UUID | Não | Categoria financeira | | `reference_code` | string | Não | Auto-gerado (`REC-…`) se omitido; único por tenant | | `discount_amount` | string | Não | Desconto (≥ 0) — decimal como string | | `surcharge_amount` | string | Não | Acréscimo/juros (≥ 0) — decimal como string | | `origin_type` / `origin_id` | enum / UUID | Não | Origem do lançamento (`manual`, `quote`, `sale_order`, `invoice`, `contract`, `recurring`) | | `sales_order_id` | UUID | Não | Pedido de venda de origem | | `description` / `notes` | string | Não | | | `auto_generate_transaction` | boolean | Não | Em conta manual (`cash`/`digital_wallet`), gera a transação de crédito, aloca e marca como `paid` | | `metadata` | object | Não | | ```bash curl -X POST https://api.biterp.ai/receivables \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "9f1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d", "financial_account_id": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6071", "amount": "1500.000000", "issue_date": "2026-07-01", "due_date": "2026-08-01", "description": "Venda de serviços - julho/2026" }' ``` Receber: alocar uma transação [#receber-alocar-uma-transação] Receber um valor é **alocar uma [transação](/docs/api/guides/financial-transactions) de crédito** ao recebível. `POST /receivables/{id}/allocate`: | Campo | Tipo | Obrigatório | Observações | | -------------------------- | ------ | ----------- | ------------------------------------------ | | `financial_transaction_id` | UUID | Sim | Transação de **crédito** (amount positivo) | | `allocated_amount` | string | Sim | Valor a alocar (> 0) — decimal como string | | `notes` | string | Não | | Regras de validação: * O recebível não pode estar `paid` nem `cancelled`. * A transação deve pertencer à **mesma** `financial_account_id` do recebível e ser um crédito (positiva). * `allocated_amount` não pode exceder o valor restante do recebível **nem** o saldo restante da transação. O status é recalculado após a alocação, e a transação é marcada como conciliada. ```bash curl -X POST https://api.biterp.ai/receivables/{id}/allocate \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "financial_transaction_id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "allocated_amount": "500.000000", "notes": "Recebimento parcial via PIX" }' ``` Para desfazer, use `DELETE /receivables/{id}/allocate/{allocationId}` — a alocação é removida e o status recalculado (`204 No Content`). O que pode ser alterado, por estado [#o-que-pode-ser-alterado-por-estado] O `PATCH` aceita campos diferentes conforme o `status` do recebível. Depois que o dinheiro se moveu — total (`paid`) ou parcialmente (`partial`) — o título descreve um fato consumado, e só continua editável o que **não** altera esse fato: | Status do recebível | Campos aceitos no `PATCH` | | -------------------- | --------------------------------------------------------------- | | `pending`, `overdue` | Todos os do corpo do `PATCH`, sujeitos às validações do recurso | | `partial` | Somente `notes`, `financial_category_id` e `metadata` | | `paid` | Somente `notes`, `financial_category_id` e `metadata` | | `cancelled` | Nenhum — nem mesmo um corpo vazio | Um `PATCH` com algum campo não permitido para o estado atual retorna `422` **nomeando o(s) campo(s) rejeitado(s)**. E se o status mudar enquanto o `PATCH` estiver em voo — uma baixa ou um cancelamento concorrente —, a atualização é recusada com `409` em vez de ser aplicada com as regras do estado antigo: releia o registro e repita. Para alterar qualquer outro campo de um título já baixado, **desfaça, estorne ou desvincule o recebimento** (`DELETE /receivables/{id}/allocate/{allocationId}`, ou as ações de estorno). A edição total volta quando o status **recalculado** for `pending` ou `overdue` — e quem decide entre os dois é o **vencimento**, não a ausência de valor baixado: com saldo em aberto e vencimento já passado o título fica `overdue`, que mantém a edição total mesmo com baixa parcial; ainda dentro do prazo e com valor baixado remanescente — várias baixas, ou um estorno parcial —, ele fica `partial` e continua restrito. Não há beco sem saída — só a obrigação de passar pela operação que corresponde ao que de fato aconteceu. `financial_category_id` continua editável depois da baixa de propósito: a categoria contábil existe **apenas** no título (a transação financeira não tem esse campo), então corrigir uma classificação errada não teria outro caminho a não ser desfazer e refazer o recebimento. Antes, um recebível `partial` aceitava alterações em `amount`, `due_date`, `issue_date`, `financial_account_id`, `payment_method_id`, descontos e acréscimos mesmo com o recebimento já registrado — e um recebível `paid` rejeitava o `PATCH` inteiro, inclusive anotações. Agora `partial` e `paid` seguem a mesma regra: só `notes`, `financial_category_id` e `metadata`. Se a sua integração ajustava valores ou datas depois da baixa, mova o ajuste para **antes** de baixar, ou desfaça a baixa, corrija e refaça. Cancelar [#cancelar] `POST /receivables/{id}/cancel` cancela o recebível (e suas alocações, de forma atômica). É bloqueado se a conta já estiver `paid`. Parcelar [#parcelar] `POST /receivables/installments` gera várias contas a receber de uma vez, todas com o mesmo `installment_group_id`: | Campo | Tipo | Obrigatório | Observações | | ----------------------- | ------ | ----------- | ------------------------------------------------------------------- | | `customer_id` | UUID | Sim | | | `financial_account_id` | UUID | Sim | | | `total_amount` | string | Sim | Valor total (> 0), dividido entre as parcelas — decimal como string | | `installment_count` | int | Sim | Número de parcelas (2–120) — inteiro, **não** string | | `issue_date` | date | Sim | | | `first_due_date` | date | Sim | Vencimento da 1ª parcela; as demais somam 1 mês cada | | `reference_code_prefix` | string | Não | Gera `PREFIXO/1`, `PREFIXO/2`… (senão, sequência `REC-`) | | `description` / `notes` | string | Não | | O total é dividido igualmente (6 casas decimais); as sobras de arredondamento são distribuídas nas primeiras parcelas. O retorno é o array das parcelas criadas. ```bash curl -X POST https://api.biterp.ai/receivables/installments \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "9f1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d", "financial_account_id": "1a2b3c4d-5e6f-4708-9a1b-2c3d4e5f6071", "total_amount": "1200.000000", "installment_count": 3, "issue_date": "2026-07-01", "first_due_date": "2026-08-05", "reference_code_prefix": "REC-2026-PED-15" }' ``` Vencidas e resumo [#vencidas-e-resumo] * `GET /receivables/overdue` lista as contas **vencidas** — critério dinâmico: status `pending` ou `partial` **e** `due_date` no passado. Retorno paginado. * `GET /receivables/summary` retorna um agregado **por status**, cada linha com a contagem e a soma dos valores: ```json { "data": [ { "status": "pending", "total_count": 12, "total_amount": "18400.000000" }, { "status": "paid", "total_count": 30, "total_amount": "52310.500000" } ] } ``` Filtros [#filtros] Campos filtráveis em `GET /receivables`: `reference_code`, `status`, `amount`, `due_date`, `issue_date`, `customer_id`, `financial_account_id`, `financial_category_id`, `payment_method_id`, `installment_group_id`, `origin_type`, `sales_order_id`, `invoice_id`, `created_at`, `updated_at`. Ver [paginação e filtros](/docs/api/pagination). Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | ------------------------------------------- | ------------------------------------------------------------------------ | | POST | `/receivables` | [Criar](/docs/api/reference/receivables/create) | | GET | `/receivables` | [Listar](/docs/api/reference/receivables/find-all) | | POST | `/receivables/installments` | [Criar parcelas](/docs/api/reference/receivables/create-installments) | | GET | `/receivables/overdue` | [Listar vencidas](/docs/api/reference/receivables/find-overdue) | | GET | `/receivables/summary` | [Resumo por status](/docs/api/reference/receivables/get-summary) | | GET | `/receivables/{id}` | [Buscar por ID](/docs/api/reference/receivables/find-by-id) | | PATCH | `/receivables/{id}` | [Atualizar](/docs/api/reference/receivables/update) | | DELETE | `/receivables/{id}` | [Remover](/docs/api/reference/receivables/delete) | | POST | `/receivables/{id}/cancel` | [Cancelar](/docs/api/reference/receivables/cancel) | | POST | `/receivables/{id}/allocate` | [Alocar transação](/docs/api/reference/receivables/allocate-transaction) | | DELETE | `/receivables/{id}/allocate/{allocationId}` | [Remover alocação](/docs/api/reference/receivables/remove-allocation) | # Dados de referência Os **dados de referência** são catálogos **somente leitura** que alimentam os cadastros: o `bank_id` de uma [conta financeira](/docs/api/guides/financial-accounts), a `unit_of_measurement_id` de um [produto](/docs/api/guides/products), o `payment_method_id` de um [recebível](/docs/api/guides/receivables), o `city_id`/`state_id` de um [cliente](/docs/api/guides/customers) ou [fornecedor](/docs/api/guides/suppliers). São seis recursos, cada um com sua permissão (`banks`, `payment-methods`, `units-of-measurement`, `countries`, `states`, `cities`), todos na ação `read`. Todas as listagens são paginadas e aceitam `search` (ver [paginação e filtros](/docs/api/pagination)). Escopo por país do tenant [#escopo-por-país-do-tenant] A maioria dos catálogos é **escopada ao país do tenant** — você só enxerga o que faz sentido para a sua região: * **Bancos** pertencem a um único país: a lista traz os bancos ativos do país do tenant. (As buscas por `swift/{code}` e por `{id}` **não** são escopadas por país.) * **Formas de pagamento** e **unidades de medida** usam uma lista de países (`country_codes`): uma lista **vazia significa universal** (disponível em todos os países); caso contrário, o país do tenant precisa estar nela. * **Países** são um catálogo **global** — não filtrado pelo tenant. Bancos (`/banks`) [#bancos-banks] Campos principais da resposta: `id`, `code` (código nacional, ex.: `341`), `name`, `official_name`, `swift_code`, `country_code`, `logo_url`, `aliases`, `is_active`. * `GET /banks` — lista os bancos do país do tenant. * `GET /banks/code/{code}` — por código nacional (escopado ao país). * `GET /banks/swift/{code}` — por SWIFT/BIC. * `GET /banks/{id}` — por UUID. Formas de pagamento (`/payment-methods`) [#formas-de-pagamento-payment-methods] Campos: `id`, `code` (ex.: `pix`, `credit_card`), `country_codes`, `titles` (por idioma), `descriptions`, `is_active`. Uma forma universal tem `country_codes: []`. * `GET /payment-methods` — disponíveis para o país do tenant. * `GET /payment-methods/code/{code}` — por código. * `GET /payment-methods/{id}` — por UUID. Unidades de medida (`/units-of-measurement`) [#unidades-de-medida-units-of-measurement] Campos: `id`, `code` (ex.: `UN`, `KG`), `symbol`, `country_codes`, `titles`, `descriptions`, `is_active`. É a `unit_of_measurement_id` usada em [produtos](/docs/api/guides/products). * `GET /units-of-measurement` — disponíveis para o país do tenant. * `GET /units-of-measurement/code/{code}` — por código. * `GET /units-of-measurement/{id}` — por UUID. Geografia: países, estados e cidades [#geografia-países-estados-e-cidades] A geografia é hierárquica: **país → estado → cidade**. É assim que você obtém os `city_id`/`state_id` exigidos por [fornecedores](/docs/api/guides/suppliers) (e aceitos por [clientes](/docs/api/guides/customers)). ``` GET /countries → escolha country.id GET /states?country_id={id} → escolha state.id GET /cities?state_id={id} → escolha city.id ``` Países (`/countries`) [#países-countries] Catálogo global. Campos: `id`, `iso2`, `iso3`, `numeric_code`, `name_en`, `name_pt`, `name_es`, `currency`, `region`, entre outros. * `GET /countries` — lista. * `GET /countries/iso2/{code}` — por ISO2 (ex.: `BR`). * `GET /countries/iso3/{code}` — por ISO3 (ex.: `BRA`). * `GET /countries/{id}` — por ID. Estados (`/states`) [#estados-states] Campos: `id`, `country_id`, `state_code`, `country_code`, `name`. A lista e a busca aceitam um seletor de país **opcional**: `country_id`, `country_iso2` ou `country_iso3` (o alias `country_code` está depreciado). * `GET /states?country_id={id}` — estados do país. * `GET /states/search?q={texto}` — busca por nome (`q` obrigatório). * `GET /states/code/{state_code}` — por código. * `GET /states/{id}` — por ID. Cidades (`/cities`) [#cidades-cities] Campos: `id`, `country_id`, `state_id`, `country_code`, `state_code`, `name`. Cidades são navegadas por **`state_id`, que é obrigatório**. * `GET /cities?state_id={id}` — cidades do estado (`state_id` obrigatório). * `GET /cities/search?state_id={id}&q={texto}` — busca por nome no estado (`state_id` e `q` obrigatórios). * `GET /cities/{id}` — por ID. Referência dos endpoints [#referência-dos-endpoints] | Recurso | Método + Endpoint | Referência | | ------------------- | --------------------------------------- | ------------------------------------------------------------------- | | Bancos | `GET /banks` | [Listar bancos](/docs/api/reference/banks/find-all) | | Bancos | `GET /banks/code/{code}` | [Por código](/docs/api/reference/banks/find-by-code) | | Bancos | `GET /banks/swift/{code}` | [Por SWIFT](/docs/api/reference/banks/find-by-swift-code) | | Bancos | `GET /banks/{id}` | [Por ID](/docs/api/reference/banks/find-by-id) | | Formas de pagamento | `GET /payment-methods` | [Listar](/docs/api/reference/payment-methods/find-all) | | Formas de pagamento | `GET /payment-methods/code/{code}` | [Por código](/docs/api/reference/payment-methods/find-by-code) | | Formas de pagamento | `GET /payment-methods/{id}` | [Por ID](/docs/api/reference/payment-methods/find-by-id) | | Unidades | `GET /units-of-measurement` | [Listar](/docs/api/reference/units-of-measurement/find-all) | | Unidades | `GET /units-of-measurement/code/{code}` | [Por código](/docs/api/reference/units-of-measurement/find-by-code) | | Unidades | `GET /units-of-measurement/{id}` | [Por ID](/docs/api/reference/units-of-measurement/find-by-id) | | Países | `GET /countries` | [Listar](/docs/api/reference/countries/find-all) | | Países | `GET /countries/iso2/{code}` | [Por ISO2](/docs/api/reference/countries/find-by-iso2) | | Países | `GET /countries/iso3/{code}` | [Por ISO3](/docs/api/reference/countries/find-by-iso3) | | Países | `GET /countries/{id}` | [Por ID](/docs/api/reference/countries/find-by-id) | | Estados | `GET /states` | [Listar por país](/docs/api/reference/states/find-by-country) | | Estados | `GET /states/search` | [Buscar](/docs/api/reference/states/search) | | Estados | `GET /states/code/{state_code}` | [Por código](/docs/api/reference/states/find-by-code) | | Estados | `GET /states/{id}` | [Por ID](/docs/api/reference/states/find-by-id) | | Cidades | `GET /cities` | [Listar por estado](/docs/api/reference/cities/find-by-state) | | Cidades | `GET /cities/search` | [Buscar](/docs/api/reference/cities/search) | | Cidades | `GET /cities/{id}` | [Por ID](/docs/api/reference/cities/find-by-id) | # Fluxo de vendas Esta página é um **mapa** do fluxo comercial na API de Integrações. Ela costura os quatro recursos de venda e mostra como eles se ligam; cada etapa tem seu guia dedicado com os detalhes de campos. As etapas [#as-etapas] ``` Orçamento ─────► Pedido de venda ─────► Nota / fatura ─────► Contas a receber (quote) (sales-order) (invoice) (receivables) ``` 1. **[Orçamento (quote)](/docs/api/guides/quotes)** — uma proposta comercial com itens e valores, ainda sem compromisso financeiro. 2. **[Pedido de venda (sales-order)](/docs/api/guides/sales-orders)** — a venda confirmada. Pode nascer a partir de um orçamento. 3. **[Nota / fatura (invoice)](/docs/api/guides/invoices)** — o documento de faturamento gerado para o pedido. 4. **[Contas a receber (receivables)](/docs/api/guides/receivables)** — os valores a receber do cliente, com vencimentos. Nenhuma etapa é obrigatória para a próxima existir — você pode criar um pedido sem orçamento, ou uma nota sem pedido. Os vínculos são **opcionais** e servem para rastreabilidade e validação. Como os recursos se ligam [#como-os-recursos-se-ligam] O bitERP **não** tem endpoints de "converter" ou "gerar automaticamente" na API de Integrações. Os vínculos são feitos por **campos de referência** que você envia no `POST` de cada etapa, e a API valida a coerência entre eles. | De → Para | Como vincular | O que a API valida | | ----------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- | | Orçamento → Pedido | `quote_id` no `POST /sales-orders` | O orçamento existe e o `product_type` do pedido casa com o do orçamento | | Pedido → Nota | `sales_order_id` no `POST /invoices` | `customer_id`, `operation_type` e `product_type` casam com o pedido; **só 1 nota ativa por pedido** | | Item da nota → item do pedido | `sales_order_item_id` em cada item da nota | Rastreia a linha de origem | | Nota → Contas a receber | array `receivables[]` no `POST /invoices` | A **soma** dos `amount` deve igualar o total líquido da nota | > Ao vincular, os itens **não** são copiados automaticamente — você sempre envia os `items[]` da etapa atual. O `quote_id`/`sales_order_id` apenas registra a origem e dispara as validações de coerência. Gerando contas a receber [#gerando-contas-a-receber] Contas a receber **não** são criadas sozinhas ao faturar. Elas surgem quando você inclui um array `receivables[]` no `POST` do pedido de venda **ou** da nota. Nesse caso vale uma regra rígida: a **soma** de `receivables[].amount` deve bater exatamente com o total (o total líquido, no caso da nota). Se você enviar mais de um, eles viram um parcelamento (compartilham `installment_group_id`). A partir daí, o recebimento segue o ciclo de vida de [Contas a receber](/docs/api/guides/receivables): alocar transações, parcial/quitado, cancelar. Do lado das compras [#do-lado-das-compras] O espelho deste fluxo, para o que a empresa compra, é a **[nota de compra (purchase-invoice)](/docs/api/guides/purchase-invoices)**, ligada a um [fornecedor](/docs/api/guides/suppliers) e capaz de gerar **[contas a pagar](/docs/api/guides/payables)** pela mesma mecânica (array `payables[]`). A primeira etapa: propostas com itens. A venda confirmada, ligada ao orçamento. O faturamento e a geração de recebíveis. # Pedidos de venda Um **pedido de venda** (`/sales-orders`) é a venda confirmada — a etapa central do [fluxo de vendas](/docs/api/guides/sales-flow). Ele pode nascer de um [orçamento](/docs/api/guides/quotes), gerar [contas a receber](/docs/api/guides/receivables) e ser faturado por uma [nota](/docs/api/guides/invoices). Permissão exigida: `sales-orders` (ações `create`, `read`, `update`, `delete`). Ciclo de vida [#ciclo-de-vida] O `status` de um pedido é `draft`, `confirmed` ou `cancelled`. Na criação, só `draft` ou `confirmed` são aceitos — o **padrão é `confirmed`**. As transições permitidas via `PATCH`: * `draft` → `confirmed` (confirmar) * `draft` ou `confirmed` → `cancelled` (cancelar) * `confirmed` → `draft` é **bloqueado** Um pedido `cancelled` não é editável. Conceitos e campos-chave [#conceitos-e-campos-chave] | Campo | Tipo | Obrigatório | Observações | | --------------------------------------------- | ------ | ----------- | ------------------------------------------------------------------------- | | `customer_id` | UUID | Sim\* | \*Ou o objeto `customer` inline — envie **um** dos dois | | `product_type` | enum | Sim | `goods`, `service` ou `mixed` | | `order_date` | date | Sim | | | `items` | array | Sim | 1 a 200 itens | | `status` | enum | Não | `draft` ou `confirmed` (padrão `confirmed`) | | `reference_code` | string | Não | Auto-gerado (`SOR-…`) se omitido | | `operation_type` | string | Não | Tipo de operação (padrão `sale`) | | `quote_id` | UUID | Não | Orçamento de origem (`product_type` deve casar) | | `delivery_date` | date | Não | | | `salesperson_name` | string | Não | | | `discount_amount` | string | Não | Desconto em valor (≥ 0) — decimal `numeric(18,6)` como string | | `discount_rate` | string | Não | Desconto em **percentual** (0 a 100) — decimal `numeric(9,6)` como string | | `receivables` | array | Não | Gera contas a receber (ver abaixo) | | `metadata`, `br_data`, `notes`, `description` | — | Não | | Itens do pedido [#itens-do-pedido] > `quantity` e `unit_price` são **strings decimais** (ex.: `"2.000000"`, `"450.000000"`), como em todos os campos decimais da API. Cada item referencia um `product_id` (com `item_description`, `quantity` e `unit_price`) ou traz um objeto `product` inline, que reaproveita/cria o produto pelo `reference_code`. `quantity` é ≥ `"0.000001"` e `unit_price` ≥ `"0"`. Gerando contas a receber [#gerando-contas-a-receber] Inclua um array `receivables[]` para já criar os recebíveis do pedido. Cada item exige `amount` (decimal como string), `due_date` (date-time) e `financial_account_id` (ativo); opcionalmente `payment_method_id`, `financial_category_id` e `description`. A resposta traz `has_active_receivables` e o array `receivables` do pedido. Criar um pedido (a partir de um orçamento, com recebível) [#criar-um-pedido-a-partir-de-um-orçamento-com-recebível] ```bash curl -X POST https://api.biterp.ai/sales-orders \ -H "Authorization: Bearer sk_xxx_yyy" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "8f2c1e4a-9b3d-4c7e-a1f2-6d5b8e0c3a71", "quote_id": "b1d2f3a4-5c6e-4718-9a0b-2c3d4e5f6a7b", "product_type": "goods", "order_date": "2026-07-06", "status": "confirmed", "salesperson_name": "Ana Souza", "items": [ { "product_id": "3a7b9c2d-1e4f-4a6b-8c0d-2f5e7a9b1c3d", "item_description": "Cadeira ergonômica", "quantity": "2.000000", "unit_price": "450.000000" }, { "product_id": "5d8e1f3a-6b2c-4d7e-9a0f-1c3b5e7d9a2f", "item_description": "Mesa de escritório", "quantity": "1.000000", "unit_price": "1200.000000" } ], "receivables": [ { "amount": "2100.000000", "due_date": "2026-08-06T00:00:00.000Z", "financial_account_id": "9c8b7a6d-5e4f-4321-8b0a-1d2c3e4f5a6b" } ] }' ``` Atualizar [#atualizar] Na operação [Atualizar pedido](/docs/api/reference/sales-orders/update), os campos `customer_id`, `quote_id`, `receivables` e `operation_type` **não** são editáveis. Além disso, você **não pode alterar itens nem descontos quando há recebíveis ativos** vinculados ao pedido — cancele/ajuste os recebíveis primeiro. Buscar pelo número do pedido [#buscar-pelo-número-do-pedido] Além do UUID, cada pedido recebe um `record_number` — um **inteiro sequencial por tenant**, visível para o usuário e distinto do `reference_code`. É também o critério de [ordenação padrão](/docs/api/pagination) da listagem. ```bash curl "https://api.biterp.ai/sales-orders/by-record-number/2087" \ -H "Authorization: Bearer sk_xxx_yyy" ``` Um `record_number` que não seja inteiro positivo retorna `400`; um número inexistente no tenant retorna `404`. Remover [#remover] A operação [Remover pedido](/docs/api/reference/sales-orders/delete) faz **soft delete** e responde `204 No Content`. **Não há endpoint de restauração** — ver [`DELETE` é de mão única](/docs/api#delete-é-de-mão-única). Consulte a [referência OpenAPI de pedidos de venda](/docs/api/reference/sales-orders/find-all) para todos os endpoints disponíveis. # Fornecedores O recurso **Fornecedores** (`/suppliers`) mantém o cadastro de fornecedores do tenant. Ele é o **espelho de [Clientes](/docs/api/guides/customers)**: a mesma estrutura de pessoa física/jurídica, `tax_id`, contatos, dados bancários e extensões por país (`br_data`, `us_data`). Fornecedores são a ponta de origem do fluxo de compras — [notas de compra](/docs/api/guides/purchase-invoices) e [contas a pagar](/docs/api/guides/payables). Permissão exigida: `suppliers` (ações `create`, `read`, `update`, `delete`). Conceitos e campos-chave [#conceitos-e-campos-chave] A estrutura é idêntica à de clientes, com **duas diferenças** que valem atenção: 1. **`country_code` é obrigatório e deve ser ISO2** (ex.: `BR`, `US`). Em clientes ele é opcional e aceita vários formatos; aqui é obrigatório e restrito ao código de duas letras. 2. **Sem resolução de cidade/estado por texto.** Fornecedores aceitam apenas `city_id` e `state_id` (IDs do catálogo, obtidos via [Dados de referência](/docs/api/guides/reference-data)) — não há campos `city`/`state` de texto livre. | Campo | Tipo | Obrigatório | Observações | | ---------------- | --------- | ----------- | ------------------------------------------------------------------------- | | `type` | enum | Sim | `individual` ou `company` | | `tax_id` | string | Sim | Documento fiscal; único por tenant; normalizado (sem validação de dígito) | | `legal_name` | string | Sim | Razão social / nome completo | | `country_code` | string | Sim | **ISO2** (`BR`, `US`) | | `tax_id_type` | enum | Não | `CPF`, `CNPJ`, `EIN` ou `SSN` | | `trade_name` | string | Não | Nome fantasia | | `reference_code` | string | Não | Auto-gerado (`SPL-…`) se omitido; único por tenant | | `emails` | string\[] | Não | | | `phones` | string\[] | Não | | | `address_line1` | string | Não | Endereço (linha 1) | | `city_id` | int | Não | ID da cidade no catálogo | | `state_id` | int | Não | ID do estado no catálogo | | `postal_code` | string | Não | | | `tags` | string\[] | Não | | | `is_active` | boolean | Não | Padrão `true` | | `metadata` | object | Não | | | `br_data` | object | Não | `ie`, `ie_exempt`, `cnae_primary`, `pix_key`… | | `us_data` | object | Não | `w9_on_file`, `naics_code`, `routing_number`… | O `tax_id` segue a mesma regra de [Clientes](/docs/api/guides/customers): normalizado (pontuação ignorada) e sem validação de dígitos verificadores. Na resposta, o país vem como `country_code` (string), e `city`/`state` como objetos resolvidos. Criar um fornecedor [#criar-um-fornecedor] ```bash curl -X POST https://api.biterp.ai/suppliers \ -H "Authorization: Bearer sk_abc123_secretXYZ" \ -H "Content-Type: application/json" \ -d '{ "type": "company", "tax_id": "98.765.432/0001-10", "tax_id_type": "CNPJ", "legal_name": "Fornecedora Industrial S.A.", "trade_name": "FornInd", "country_code": "BR", "emails": ["compras@fornind.com.br"], "address_line1": "Rua da Indústria, 500", "postal_code": "04500-000", "state_id": 26, "city_id": 5270, "br_data": { "ie": "111222333444", "cnae_primary": "4671100", "pix_key": "98765432000110", "pix_key_type": "cnpj" } }' ``` > `state_id` e `city_id` devem ser IDs reais do catálogo bitERP. Obtenha-os pelos endpoints de estados e cidades em [Dados de referência](/docs/api/guides/reference-data). Buscar fornecedores [#buscar-fornecedores] Além da listagem paginada (`GET /suppliers`, com [filtros e paginação](/docs/api/pagination)): | Endpoint | Uso | | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `GET /suppliers/search` | Busca full-text por `legal_name`, `trade_name` e `tax_id`. Aceita `q`, `limit` (padrão 50, 1–100) e `activeOnly`. | | `GET /suppliers/tax-id/{taxId}` | Localiza um fornecedor pelo `tax_id` (normalizado). `404` se não existir. | | `GET /suppliers/count` | Contagem total: `{ "count": 42 }`. | | `GET /suppliers/{id}` | Busca por UUID. | Campos filtráveis em `GET /suppliers`: `legal_name`, `trade_name`, `tax_id`, `type`, `city`, `state`, `country_code`, `is_active`, `reference_code`, `tags`, `created_at`, `updated_at`. Ordenação por `id`, `legal_name`, `trade_name`, `reference_code`, `created_at`, `updated_at`. Remover [#remover] `DELETE /suppliers/{id}` faz **soft delete** e responde `204 No Content`. Referência dos endpoints [#referência-dos-endpoints] | Método | Endpoint | Referência | | ------ | --------------------------- | ------------------------------------------------------------------ | | POST | `/suppliers` | [Criar fornecedor](/docs/api/reference/suppliers/create) | | GET | `/suppliers` | [Listar fornecedores](/docs/api/reference/suppliers/find-all) | | GET | `/suppliers/search` | [Buscar por texto](/docs/api/reference/suppliers/search) | | GET | `/suppliers/tax-id/{taxId}` | [Buscar por tax\_id](/docs/api/reference/suppliers/find-by-tax-id) | | GET | `/suppliers/count` | [Contar fornecedores](/docs/api/reference/suppliers/count) | | GET | `/suppliers/{id}` | [Buscar por ID](/docs/api/reference/suppliers/find-by-id) | | PATCH | `/suppliers/{id}` | [Atualizar fornecedor](/docs/api/reference/suppliers/update) | | DELETE | `/suppliers/{id}` | [Remover fornecedor](/docs/api/reference/suppliers/delete) | # Entrega e retentativas Cada evento assinado vira uma **entrega** (*delivery*): uma linha com histórico próprio, visível no painel em **Configurações > WebHooks**, na aba **Histórico** do endpoint. Ciclo de vida [#ciclo-de-vida] | Status | O que significa | | --------------- | ----------------------------------------------------- | | **Pendente** | A entrega foi criada e aguarda o primeiro envio | | **Processando** | O envio está em curso | | **Entregue** | O seu servidor respondeu `2xx` | | **Falhou** | A tentativa não deu certo e há uma nova agendada | | **Esgotado** | As tentativas acabaram. Não haverá reenvio automático | O detalhe de cada entrega registra as tentativas feitas, o status HTTP e o corpo da sua resposta (primeiros 1.024 caracteres), o tempo de resposta e a mensagem de erro quando houve falha de conexão. Idempotência é obrigatória [#idempotência-é-obrigatória] O bitERP garante entrega **pelo menos uma vez**, não exatamente uma vez. Uma resposta `2xx` que se perca na rede, um timeout do seu lado que na verdade processou, um reenvio manual — todos produzem uma segunda chegada do mesmo evento. Use o **`id` do payload** (idêntico ao header `x-biterp-webhook-id`) como chave de deduplicação. Ele é estável entre todas as tentativas do mesmo evento, inclusive nos reenvios manuais. ```js // Guarde o id antes de processar; ignore o que já viu const known = await db.webhookEvents.findUnique({ where: { id: event.id } }) if (known) return res.status(200).send("duplicate") await db.webhookEvents.create({ data: { id: event.id, type: event.event_type } }) ``` A ordem não é garantida [#a-ordem-não-é-garantida] Entregas são independentes e podem ser processadas em paralelo, com retentativas em momentos diferentes. Um `sales-orders.update` que falhou e voltou duas horas depois chega **depois** de um `update` mais recente do mesmo pedido. A consequência prática: **não reconstrua estado a partir da sequência de eventos**. Trate cada evento como um convite a buscar o estado atual via [API de Integrações](/docs/api/webhooks/events#do-evento-ao-dado-completo) — o `GET` sempre devolve a versão mais recente, o que torna a ordem de chegada irrelevante. Se você precisa descartar processamento obsoleto, compare o `updated_at` do recurso com o que já gravou, não o `timestamp` do evento. Retentativas e backoff [#retentativas-e-backoff] Toda tentativa que não resulte em `2xx` dentro de 10 segundos agenda a próxima, com espera exponencial a partir de 1 minuto e teto de 6 horas: | Tentativa | Espera até a próxima | | ------------ | -------------------- | | 1 | 1 minuto | | 2 | 2 minutos | | 3 | 4 minutos | | 4 | 8 minutos | | 5 | 16 minutos | | 6 | 32 minutos | | 7 | 1 hora e 4 minutos | | 8 | 2 horas e 8 minutos | | 9 | 4 horas e 16 minutos | | 10 em diante | 6 horas (teto) | São **20 tentativas** no total, o que estende a entrega por cerca de **2 dias e 20 horas**. Depois disso a entrega fica **Esgotada** e só volta a ser enviada por reenvio manual. Uma indisponibilidade curta do seu servidor é absorvida sem perda. Uma queda mais longa que essa janela exige ação no painel. Desativação automática (circuit breaker) [#desativação-automática-circuit-breaker] Falhas consecutivas em um endpoint — de qualquer entrega — incrementam um contador. Qualquer entrega bem-sucedida zera esse contador. | Falhas consecutivas | O que acontece | | ------------------- | ---------------------------------------------------------------------------------- | | 10 | Os administradores da empresa recebem um **aviso** no painel, uma vez por episódio | | 20 | O endpoint é **desativado automaticamente** e os administradores são notificados | Enquanto o endpoint está desativado, **nenhuma entrega nova é criada** — os eventos daquele período não ficam em fila esperando. É por isso que o aviso aos 10 existe: ele dá margem para corrigir antes do desligamento. Ao reativar o endpoint no painel, o bitERP executa um **backfill** da janela em que ele esteve fora e gera as entregas que faltaram. A recuperação alcança até 90 dias, limitada pela retenção da trilha de auditoria, e processa um volume máximo por execução — em janelas longas ou de muito movimento, parte dos eventos pode não ser recuperada. Reative o quanto antes. Reenviar manualmente [#reenviar-manualmente] Na aba **Histórico** do endpoint, entregas com status **Falhou** ou **Esgotado** têm a ação **Reenviar**. Ela devolve a entrega ao início do ciclo: o contador de tentativas volta a zero e o envio é refeito imediatamente. O `id` do evento **não muda** no reenvio — a sua deduplicação continua funcionando, e um evento já processado com sucesso do seu lado será corretamente ignorado. Retenção do histórico [#retenção-do-histórico] Entregas são apagadas após **30 dias**, independentemente do status. O histórico serve para diagnóstico recente, não como registro permanente: se a sua integração precisa de trilha de auditoria, guarde os eventos do seu lado ao recebê-los. Diagnóstico [#diagnóstico] | Sintoma | Onde olhar | Ação | | -------------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------ | | Nenhuma entrega aparece no histórico | Status do endpoint | Se estiver desativado, reative — o backfill recupera a janela | | Nenhuma entrega para um recurso específico | Lista de eventos assinados | O evento pode não estar na assinatura, ou o recurso pode não ser assinável | | Entregas param de repente em horários de pico | Volume por hora | O teto de 1.000 entregas por hora foi atingido; reduza os eventos assinados | | Todas as entregas com status `401` | Sua verificação de assinatura | Ver [erros comuns](/docs/api/webhooks/receiving#erros-comuns) | | Entregas com timeout e tempo de resposta próximo de 10 s | Seu handler | Responda `2xx` antes de processar | | Entregas com erro de conexão e sem status HTTP | DNS e certificado do endpoint | Confirme que a URL resolve para um IP público e que o certificado TLS é válido | | Eventos duplicados no seu sistema | Sua deduplicação | Use o `id` do evento, não o `resource.id` | Quando abrir um chamado de suporte, informe o **`id` do evento** (`x-biterp-webhook-id`): ele identifica a entrega exata no histórico. # Eventos Todo evento tem o formato `.` — por exemplo `sales-orders.update` ou `customers.create`. Você assina os pares que interessam à sua integração; cada evento assinado gera uma entrega independente. As quatro ações [#as-quatro-ações] | Ação | Quando é disparada | | --------- | ---------------------------------------------------------------------------------------------------------- | | `create` | O registro foi criado | | `update` | Qualquer campo do registro mudou — incluindo mudanças de status, baixas, alocações e autorizações fiscais | | `delete` | O registro foi removido (exclusão lógica — ver [`DELETE` é de mão única](/docs/api#delete-é-de-mão-única)) | | `restore` | Um registro removido foi restaurado | A API de Integrações não expõe restauração em nenhum recurso — desfazer uma exclusão é uma operação de usuário. Assine `restore` se a sua integração precisa reagir a esse desfazer feito por alguém dentro do bitERP. Recursos assináveis [#recursos-assináveis] Cada recurso abaixo aceita as quatro ações. A coluna **Hidratação** indica se existe `GET //:id` na API de Integrações para buscar o estado completo depois de receber o evento. Cadastros [#cadastros] | Recurso | Eventos | Hidratação | | ----------- | ---------------------------------------------------- | ---------------------------------------------------------------- | | `products` | `products.create`, `.update`, `.delete`, `.restore` | [`GET /products/:id`](/docs/api/reference/products/find-by-id) | | `customers` | `customers.create`, `.update`, `.delete`, `.restore` | [`GET /customers/:id`](/docs/api/reference/customers/find-by-id) | | `suppliers` | `suppliers.create`, `.update`, `.delete`, `.restore` | [`GET /suppliers/:id`](/docs/api/reference/suppliers/find-by-id) | Financeiro [#financeiro] | Recurso | Eventos | Hidratação | | ------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | `financial-accounts` | `financial-accounts.create`, `.update`, `.delete`, `.restore` | [`GET /financial-accounts/:id`](/docs/api/reference/financial-accounts/find-by-id) | | `financial-categories` | `financial-categories.create`, `.update`, `.delete`, `.restore` | [`GET /financial-categories/:id`](/docs/api/reference/financial-categories/find-by-id) | | `financial-transactions` | `financial-transactions.create`, `.update`, `.delete`, `.restore` | [`GET /financial-transactions/:id`](/docs/api/reference/financial-transactions/find-by-id) | | `receivables` | `receivables.create`, `.update`, `.delete`, `.restore` | [`GET /receivables/:id`](/docs/api/reference/receivables/find-by-id) | | `payables` | `payables.create`, `.update`, `.delete`, `.restore` | [`GET /payables/:id`](/docs/api/reference/payables/find-by-id) | Vendas e faturamento [#vendas-e-faturamento] | Recurso | Eventos | Hidratação | | -------------- | ------------------------------------------------------- | ---------------------------------------------------------------------- | | `quotes` | `quotes.create`, `.update`, `.delete`, `.restore` | [`GET /quotes/:id`](/docs/api/reference/quotes/find-by-id) | | `sales-orders` | `sales-orders.create`, `.update`, `.delete`, `.restore` | [`GET /sales-orders/:id`](/docs/api/reference/sales-orders/find-by-id) | | `invoices` | `invoices.create`, `.update`, `.delete`, `.restore` | [`GET /invoices/:id`](/docs/api/reference/invoices/find-by-id) | Compras [#compras] | Recurso | Eventos | Hidratação | | ------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------- | | `purchase-invoices` | `purchase-invoices.create`, `.update`, `.delete`, `.restore` | [`GET /purchase-invoices/:id`](/docs/api/reference/purchase-invoices/find-by-id) | Sem endpoint público de hidratação [#sem-endpoint-público-de-hidratação] Estes recursos geram eventos, mas não têm `GET //:id` na API de Integrações. Você recebe a notificação de que algo mudou, sem uma URL pública para buscar os dados. | Recurso | Eventos | | ------------------------- | ------------------------------------------------------------------ | | `fiscal-rules-br` | `fiscal-rules-br.create`, `.update`, `.delete`, `.restore` | | `subscription-plans` | `subscription-plans.create`, `.update`, `.delete`, `.restore` | | `customer-subscriptions` | `customer-subscriptions.create`, `.update`, `.delete`, `.restore` | | `support-tickets` | `support-tickets.create`, `.update`, `.delete`, `.restore` | | `email-dispatches` | `email-dispatches.create`, `.update`, `.delete`, `.restore` | | `tenant-email-identities` | `tenant-email-identities.create`, `.update`, `.delete`, `.restore` | Mudanças em itens contam como `update` do pai [#mudanças-em-itens-contam-como-update-do-pai] Itens e dados específicos do país não têm eventos próprios — eles fazem parte do recurso a que pertencem: * Adicionar, alterar ou remover um item de um pedido de venda gera **`sales-orders.update`**, com o `id` do pedido. * O mesmo vale para itens de orçamento (`quotes.update`), tributos de regra fiscal (`fiscal-rules-br.update`) e mensagens ou anexos de chamado (`support-tickets.update`). * Alterar os dados fiscais brasileiros de um produto gera **`products.update`**, com o `id` do produto — não um evento separado. Na prática: **uma operação de negócio produz um evento por endpoint assinante**, apontando sempre para o registro principal. Você não recebe uma enxurrada de eventos ao salvar um pedido com dez itens. O que não gera evento [#o-que-não-gera-evento] * **Recursos administrativos**: usuários e permissões, API Keys, configurações da empresa, instalações OAuth, integrações nativas e [mapeamento de entidades](/docs/api/guides/integration-entity-mappings) não são assináveis. * **Dados de referência**: bancos, formas de pagamento, unidades de medida, países, estados e cidades são [catálogos somente leitura](/docs/api/guides/reference-data) e não produzem eventos. * **Leituras**: consultas via API, MCP ou painel nunca disparam webhooks — só escritas. * **A própria configuração de webhooks**: alterar endpoints ou entregas não gera evento, o que evita laços de realimentação. Do evento ao dado completo [#do-evento-ao-dado-completo] O payload identifica o recurso, não o seu conteúdo. Para os recursos com hidratação, o caminho é: ```http GET /sales-orders/550e8400-e29b-41d4-a716-446655440000 Authorization: Bearer sk_... ``` Para eventos `create`, `update` e `restore`, o `GET` simples basta. Quando a entrega do evento `delete` chega, o registro já saiu das consultas padrão — um `GET` comum devolve `404`. Busque com `?with_deleted=true` para receber o registro com o campo `deleted_at` preenchido: ```http GET /sales-orders/550e8400-e29b-41d4-a716-446655440000?with_deleted=true ``` Se o recurso não tem endpoint público, guarde o evento e trate a notificação como um sinal (por exemplo, para avisar uma pessoa) — não há URL alternativa para buscar o conteúdo. # Webhooks Webhooks invertem o sentido da integração: em vez de a sua aplicação perguntar à API se algo mudou, o **bitERP avisa você** assim que a mudança acontece. Você registra uma URL HTTPS, escolhe os eventos que interessam e passa a receber um `POST` assinado a cada ocorrência. É a alternativa recomendada ao *polling*. Uma rotina que chama `GET /sales-orders` de minuto em minuto gasta requisições, atrasa a reação e ainda perde alterações que aconteceram e foram desfeitas entre duas leituras. Com webhooks você reage ao fato, não à varredura. O cadastro de endpoints é feito no **painel do bitERP**, por um administrador da empresa. A API de Integrações não expõe endpoints de webhook — ela é usada depois, para buscar o estado completo do recurso que mudou. Como funciona [#como-funciona] 1. Alguém altera um recurso no bitERP — pelo painel, pela API de Integrações, pelo MCP ou por uma automação interna. 2. O bitERP identifica que a mudança corresponde a um evento assinado pelo seu endpoint. 3. Uma **entrega** (*delivery*) é criada e enviada como `POST` para a sua URL, com assinatura HMAC. 4. O seu servidor responde `2xx`. Se não responder, o bitERP **repete a entrega** com intervalos crescentes. 5. O payload identifica **o que** mudou, não o estado do recurso — você busca os dados atuais via [API de Integrações](/docs/api). Configurar um endpoint [#configurar-um-endpoint] Os recursos `webhook-endpoints` e `webhook-deliveries` são restritos a administradores da empresa. Usuários comuns e API Keys não acessam essa configuração. 1. Acesse o painel do bitERP em [app.biterp.ai](https://app.biterp.ai). 2. Navegue até **Configurações > WebHooks**. 3. Clique em **Novo Webhook de saída**. 4. Informe um **nome** (identificação interna) e a **URL** que vai receber os `POST`. 5. Selecione os **eventos** que deseja assinar — ver [Eventos](/docs/api/webhooks/events). 6. Salve. O **signing secret** é exibido **uma única vez**: copie e guarde em local seguro. O secret tem o formato `whsec_` seguido de 64 caracteres hexadecimais e é a chave usada para [verificar a assinatura](/docs/api/webhooks/receiving) de cada entrega. Se você perdê-lo, é possível gerar um novo pelo painel, em **Trocar segredo** — o secret anterior deixa de valer imediatamente, então atualize a sua aplicação logo em seguida. Requisitos da URL [#requisitos-da-url] | Regra | Detalhe | | -------------------- | ----------------------------------------------------------------------------------------- | | Protocolo | `https` obrigatório. `http` só é aceito fora de produção | | Endereço | Precisa resolver para um IP **público** e roteável | | Endereços bloqueados | `localhost`, faixas privadas (RFC 1918), link-local, CGNAT, multicast e equivalentes IPv6 | | Unicidade | A mesma URL não pode ser cadastrada duas vezes na mesma empresa | | Tamanho | Até 2.048 caracteres | A validação acontece no cadastro **e de novo antes de cada entrega**. Um domínio que passe a apontar para um endereço interno depois do cadastro tem as entregas recusadas — o que conta como falha e alimenta o [circuit breaker](/docs/api/webhooks/delivery). Para testar localmente, exponha a sua máquina com um túnel HTTPS público (ngrok, Cloudflare Tunnel, localtunnel). Apontar o endpoint direto para `localhost` ou para um IP da sua rede interna é recusado. Testar a conexão [#testar-a-conexão] Na tela do endpoint, a ação **Testar conexão** envia imediatamente um `POST` assinado com o evento `webhook-endpoints.test` e mostra o status HTTP, o corpo da resposta e o tempo de resposta. É a forma mais rápida de validar que a URL está acessível e que a sua verificação de assinatura funciona. O teste é **síncrono e avulso**: não gera uma entrega, não aparece no histórico e não conta para as retentativas nem para o circuit breaker. Status do endpoint [#status-do-endpoint] | Status | O que significa | | -------------------------------- | -------------------------------------------------------------------------------------------------------------- | | **Ativo** | Recebendo entregas normalmente | | **Desativado (manual)** | Desligado por um administrador; nenhum evento novo é enfileirado | | **Desativado (circuit breaker)** | Desligado automaticamente após falhas consecutivas — ver [Entrega e retentativas](/docs/api/webhooks/delivery) | Ao reativar um endpoint que estava desativado, o bitERP faz um **backfill automático** da janela em que ele esteve fora, gerando as entregas que não chegaram a existir. Você recebe os eventos atrasados sem precisar de nenhuma ação. Limites e garantias [#limites-e-garantias] | Aspecto | Comportamento | | ----------------------- | ----------------------------------------------------------------------------- | | Latência típica | Menos de um minuto após a operação | | Timeout por tentativa | 10 segundos | | Tentativas por evento | Até 20, com backoff exponencial | | Janela total de entrega | Cerca de 2 dias e 20 horas | | Entregas por empresa | Até 1.000 por hora | | Histórico de entregas | 30 dias | | Ordem de entrega | **Não garantida** — ver [Entrega e retentativas](/docs/api/webhooks/delivery) | | Entrega duplicada | Possível — trate o consumo de forma idempotente | Ao ultrapassar 1.000 entregas em uma hora, os eventos seguintes da empresa **não são enfileirados** — eles não chegam com atraso, simplesmente não chegam. Assine apenas os eventos que a sua integração realmente consome, especialmente em recursos de alto volume como `financial-transactions`. Próximos passos [#próximos-passos] O catálogo completo do que você pode assinar e o que dispara cada evento. Headers, payload, verificação da assinatura HMAC e como responder. Idempotência, ordem, backoff, circuit breaker e diagnóstico. Busque o estado completo do recurso que mudou. # Receber e verificar Cada entrega chega como um `POST application/json` assinado. Antes de processar qualquer coisa, **verifique a assinatura** — sem isso, qualquer pessoa que descubra a sua URL pode enviar eventos falsos. Anatomia da requisição [#anatomia-da-requisição] | Header | Exemplo | Para que serve | | ---------------------------- | --------------------- | ------------------------------------------------------------------------------------------ | | `content-type` | `application/json` | O corpo é sempre JSON | | `user-agent` | `bitERP-Webhooks/1.0` | Identifica a origem (não use como autenticação) | | `x-biterp-webhook-id` | `018f...:9c2b...` | Identificador único do evento — a chave da sua [idempotência](/docs/api/webhooks/delivery) | | `x-biterp-webhook-timestamp` | `1774612345` | Momento da assinatura, em segundos desde a época Unix | | `x-biterp-signature` | `sha256=9a1f...` | HMAC-SHA256 em hexadecimal, prefixado por `sha256=` | O corpo identifica o que mudou: ```json { "id": "018f3c1e-7b2a-7c31-9d44-2f1a0b8e5c77:9c2b6a10-5e4d-4f38-b0c1-7a9d2e3f4b56", "event_type": "sales-orders.update", "timestamp": "2026-05-24T12:34:56.000Z", "tenant_id": "550e8400-e29b-41d4-a716-446655440000", "resource": { "type": "sales-orders", "id": "7f3d9c22-8a61-4e0b-9f52-3c8d1a7e6b04" } } ``` | Campo | Descrição | | --------------- | ------------------------------------------------------------------------------------------- | | `id` | Mesmo valor do header `x-biterp-webhook-id`. Estável entre as retentativas do mesmo evento | | `event_type` | O evento assinado, no formato `.` | | `timestamp` | Momento em que **esta tentativa** foi montada, em ISO 8601 UTC. Muda a cada retentativa | | `tenant_id` | A empresa em que a mudança ocorreu — útil se o seu sistema atende várias | | `resource.type` | O recurso, igual ao prefixo do `event_type` | | `resource.id` | O `id` a usar na [hidratação via API](/docs/api/webhooks/events#do-evento-ao-dado-completo) | Ele diz **o que** mudou, não **como** ficou. É uma decisão de projeto: o estado no momento da entrega pode já estar desatualizado, e um payload magro não vaza dados de negócio para um endpoint que porventura tenha sido comprometido. Busque o registro pela API quando precisar dos campos. O evento de teste disparado pelo painel segue o mesmo formato, com `event_type` igual a `webhook-endpoints.test` e `id` no formato `test_` seguido de um UUID. Verificar a assinatura [#verificar-a-assinatura] A assinatura é calculada assim: ``` assinatura = HMAC_SHA256(signing_secret, "{timestamp}.{corpo_bruto}") header = "sha256=" + hexadecimal(assinatura) ``` Três detalhes que costumam quebrar a verificação: 1. **Use o corpo bruto**, exatamente como chegou. Se o seu framework já transformou o JSON em objeto e você o serializa de novo, a menor diferença de espaço ou de ordem de chaves invalida o cálculo. 2. **A chave é o secret inteiro**, incluindo o prefixo `whsec_`. Não remova nada. 3. **O separador é um ponto** entre o timestamp e o corpo — `1774612345.{"id":...}`. Compare com uma função de tempo constante (`crypto.timingSafeEqual`, `hmac.compare_digest`) e **rejeite timestamps antigos** — uma tolerância de cinco minutos barra a repetição de uma requisição capturada. Node.js (Express) [#nodejs-express] ```js import crypto from "node:crypto" import express from "express" const app = express() const SECRET = process.env.BITERP_WEBHOOK_SECRET // whsec_... const TOLERANCE_SECONDS = 300 function isValid(rawBody, timestamp, signature) { if (!timestamp || !signature) return false const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) if (!Number.isFinite(age) || age > TOLERANCE_SECONDS) return false const digest = crypto .createHmac("sha256", SECRET) .update(`${timestamp}.${rawBody}`) .digest("hex") const expected = Buffer.from(`sha256=${digest}`) const received = Buffer.from(signature) return ( expected.length === received.length && crypto.timingSafeEqual(expected, received) ) } // express.raw preserva o corpo original — express.json() o descartaria app.post("/webhooks/biterp", express.raw({ type: "application/json" }), (req, res) => { const rawBody = req.body.toString("utf8") if ( !isValid( rawBody, req.get("x-biterp-webhook-timestamp"), req.get("x-biterp-signature") ) ) { return res.status(401).send("invalid signature") } const event = JSON.parse(rawBody) enqueue(event) // processe fora do ciclo da requisição res.status(200).send("ok") }) ``` Python (Flask) [#python-flask] ```python import hashlib import hmac import os import time from flask import Flask, abort, request app = Flask(__name__) SECRET = os.environ["BITERP_WEBHOOK_SECRET"].encode() # whsec_... TOLERANCE_SECONDS = 300 @app.post("/webhooks/biterp") def receive(): timestamp = request.headers.get("x-biterp-webhook-timestamp", "") signature = request.headers.get("x-biterp-signature", "") raw_body = request.get_data() # bytes, sem reserializar try: age = abs(int(time.time()) - int(timestamp)) except ValueError: abort(401) if age > TOLERANCE_SECONDS: abort(401) digest = hmac.new(SECRET, f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest() if not hmac.compare_digest(f"sha256={digest}", signature): abort(401) enqueue(request.get_json()) # processe fora do ciclo da requisição return "ok", 200 ``` Como responder [#como-responder] | Regra | Detalhe | | ------------------- | ------------------------------------------------------------------------------------------------------- | | Sucesso | Qualquer status `2xx`. O corpo é irrelevante | | Falha | Qualquer outro status, timeout ou erro de conexão agenda uma [retentativa](/docs/api/webhooks/delivery) | | Prazo | **10 segundos** por tentativa. Depois disso a conexão é abortada e a entrega conta como falha | | Assinatura inválida | Responda `401`. Isso sinaliza um problema real de configuração, e o histórico no painel mostra o status | **Responda antes de processar.** Grave o evento numa fila ou tabela, devolva `2xx` e faça o trabalho pesado depois. Chamar a API do bitERP, gerar relatórios ou atualizar sistemas de terceiros dentro do handler é o caminho mais curto para estourar os 10 segundos e transformar entregas boas em retentativas. Os primeiros 1.024 caracteres da sua resposta ficam registrados no histórico de entregas — útil para diagnosticar, então devolva uma mensagem de erro legível quando algo der errado do seu lado. Erros comuns [#erros-comuns] | Sintoma | Causa provável | | --------------------- | ---------------------------------------------------------------------------------------------------- | | Assinatura nunca bate | O corpo foi reserializado (`express.json()`, `body-parser`) em vez de usar o bruto | | Assinatura nunca bate | O prefixo `whsec_` foi removido do secret | | Assinatura nunca bate | O secret foi rotacionado no painel e a aplicação continua com o anterior | | Funcionava e parou | Proxy ou CDN à frente do endpoint alterando o corpo, ou removendo os headers `x-biterp-*` | | Entregas com timeout | Processamento síncrono dentro do handler | | Nenhuma entrega chega | Endpoint desativado pelo circuit breaker — ver [Entrega e retentativas](/docs/api/webhooks/delivery) | # List banks available for the authenticated tenant's country {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get bank by national code (for tenant country) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get bank by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get bank by SWIFT/BIC code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get city by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List cities by state ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Search cities by name within a state {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List all active countries {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get country by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get country by ISO2 code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get country by ISO3 code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Activate customer subscription {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Cancel customer subscription {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create customer subscription {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Delete customer subscription {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List customer subscriptions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List customer subscription billing cycles {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get customer subscription by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get customer subscription by record number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List customer subscription events {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get next suggested customer subscription reference code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Check customer subscription reference code availability {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Pause customer subscription {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Resume customer subscription {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Update customer subscription {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Contar clientes {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar cliente {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover cliente {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar clientes {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar cliente por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar cliente por tax_id {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar clientes por texto {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar cliente {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar conta financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover conta financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar contas financeiras {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar conta financeira por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Calcular saldo da conta financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar conta financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar categoria financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover categoria financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar categorias financeiras {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar categoria financeira por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar arvore de categorias financeiras {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar categoria financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover transacao financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar transacoes financeiras {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar transacoes de uma conta (extrato) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar transacao financeira por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Calcular saldo da conta financeira {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Health check da API integrations {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar mapeamento manual {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover mapeamento (soft delete) {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar mapeamentos de entidade da integracao {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar mapeamento por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar mapeamento {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create invoice {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Delete invoice {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List invoices {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get invoice by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Update invoice {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # OAuth token endpoint {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Alocar transacao em conta a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Cancelar conta a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar parcelas de contas a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar conta a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover conta a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar contas a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar conta a pagar por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar contas a pagar vencidas {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Resumo agregado por status {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover alocacao da conta a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar conta a pagar {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List payment methods available for tenant country {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get payment method by code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get payment method by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar produto {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover produto {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar produtos {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar produto por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar produto por reference_code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar produtos por texto {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar produto {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create purchase invoice {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Delete purchase invoice {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Finalize purchase invoice and materialize payables {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List purchase invoices {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get purchase invoice by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Update purchase invoice {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create quote {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Delete quote {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List quotes {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get quote by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get quote by record number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Update quote {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Alocar transacao em conta a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Cancelar conta a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar parcelas de contas a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar conta a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover conta a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar contas a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar conta a receber por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar contas a receber vencidas {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Resumo agregado por status {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover alocacao da conta a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar conta a receber {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create sales order {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Delete sales order {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List sales orders {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get sales order by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get sales order by record number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Update sales order {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get state by code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List states by country filter {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get state by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Search states by name and country filter {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Create subscription plan {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Delete subscription plan {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List subscription plans {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get subscription plan by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get subscription plan by record number {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get next suggested subscription plan reference code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Check subscription plan reference code availability {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Update subscription plan {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Contar fornecedores {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Criar fornecedor {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Remover fornecedor {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Listar fornecedores {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar fornecedor por ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar fornecedor por tax_id {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Buscar fornecedores por texto {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Atualizar fornecedor {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Obter tenant atual {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List units of measurement available for tenant country {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get unit of measurement by code {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get unit of measurement by ID {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}