SaldoEstoque
Consulta read-only de saldo em estoque por produto e
armazém (tabela SB2). Cálculo via função padrão Protheus
SaldoSB2(), que cobre empenhos (SD4), saldo
em terceiros e reservas conforme o parâmetro MV_TPSALDO.
Não há POST, PUT ou DELETE —
inclusão/baixa de saldo acontece via documentos de movimento (NF de
entrada/saída, requisição, produção).
Endpoint OAuth2
Sem credenciais? Você pode pegar o token rodando
./src/scripts/get-token.sh na biblioteca e
colar o access_token aqui no campo de refresh
(ou clique em "Logout" para limpar e voltar ao fake).
Vinculação com o ERP
Convenções
Endpoint read-only. Toda escrita em SB2 é
consequência de outro processo do ERP (entrada/saída fiscal, requisição,
produção, transferência). Esta API não inclui, altera ou exclui
saldo — só consulta. Inexiste POST, PUT ou
DELETE nesta rota.
Chave externa composta. A rota canônica
/WsSB2/{produto}/{local} identifica o saldo por
B2_COD + B2_LOCAL. O endpoint aceita também
/WsSB2/{produto} sem o segundo segmento — nesse caso o
armazém é assumido como "01" (padrão default Protheus).
Bloqueio TOTVS não se aplica. A tabela SB2
não possui o par *_MSBLQL/*_MSBLQD
no dicionário padrão — bloqueio administrativo de saldo não faz sentido
(o saldo é resultado da movimentação, não um cadastro). Bloqueio comercial,
se houver, mora no produto (SB5)
ou no cliente/fornecedor.
Cálculo de saldo via SaldoSB2(). A rota
/WsSB2/{produto}/{local} compõe um envelope que mistura
saldo físico (lido direto de B2_QATU) com
saldo disponível (calculado por SaldoSB2() aplicando
empenhos, reservas e terceiros conforme query params opcionais).
Os modificadores subtrairEmpenho, dataEmpenho,
consideraTerceirosNossoPoder, consideraNossoEmTerceiros
e subtrairReserva mapeiam diretamente para os argumentos
posicionais (2º, 3º, 4º, 5º e 9º) da função nativa.
Paginação e delta-sync. /_list,
/porProduto/{produto} e /porArmazem/{armazem}
compartilham o mesmo motor de paginação: page/pageSize
(default 50, máx 500) em orderBy=chave,
ou orderBy=recno com since/cursor para
sincronização incremental seguindo o nextCursor.
Campos opcionais do dicionário. B2_DUM
(data da última movimentação) e B2_VATU1 (custo médio)
existem na maioria dos ambientes mas não em todos os dicionários
customizados. O endpoint testa presença via FieldPos();
se ausentes, as propriedades atualizadoEm e custoMedio
simplesmente não aparecem na resposta — nunca há erro 500 por causa disso.
Saldo
Consulta consolidada por produto+localDescrição
Retorna o saldo consolidado de um produto em um armazém específico.
A resposta combina campos diretos da SB2 (físico, reservado,
empenhado, terceiros) com o resultado de SaldoSB2() em
saldoDisponivel — aplicando empenhos, reservas e terceiros
conforme os modificadores de cálculo passados na query string.
Descrição (B1_DESC) e unidade (B1_UM) são
enriquecidas a partir de SB1 com restore automático de
área (sem efeito colateral no alias do chamador).
A rota /WsSB2/{produto} (sem o segundo segmento) é
aceita e equivale a {produto}/01.
Path parameters
TamSX3("B2_COD"); o endpoint aplica PadR automaticamente."01" quando omitido.Cenário
Query parameters · modificadores de cálculo
SD4) do saldo disponível. Default true. Mapeia para o 2º argumento de SaldoSB2().YYYYMMDD). Vazio = sem corte. 3º argumento de SaldoSB2().B2_QTNP). Default false. 4º argumento de SaldoSB2().B2_QNPT). Default false. 5º argumento de SaldoSB2().B2_RESERVA) do saldo disponível. Default false. 9º argumento de SaldoSB2().
Valores booleanos aceitam true, 1,
sim ou yes (case-insensitive) — qualquer
outra string é tratada como false.
Exemplo da requisição (cenário ativo)
GET https://erpapi.jetme.com.br/api/99/01/WsSB2/MP001/01 Authorization: Bearer eyJhbGciOiJIUzI1NiIs… Accept: application/json
GET https://erpapi.jetme.com.br/api/99/01/WsSB2/MP001/01?subtrairEmpenho=true&subtrairReserva=true&dataEmpenho=20260512 Authorization: Bearer eyJhbGciOiJIUzI1NiIs… Accept: application/json
Respostas
{ success: true, data: SaldoSB2 }.SB2). Tier: not-found.DbSeek, SaldoSB2() ou serialização). Tier: business-error.Descrição
Busca o registro de saldo por id técnico — útil para integrações
que persistem o recno retornado em /_list
ou na linha do saldo consolidado, e querem reconfirmar o registro
sem refazer o seek por produto+local.
Diferente de /WsSB2/{produto}/{local}, esta rota
retorna o registro cru (campos do whitelist
CAMPOS_GET), sem agregação via SaldoSB2().
Apenas tipo=recno é suportado. SB2 no
dicionário padrão não tem coluna MSUID indexada que
justifique a opção; se o ambiente expuser B2_MSUID,
ele aparece como campo opcional no payload de resposta.
Cenário
Query parameters
recno nesta versão.recnoSB2.Exemplo da requisição (cenário ativo)
GET https://erpapi.jetme.com.br/api/99/01/WsSB2/_byid?tipo=recno&valor=1234 Authorization: Bearer eyJhbGciOiJIUzI1NiIs… Accept: application/json
Respostas
{ success: true, data: SaldoLinha }.tipo ausente, fora do enum, ou valor não-positivo. Tier: validation-error.Listagem
Listagem paginada e filtros pré-definidosDescrição
Lista registros de saldo da SB2 com filtros opcionais
por cod (prefix de produto) e armazem
(match exato). Retorna campos crus de CAMPOS_GET
(B2_COD, B2_LOCAL, B2_QATU,
B2_RESERVA, B2_QEMP, B2_QTNP,
B2_QNPT) — sem agregação via SaldoSB2().
Dois modos de ordenação:
orderBy=chave(default) — paginação porpage/pageSize. Usa o índice de chave (B2_FILIAL+B2_COD+B2_LOCAL); quandocodé informado, fazDbSeekcom soft e corta no primeiro prefixo divergente.orderBy=recno— delta-sync; ordena por RecNo físico (DbSetOrder(0)) e retornanextCursorpara a próxima página. Usesince/cursorpara retomar onde parou.
Cenário
Query parameters
B2_COD. Em orderBy=chave, ativa DbSeek com corte no primeiro prefixo fora do filtro.B2_LOCAL. Sem cod, força scan-full filtrando em memória — funciona, mas raro.CAMPOS_GET; campo fora do whitelist provoca 400.orderBy=chave (default 1, mínimo 1). Ignorado em orderBy=recno.50, máx 500).chave.chaverecnoorderBy=recno; em orderBy=chave filtra em memória.nextCursor da página anterior. Válido em orderBy=recno.Exemplo da requisição (cenário ativo)
GET https://erpapi.jetme.com.br/api/99/01/WsSB2/_list?cod=MP&armazem=01&page=1&pageSize=50 Authorization: Bearer eyJhbGciOiJIUzI1NiIs… Accept: application/json
GET https://erpapi.jetme.com.br/api/99/01/WsSB2/_list?orderBy=recno&cursor=1234&pageSize=200 Authorization: Bearer eyJhbGciOiJIUzI1NiIs… Accept: application/json
Respostas
ListResponse com data (array de SaldoLinha), page, pageSize, count, orderBy, opcionalmente nextCursor.fields com campo fora de CAMPOS_GET. Tier: validation-error.Descrição
Atalho semântico para listagem por produto. Equivale a
GET /WsSB2/_list?cod={produto} e devolve todos os
armazéns em que o produto tem saldo cadastrado em SB2.
Aceita os mesmos query params de /_list
(fields, page, pageSize,
orderBy, since, cursor) —
exceto cod, que é forçado pelo path.
Path parameter
B2_COD.Cenário
Exemplo da requisição (cenário ativo)
GET https://erpapi.jetme.com.br/api/99/01/WsSB2/porProduto/MP001?fields=B2_COD,B2_LOCAL,B2_QATU&pageSize=100 Authorization: Bearer eyJhbGciOiJIUzI1NiIs… Accept: application/json
Respostas
ListResponse. Quando o produto não tem saldo em nenhum armazém, data = [] e count = 0 (não retorna 404).Descrição
Atalho semântico para listagem por armazém. Equivale a
GET /WsSB2/_list?armazem={armazem} e devolve todos
os produtos com saldo no armazém. Aceita os mesmos query params de
/_list exceto armazem (forçado pelo path).
Atenção a custo. Sem prefix de cod,
o filtro por armazém faz scan-full do índice de chave filtrando em
memória — barato em ambientes pequenos, caro em milhões de SKUs.
Para inventário grande prefira orderBy=recno com
since/cursor.
Path parameter
B2_LOCAL).Cenário
Exemplo da requisição (cenário ativo)
GET https://erpapi.jetme.com.br/api/99/01/WsSB2/porArmazem/01?orderBy=recno&pageSize=500 Authorization: Bearer eyJhbGciOiJIUzI1NiIs… Accept: application/json
Respostas
ListResponse.Schemas
Definições canônicas — campos com origemSX3 rastreável
GET /WsSB2/{produto}/{local}.
Combina campos diretos de SB2 com saldoDisponivel
calculado por SaldoSB2() aplicando os modificadores da
query string. Campos atualizadoEm e custoMedio
só aparecem se o dicionário tiver B2_DUM e B2_VATU1.
SB1 via seek auxiliar; vazia se o produto não estiver cadastrado.SaldoSB2(). Reflete a combinação de modificadores da query string (subtração de empenho/reserva, terceiros).YYYYMMDD. Só aparece se o dicionário tiver B2_DUM.B2_VATU1.SB2 (RecNo do AdvPL).B2_MSUID.msuid. Só aparece se o dicionário tiver B2_MSUIDT./_list, /porProduto,
/porArmazem e /_byid. Campos crus de
SB2 conforme CAMPOS_GET — sem agregação
via SaldoSB2(). O conjunto efetivo depende de
?fields=.
page só aparece
em orderBy=chave; nextCursor só aparece em
orderBy=recno.
true em respostas de sucesso.[] quando o filtro não casa.orderBy=chave.chaverecno?cursor= da próxima requisição. Presente em orderBy=recno.false em erros.Cenários
Catálogo de combinações reconhecidas em cada rota. Endpoint read-only — todos os cenários usam o métodoGET; o tier é semântico no slug; endpoint no type-pill.
SaldoSB2(): subtrai empenho, mantém reserva, ignora terceiros.
Quando usar: consulta pontual em tela de venda / produção / planejamento. Caminho feliz mínimo.
Quando usar: simulação para MRP / planejamento de compras.
RecNo() via tipo=recno&valor=N. Recno do AdvPL, sempre disponível. Não há byid-msuid: o dicionário padrão da SB2 não traz B2_MSUID nesta base — ver Pendências.
orderBy=chave + page/pageSize. Filtro por prefixo de produto e armazém exato; paginação por offset sobre o índice B2_FILIAL+B2_COD+B2_LOCAL.
orderBy=recno + cursor retomável. Cada página devolve nextCursor para a próxima chamada. since aceito como corte mínimo.
Quando usar: sincronização incremental para data warehouses ou aplicações Angular que cacheiam saldos.
/_list?cod={produto}.
/_list?armazem={armazem}. Para conjuntos grandes prefira orderBy=recno.
Pendências conhecidas (rev3)
B2_MSUID ausente nesta base. O dicionário
padrão TOTVS de SB2 não traz a coluna B2_MSUID nesta
instalação. Por isso o _byid só aceita tipo=recno;
tentativas com tipo=msuid retornariam 501 Not Implemented
se a opção fosse exposta. Quando o ambiente do cliente expuser o campo
(via UPDDISTR), o payload de resposta já vai incluí-lo
automaticamente (FieldPos defensivo); a rota
_byid?tipo=msuid precisa de uma revisão futura para
implementar a busca via índice MSUID.
Custo de varredura em ambientes grandes. A
/porArmazem e /_list sem cod
fazem scan-full do índice de chave; em bases com milhões de SKUs isso
pode ser caro. Sempre que possível, combinar com orderBy=recno
+ cursor. O limite hard PAGE_SIZE_MAX=500 evita
OOM, mas não evita latência alta em catálogos extensos. Para reports
analíticos prefira o DW (src/dw/) em vez deste endpoint.
Bloqueio MSBLQL/MSBLQD não aplicável. Saldo é
resultado de movimentação, não cadastro. A SB2 não tem o par
B2_MSBLQL/B2_MSBLQD no dicionário, e o endpoint
é read-only — não há fluxo de bloqueio a documentar. Bloqueio
comercial mora no produto (SB5)
ou cliente/fornecedor; consultar lá quando relevante.
Sem _search (suggestion endpoint). A
família de saldos não expõe /_search para autocomplete —
o autocomplete de produto vem do WsProduto
(SB1) e o de armazém do WsNNR. Esta API só consulta saldo, assumindo
que o consumer já tem o código do produto/armazém em mãos.
Concorrência com movimento. O saldo retornado é um
snapshot do instante do DbSeek; movimentos em paralelo
(NF de saída, requisição) podem alterar B2_QATU entre a
leitura e o uso pelo consumer. Para fluxos críticos (separação,
expedição) prefira SaldoSB2() dentro do mesmo processo do
movimento, em vez de cachear a resposta deste endpoint.