Manual de Utilização — Protheus REST API
API REST sobre o ERP Protheus/TOTVS · Node.js + Express + TypeScript
1. Introdução e Visão Geral
A Protheus REST API é uma solução desenvolvida em Node.js + Express + TypeScript que expõe qualquer tabela do ERP Protheus/TOTVS como um endpoint REST completo — sem necessidade de código adicional para cada entidade do banco de dados.
A API conecta-se diretamente ao banco de dados do Protheus (SQL Server ou MySQL), interpreta as requisições
HTTP e gera as queries SQL correspondentes de forma automática, respeitando as convenções do Protheus como
o soft-delete via coluna D_E_L_E_T_ e a chave primária R_E_C_N_O_.
Características principais
- Exposição automática de qualquer tabela Protheus via REST
- Suporte a MSSQL (SQL Server) e MySQL
- Operações CRUD individuais e em lote (bulk)
- Operações bulk atômicas com rollback automático em caso de falha
- Soft-delete nativo compatível com o padrão Protheus
- Múltiplos ambientes de banco de dados configuráveis
- Sistema completo de filtros, ordenação, paginação e relacionamentos
- Autenticação JWT via header HTTP
- Controle de acesso por endereço IP
2. Pré-requisitos e Instalação
2.1 Pré-requisitos
| Componente | Versão mínima | Observação |
|---|---|---|
| Node.js | v14 LTS | Recomendado v16 ou superior |
| npm | v6 | Incluído com Node.js |
| SQL Server | 2012+ | Para plataforma mssql |
| MySQL | 5.7+ | Para plataforma mysql |
| TypeScript | v4.3 | Instalado via devDependencies |
2.2 Instalação
# 1. Instale as dependências npm install # 2. Compile o TypeScript para a pasta server/ npm run build # 3. Inicie o servidor npm run serve
2.3 Scripts npm
| Script | Comando | Descrição |
|---|---|---|
build | tsc | Compila TypeScript → server/ |
dev / serve | nodemon server | Inicia com hot-reload |
watch | tsc -w | Compila em modo watch (sem iniciar) |
appConfigFilePath no package.json para apontar para
config/protheus-rest-api.config.jsonc. Por padrão ele aponta para o arquivo de teste.
2.4 Estrutura de diretórios
protheus-rest-api/ ├── config/ │ ├── protheus-rest-api.config.jsonc # Config de produção │ └── protheus-rest-api.config-test.jsonc # Config de teste ├── rdbconfig.json # Config da biblioteca rdb-server ├── src/ # Fonte TypeScript │ ├── index.ts │ └── libs/protheus-sql-server/ │ └── libs/rdb-server/ │ ├── controller.js │ ├── db-manager.js │ ├── query-generator.js │ ├── query-support.js │ ├── routes.js │ └── db-connection/ ├── server/ # Build compilado (npm run build) └── docs/
3. Configuração Detalhada
3.1 protheus-rest-api.config.jsonc
Arquivo principal de configuração. O arquivo utilizado é definido pela chave appConfigFilePath do package.json.
{
"service": {
"name": ".3001 - Node Protheus REST API",
"description": "Protheus REST API",
"script": "server"
},
"port": 3001,
"serverConfig": {
"logDateWhenStarting": true,
"allowOnlyAddresses": ["::ffff:127.0.0.1", "::1"]
},
"completeLogger": false,
"bulkMaxRecords": 100,
"environments": [
{
"name": "protheus-prd",
"plataform": "mssql",
"config": {
"user": "seu_usuario",
"password": "sua_senha",
"server": "ip_ou_hostname",
"database": "PROTHEUS_PRD",
"connectionTimeout": 999999999,
"requestTimeout": 999999999,
"options": { "enableArithAbort": true }
},
"default": true
}
]
}
| Propriedade | Tipo | Descrição |
|---|---|---|
port | number | Porta HTTP em que o servidor irá escutar |
serverConfig.allowOnlyAddresses | string[] | IPs autorizados. Outros IPs recebem 401 |
serverConfig.logDateWhenStarting | boolean | Exibe data/hora em cada entrada de log |
completeLogger | boolean | true para registrar queries SQL completas no console |
bulkMaxRecords | number | Limite máximo de registros por requisição bulk (padrão: 100) |
environments[].name | string | Identificador do ambiente, enviado no header environment |
environments[].plataform | string | "mssql" para SQL Server ou "mysql" para MySQL |
environments[].default | boolean | Ambiente usado quando o header environment não é enviado |
environments[].config | object | Credenciais e parâmetros de conexão com o banco |
3.2 rdbconfig.json
Configuração interna da biblioteca rdb-server. Define o comportamento padrão para todas as tabelas.
{
"rdbConfig": {
"ENVIRONMENT_REQUEST_LOCATION": "headers",
"envDefaults": {
"idColumnName": "R_E_C_N_O_",
"deletedColumnName": "D_E_L_E_T_",
"deletedValue": "'*'",
"deleteAdd": ["R_E_C_D_E_L_ = R_E_C_N_O_"],
"columnsNotShow": ["R_E_C_D_E_L_"],
"updateAdd": [],
"insertAdd": [],
"idAutoIncrement": false,
"relationshipConditionById": false
}
}
}
| Propriedade | Padrão | Descrição |
|---|---|---|
idColumnName | R_E_C_N_O_ | Coluna de chave primária usada em operações por ID |
deletedColumnName | D_E_L_E_T_ | Coluna que indica registros deletados |
deletedValue | '*' | Valor atribuído à coluna de deleção no soft-delete |
deleteAdd | ["R_E_C_D_E_L_ = R_E_C_N_O_"] | Expressões SQL adicionais na deleção |
columnsNotShow | ["R_E_C_D_E_L_"] | Colunas ocultadas nos resultados de GET |
updateAdd | [] | Expressões SQL adicionais em todo UPDATE |
insertAdd | [] | Expressões SQL adicionais em todo INSERT |
idAutoIncrement | false | Quando false, ID calculado via MAX(R_E_C_N_O_)+1 |
relationshipConditionById | false | Quando false, $embed/$expand exigem $on explícito |
3.3 Configurando múltiplos ambientes
MSSQL (SQL Server)
{
"name": "protheus-prd",
"plataform": "mssql",
"config": {
"user": "usuario",
"password": "senha",
"server": "192.168.1.10",
"database": "PROTHEUS",
"options": {
"enableArithAbort": true
}
},
"default": true
}
MySQL
{
"name": "protheus-hml",
"plataform": "mysql",
"config": {
"user": "usuario",
"password": "senha",
"host": "192.168.1.20",
"database": "PROTHEUS_HML"
},
"default": false
}
4. Autenticação
Todas as requisições devem incluir um token JWT no header Authorization. O ambiente é selecionado pelo header environment.
Authorization: Bearer <seu-token-jwt> Content-Type: application/json environment: protheus-prd
| Header | Obrigatório | Descrição |
|---|---|---|
Authorization | Sim | Token JWT no formato Bearer <token> |
Content-Type | POST/PUT | Deve ser application/json |
environment | Não | Nome do ambiente. Se omitido, usa o ambiente com "default": true |
allowOnlyAddresses recebem 401 Unauthorized
imediatamente, antes de qualquer validação de token.
5. Endpoints CRUD Padrão
Cada tabela Protheus é acessível pelo nome como parâmetro de rota. O :id corresponde ao valor de R_E_C_N_O_.
5.1 GET — Listar registros
Retorna array JSON com todos os registros não deletados. Suporta filtros e paginação (ver Seção 7).
GET /SBM?BM_FILIAL=01&$limit=20 Authorization: Bearer <token> environment: protheus-prd
[
{ "R_E_C_N_O_": 105, "BM_FILIAL": "01", "BM_GRUPO": "A001", "BM_DESC": "Grupo A" },
{ "R_E_C_N_O_": 106, "BM_FILIAL": "01", "BM_GRUPO": "A002", "BM_DESC": "Grupo B" }
]O header de resposta x-total-length informa o total de registros sem paginação.
5.2 GET — Buscar por ID
Retorna o registro com R_E_C_N_O_ = :id. Retorna 404 se não encontrado ou deletado.
{ "R_E_C_N_O_": 105, "BM_FILIAL": "01", "BM_GRUPO": "A001", "BM_DESC": "Grupo A" }5.3 POST — Inserir registro único
Insere um único registro. Body deve ser um objeto JSON (não array). Retorna o registro inserido com 201.
POST /SBM
{ "BM_FILIAL": "01", "BM_GRUPO": "A003", "BM_DESC": "Grupo C" }{ "R_E_C_N_O_": 107, "BM_FILIAL": "01", "BM_GRUPO": "A003", "BM_DESC": "Grupo C" }5.4 PUT — Atualizar registro único
Atualiza o registro identificado por :id. Somente os campos no body são atualizados. Retorna o registro atualizado.
PUT /SBM/105
{ "BM_DESC": "Nova Descrição" }5.5 DELETE — Soft-delete
Executa soft-delete (ver Seção 8). O registro não é removido fisicamente. Retorna 204 No Content.
6. Bulk Operations
Operações em lote permitem inserir, atualizar ou deletar múltiplos registros em uma única requisição. Todas são atômicas: se qualquer registro falhar, toda a transação é revertida (rollback) e nenhuma alteração é persistida.
bulkMaxRecords no config (padrão: 100).
Acima desse limite retorna 413 Payload Too Large.
6.1 POST — Inserção em lote (bulk insert)
Quando o body é um array, a API detecta automaticamente a operação bulk.
POST /SBM
[
{ "BM_FILIAL": "01", "BM_GRUPO": "A001", "BM_DESC": "Grupo 1" },
{ "BM_FILIAL": "01", "BM_GRUPO": "A002", "BM_DESC": "Grupo 2" }
]{ "inserted": 2 }6.2 PUT — Atualização em lote (bulk update)
PUT /SBM
[
{ "R_E_C_N_O_": 105, "BM_DESC": "Atualizado 1" },
{ "R_E_C_N_O_": 106, "BM_DESC": "Atualizado 2" }
]{ "updated": 2 }6.3 DELETE — Soft-delete em lote
DELETE /SBM [105, 106, 107]
{ "deleted": 3 }6.4 Contratos de Erro — Bulk Operations
| HTTP | Situação | Body de resposta |
|---|---|---|
| 400 | Array vazio | { "error": "Array não pode estar vazio" } |
| 400 | Body não é array | { "error": "Body deve ser um array" } |
| 413 | Acima do limite | { "error": "Máximo de 100 registros por requisição bulk" } |
| 422 | Falha em item + rollback | { "error": "Operação revertida...", "failed_at": { "index": N, "reason": "..." } } |
| 422 | Erro SQL + rollback | { "error": "Operação revertida...", "reason": "mensagem SQL" } |
failed_at.index
para identificar o item problemático no array (base 0).
7. Filtros e Query Parameters
Os endpoints GET /:tableName aceitam um conjunto rico de parâmetros de query string para
filtrar, projetar, ordenar, paginar e relacionar dados. Parâmetros prefixados com $ são
diretivas de controle; os demais são interpretados como filtros de coluna.
7.1 Filtros de coluna — igualdade exata
Qualquer parâmetro sem prefixo $ e sem sufixo de operador é interpretado como filtro
de igualdade na coluna correspondente. As comparações de texto são case-insensitive
e acentos são normalizados automaticamente.
# Filtro simples GET /SBM?BM_FILIAL=01 # Múltiplos filtros (AND implícito) GET /SBM?BM_FILIAL=01&BM_GRUPO=A001
7.2 Operadores de comparação
Para filtros além da igualdade exata, sufixe o nome da coluna com o operador:
COLUNA$operador=valor
COLUNA$operador=valorIgualdade. SQL: UPPER(COLUNA) = UPPER(valor)
Maior que. SQL: COLUNA > valor
Maior ou igual. SQL: COLUNA >= valor
Menor que. SQL: COLUNA < valor
Menor ou igual. SQL: COLUNA <= valor
Diferente de. SQL: COLUNA != valor
Contém. SQL: UPPER(COLUNA) LIKE UPPER('%valor%')
Não contém. SQL: UPPER(COLUNA) NOT LIKE UPPER('%valor%')
Em lista. SQL: COLUNA IN (v1, v2, v3)
Separe os valores com |.
Fora da lista. SQL: COLUNA NOT IN (v1, v2)
Separe os valores com |.
Valores especiais nos filtros
| Valor na query string | Convertido para |
|---|---|
$null | null |
$undefined | undefined |
$true | true (1) |
$false | false (0) |
7.3 $map — Projeção de colunas
Seleciona apenas as colunas especificadas no resultado. Sem $map, retorna todas as colunas.
R_E_C_N_O_ é sempre incluído automaticamente.
Colunas separadas por vírgula.
?$map=BM_GRUPO,BM_DESCFormato coluna alias (separados por espaço). O alias se torna a chave no JSON de retorno.
GET /SBM?$map=BM_GRUPO grupo,BM_DESC descricao
# Retorna: [{ "R_E_C_N_O_": 105, "grupo": "A001", "descricao": "Grupo A" }]7.4 $sort — Ordenação
Define a ordem dos resultados. Sem $sort, a API ordena por R_E_C_N_O_.
Prefixe com -.
Separadas por vírgula.
?$sort=BM_FILIAL,-BM_GRUPO7.5 $page / $limit — Paginação por página
Paginação clássica. $page começa em 1. O valor padrão de
$limit é 10. O header de resposta x-total-length
retorna o total de registros sem paginação.
# Página 1, 20 registros por página GET /SBM?$page=1&$limit=20 # Página 3, 50 por página, com filtro e ordenação GET /SBM?BM_FILIAL=01&$page=3&$limit=50&$sort=BM_GRUPO
7.6 $start / $end — Paginação por intervalo de linhas
Alternativa à paginação por página. Define o intervalo exato de linhas (numeradas após aplicar filtros e ordenação). A linha 1 é o primeiro resultado.
# Linhas 1 a 50 GET /SBM?$start=1&$end=50 # A partir da linha 51 (sem limite final) GET /SBM?$start=51
$start/$end e $page/$limit são mutuamente exclusivos.
Use apenas uma forma por requisição.
7.7 q — Busca full-text
Realiza busca em todas as colunas da tabela simultaneamente. Case-insensitive. Em MSSQL usa collation específico para suporte a acentos. Colunas numéricas são convertidas para texto antes da comparação.
# Busca "embalagem" em todos os campos GET /SBM?q=embalagem # Combinado com filtro de coluna GET /SBM?BM_FILIAL=01&q=materia
OR para cada coluna da tabela.
Em tabelas largas pode impactar a performance. Prefira filtros por coluna específica quando possível.
7.8 $query — Expressão SQL literal
Permite passar uma expressão SQL WHERE arbitrária. Útil para condições complexas não cobertas
pelos operadores padrão. A expressão é envolta em parênteses e adicionada ao WHERE via AND.
GET /SBM?$query=BM_GRUPO LIKE 'A%' AND BM_DESC NOT IN ('Inativo', 'Obsoleto')$query injeta SQL diretamente na query. Use apenas em integrações controladas
onde a entrada é previamente validada.
7.9 $embed — Relacionamentos embarcados (1:N)
Embarca registros de uma tabela filha em cada registro do resultado principal. Relação pai → N filhos. O resultado da tabela filha aparece como array no registro pai, sob a chave com o nome da tabela filha.
GET /SC5?$embed=SC6&$on=SC6.C6_NUM = SC5.C5_NUM # Resultado: [ { "R_E_C_N_O_": 10, "C5_NUM": "000001", "SC6": [ { "C6_ITEM": "01", "C6_PRODUTO": "PROD001" }, { "C6_ITEM": "02", "C6_PRODUTO": "PROD002" } ] } ]
7.10 $expand — Expansão de referência (N:1)
Expande uma referência de chave estrangeira trazendo o registro pai completo. Relação filho → 1 pai. O registro pai aparece como objeto no registro filho.
GET /SC6?$expand=SB1&$on=SB1.B1_COD = SC6.C6_PRODUTO # Resultado: [ { "R_E_C_N_O_": 50, "C6_PRODUTO": "PROD001", "SB1": { "B1_DESC": "Produto 001", "B1_UM": "UN" } } ]
7.11 $on — Condição de JOIN
Define a condição SQL de relacionamento usada por $embed e $expand.
Obrigatório quando relationshipConditionById é false (padrão).
Aceita expressões SQL padrão: tabela.coluna = tabela.coluna.
Resumo de todos os parâmetros de query
| Parâmetro | Tipo | Descrição | Exemplo |
|---|---|---|---|
COLUNA=valor | string | Filtro de igualdade na coluna | ?BM_FILIAL=01 |
COLUNA$gt | any | Maior que | ?R_E_C_N_O_$gt=100 |
COLUNA$gte | any | Maior ou igual | ?R_E_C_N_O_$gte=100 |
COLUNA$lt | any | Menor que | ?R_E_C_N_O_$lt=200 |
COLUNA$lte | any | Menor ou igual | ?R_E_C_N_O_$lte=200 |
COLUNA$ne | any | Diferente de | ?BM_GRUPO$ne=A001 |
COLUNA$like | string | Contém (LIKE %valor%) | ?BM_DESC$like=grupo |
COLUNA$nlike | string | Não contém (NOT LIKE) | ?BM_DESC$nlike=inativo |
COLUNA$in | string | Em lista (separar com |) | ?BM_GRUPO$in=A001|A002 |
COLUNA$nin | string | Fora da lista (separar com |) | ?BM_GRUPO$nin=A001|A002 |
q | string | Busca full-text em todas as colunas | ?q=embalagem |
$query | string | Expressão SQL WHERE literal | ?$query=B1_TIPO='PA' |
$map | string | Projeção de colunas, com suporte a alias | ?$map=BM_GRUPO,BM_DESC |
$sort | string | Ordenação. Prefixar com - para DESC | ?$sort=-BM_GRUPO |
$page | number | Número da página (começa em 1) | ?$page=2 |
$limit | number | Registros por página (padrão: 10) | ?$limit=50 |
$start | number | Linha inicial para intervalo de linhas | ?$start=1 |
$end | number | Linha final (usar com $start) | ?$end=50 |
$embed | string | Embarca tabela filha (1:N) no resultado | ?$embed=SC6&$on=... |
$expand | string | Expande referência para tabela pai (N:1) | ?$expand=SB1&$on=... |
$on | string | Condição SQL de JOIN para $embed/$expand | ?$on=SC6.C6_NUM=SC5.C5_NUM |
8. Soft Delete — Comportamento Protheus
O Protheus utiliza "deleção lógica" (soft-delete) baseada em colunas de controle. Esta API implementa esse comportamento nativamente e de forma transparente.
SQL gerado pelo DELETE
UPDATE SBM SET D_E_L_E_T_ = '*', R_E_C_D_E_L_ = R_E_C_N_O_ WHERE R_E_C_N_O_ = 105 AND D_E_L_E_T_ != '*'
Colunas de controle
| Coluna | Configuração | Comportamento |
|---|---|---|
D_E_L_E_T_ | deletedColumnName | Recebe '*' quando o registro é deletado |
R_E_C_D_E_L_ | deleteAdd | Recebe o valor de R_E_C_N_O_ (oculto no GET por padrão) |
R_E_C_N_O_ | idColumnName | Chave primária em todas as operações por ID |
Todos os GET adicionam automaticamente WHERE D_E_L_E_T_ != '*'.
Registros deletados nunca aparecem nos resultados sem intervenção direta no banco.
R_E_C_D_E_L_ é excluída dos resultados pelo array columnsNotShow
do rdbconfig.json. Para torná-la visível, remova-a da lista.
9. Códigos de Retorno HTTP
| Código | Status | Quando ocorre |
|---|---|---|
| 200 | OK | GET com sucesso; PUT individual com sucesso (retorna registro); DELETE bulk com sucesso |
| 201 | Created | POST com sucesso. Individual: retorna objeto inserido. Bulk: retorna { "inserted": N } |
| 204 | No Content | DELETE individual com sucesso. Sem body na resposta. |
| 400 | Bad Request | Body inválido, array vazio em bulk, ou body obrigatório ausente |
| 401 | Unauthorized | IP de origem não está em allowOnlyAddresses |
| 404 | Not Found | Registro com :id não existe ou foi deletado |
| 413 | Payload Too Large | Array bulk acima de bulkMaxRecords |
| 422 | Unprocessable Entity | Falha em operação bulk. Rollback executado. Body contém detalhes. |
| 500 | Internal Server Error | Erro não tratado ou falha de conexão com o banco |
10. Exemplos Práticos — Tabela SBM
A tabela SBM (Grupos de Materiais) é usada como referência. Substitua por qualquer tabela Protheus disponível.
Listar com filtro, ordenação e paginação
GET /SBM?BM_FILIAL=01&$page=1&$limit=10&$sort=BM_GRUPO Authorization: Bearer <token> environment: protheus-prd
Projeção de colunas com alias
GET /SBM?BM_FILIAL=01&$map=BM_GRUPO grupo,BM_DESC descricao&$sort=BM_GRUPO
Busca full-text + intervalo de R_E_C_N_O_
GET /SBM?q=embalagem&R_E_C_N_O_$gte=100&R_E_C_N_O_$lte=500
Filtro com lista ($in) e exclusão ($nlike)
GET /SBM?BM_GRUPO$in=MA01|PA01|EM01&BM_DESC$nlike=obsoleto
Inserção em lote
POST /SBM
[
{ "BM_FILIAL": "01", "BM_GRUPO": "MA01", "BM_DESC": "Matéria Prima" },
{ "BM_FILIAL": "01", "BM_GRUPO": "PA01", "BM_DESC": "Produto Acabado" },
{ "BM_FILIAL": "01", "BM_GRUPO": "EM01", "BM_DESC": "Embalagem" }
]Atualização em lote
PUT /SBM
[
{ "R_E_C_N_O_": 200, "BM_DESC": "Matéria Prima Nacional" },
{ "R_E_C_N_O_": 201, "BM_DESC": "Produto Acabado Exportação" }
]Deleção em lote
DELETE /SBM [200, 201, 202]
11. Troubleshooting
Erros de conexão
Causa: Banco inacessível, credenciais incorretas ou IP/porta errados.
Solução: Verifique server, user, password
e database no config. Confirme que o banco está ativo e acessível a partir do servidor Node.js.
Causa: Query demorou mais que requestTimeout.
Solução: Aumente requestTimeout no config, adicione índices ou refine os filtros.
Outros erros comuns
| Sintoma | Causa provável | Solução |
|---|---|---|
Toda requisição retorna 401 | IP não listado em allowOnlyAddresses | Adicionar o IP ao array no config e reiniciar |
| Dados do ambiente errado | Header environment ausente ou com valor incorreto | Verificar o campo name exato no environments |
POST retorna 400 | Body inválido ou Content-Type ausente | Verificar headers e estrutura do body |
PUT retorna 404 | R_E_C_N_O_ inexistente ou registro deletado | Confirmar o ID com um GET antes do PUT |
Bulk retorna 422 | Falha em um registro (constraint, tipo, etc.) | Verificar failed_at.index e failed_at.reason |
Bulk retorna 413 | Array acima de bulkMaxRecords | Reduzir o lote ou aumentar bulkMaxRecords |
| Serviço não inicia: porta em uso | Outro processo usando a mesma porta | Alterar a porta no config ou encerrar o processo conflitante |
| Módulo não encontrado | Build não gerado (server/ vazio) | Executar npm run build |
$embed/$expand sem resultado | $on não informado | Incluir $on com a condição de JOIN correta |
Ativar log completo de queries
// Temporariamente: ativa o log das queries SQL completas no console "completeLogger": true
completeLogger: false em produção. O log completo pode expor dados sensíveis.