Visão geral
A API v1 do SGEI expõe os mesmos dados que as telas do sistema, com as mesmas regras de negócio. Uma baixa de estoque feita pela API passa pelo mesmo motor transacional da tela: trava a linha do produto, registra a movimentação com saldo anterior e posterior, atualiza o lote pelo critério PEPS e dispara os alertas de estoque mínimo.
- Protocolo: HTTPS obrigatório. Chamadas em HTTP são redirecionadas.
- Formato: JSON na requisição e na resposta (
Content-Type: application/json). - Codificação: UTF-8.
- Métodos:
GET,POST,PUT,PATCH,DELETE. - Datas:
AAAA-MM-DDeAAAA-MM-DD HH:MM:SS. - Valores: ponto como separador decimal (
15.90).
Base URL
https://estoque.lopes.tec.br/api/v1
Todos os caminhos deste manual são relativos a essa base. Por exemplo,
/produtos corresponde a
https://estoque.lopes.tec.br/api/v1/produtos.
Autenticação
Cada integração recebe um trio de credenciais, gerado em
API / Integrações dentro do sistema. Os três cabeçalhos são
obrigatórios em toda requisição (exceto em /health):
| Cabeçalho | Conteúdo | Para que serve |
|---|---|---|
Authorization |
Bearer <token>64 caracteres hexadecimais |
Segredo principal da credencial. |
X-Share-Name |
<share_name>ex.: loja-virtual-a1b2c3 |
Identifica qual integração está chamando. Aparece nos logs e na auditoria. |
X-Key |
<key>32 caracteres hexadecimais |
Segundo segredo. Funciona como a “senha” do par — o token sozinho não abre nada. |
Exemplo completo
curl -X GET 'https://estoque.lopes.tec.br/api/v1/produtos?por_pagina=5' \
-H 'Authorization: Bearer 9f2c7a1e4b6d8f0a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f90' \
-H 'X-Share-Name: loja-virtual-a1b2c3' \
-H 'X-Key: 4b6d8f0a3c5e7b9d1f3a5c7e9b1d3f5a' \
-H 'Accept: application/json'
Sessão do navegador
As próprias telas do SGEI consomem esta API. Quando a requisição vem de um usuário já autenticado no navegador (cookie de sessão), os três cabeçalhos são dispensados — as permissões aplicadas passam a ser as do perfil desse usuário.
Formato das respostas
Sucesso
{
"sucesso": true,
"dados": { ... }, // objeto ou lista
"meta": { // presente nas listagens
"total": 128,
"pagina": 1,
"por_pagina": 25,
"total_paginas": 6
}
}
Erro
{
"sucesso": false,
"erro": "Dados inválidos.",
"codigo": "VALIDACAO",
"detalhes": {
"nome": "Nome é obrigatório.",
"preco_venda": "Preço de venda não pode ser menor que 0."
}
}
O campo codigo é estável e serve para tratamento programático;
o campo erro é a mensagem legível, em português, e pode mudar de redação.
Paginação e filtros
| Parâmetro | Padrão | Descrição |
|---|---|---|
pagina | 1 | Página desejada (aceita page como alias). |
por_pagina | 25 | Itens por página, máximo 200 (alias limit). |
busca | — | Busca textual nos campos principais do recurso (alias q). |
ordem | varia | coluna ASC|DESC. Só colunas permitidas pelo recurso são aceitas. |
de / ate | — | Recorte por período, no formato AAAA-MM-DD. |
GET /produtos?busca=pilha&categoria_id=1&estoque_baixo=1&pagina=2&por_pagina=50&ordem=quantidade%20ASC
Produtos
Lista paginada. Filtros: busca, categoria_id,
fornecedor_id, estoque_baixo=1, zerado=1, ativo=0|1.
Permissão: produtos.ver
Aceita o id numérico, o SKU ou o
código de barras — é o mesmo endpoint usado pelo leitor de QR Code.
Devolve também disponivel (saldo menos reservas) e qr_payload.
Permissão: produtos.ver
Produtos no ou abaixo do estoque mínimo. Parâmetro: limite.
Permissão: produtos.ver
Extrato (kardex) do produto. Parâmetros: de, ate, limite.
Permissão: estoque.ver
Cadastra um produto. O codigo (SKU) é gerado automaticamente se não for enviado.
Se quantidade vier preenchida, o saldo inicial entra como uma
movimentação de entrada — nunca como um número solto.
Permissão: produtos.criar
Atualiza o cadastro. O campo quantidade é ignorado:
saldo só muda por movimentação de estoque.
Permissão: produtos.editar
Produto sem movimentações é excluído. Produto com
histórico é apenas inativado (ativo = 0), para preservar a rastreabilidade —
a resposta indica qual dos dois aconteceu.
Permissão: produtos.excluir
Exemplo — criar produto
POST /produtos
Content-Type: application/json
{
"nome": "Pilha Alcalina AA (cartela com 4)",
"descricao": "Pilha alcalina 1,5 V",
"categoria_id": 1,
"fornecedor_id": 1,
"unidade_medida": "CX",
"preco_custo": 12.50,
"preco_venda": 24.90,
"quantidade": 100,
"quantidade_minima": 20,
"quantidade_maxima": 400,
"codigo_barras": "7891234567890",
"armazem": "Depósito A",
"prateleira": "P3-B",
"ncm": "85061010",
"controla_lote": 1,
"perecivel": 1
}
Resposta 201 Created
{
"sucesso": true,
"dados": {
"id": 42,
"codigo": "PILH-04821",
"nome": "Pilha Alcalina AA (cartela com 4)",
"categoria_id": 1,
"categoria_nome": "Tecnologia",
"fornecedor_nome": "Sellud",
"unidade_medida": "CX",
"quantidade": 100,
"quantidade_minima": 20,
"preco_custo": 12.50,
"preco_venda": 24.90,
"margem_lucro": 99.20,
"ativo": 1,
"data_criacao": "2026-09-07 08:31:02"
}
}
Estoque
Histórico com filtros produto_id, tipo,
documento_tipo, de, ate, busca.
Permissão: estoque.ver
Registra entrada, saída, devolução, perda ou ajuste. A operação é atômica: o saldo é lido e gravado dentro de uma transação com trava na linha do produto, então duas integrações simultâneas nunca se atropelam.
Permissão: estoque.criar (o tipo ajuste também exige estoque.ajustar)
Define o saldo exato (contagem física). A quantidade
informada passa a ser o novo saldo.
Permissão: estoque.ajustar
Saldo físico e disponível. Aceita id, SKU ou código de barras. Ideal para o e-commerce consultar antes de fechar a venda.
Permissão: estoque.ver
Lotes com saldo. Filtros: produto_id,
vencendo_em (dias).
Permissão: lotes.ver
Exemplo — dar entrada com lote e validade
POST /estoque/movimentacoes
Content-Type: application/json
{
"produto_id": 42,
"tipo": "entrada",
"quantidade": 120,
"origem": "Compra NF 4471",
"documento": "NF-4471",
"custo_unitario": 12.30,
"lote": "L2609A",
"validade": "2028-03-31",
"observacao": "Recebimento parcial do pedido PC-202609-0003"
}
Resposta 201 Created
{
"sucesso": true,
"dados": {
"movimentacao_id": 5183,
"saldo_anterior": 100,
"saldo_posterior": 220
}
}
entrada, saida,
ajuste, devolucao, perda, transferencia.
Em saida sem lote informado, o sistema consome pelo critério PEPS —
o lote que vence antes sai primeiro.
Saída com estoque insuficiente 409 Conflict
{
"sucesso": false,
"erro": "Estoque insuficiente para \"Pilha Alcalina AA\": saldo atual 3, saída solicitada 10.",
"codigo": "CONFLITO"
}
Categorias
Permissão: categorias.ver
Hierarquia pronta para montar um <select>, com nivel e rótulo indentado.
Campos: nome (obrigatório), descricao, categoria_pai_id, cor.
Devolve 409 se houver produtos ou subcategorias vinculados.
Fornecedores
Permissões: fornecedores.ver|criar|editar|excluir.
O CNPJ/CPF é validado pelo dígito verificador e gravado só com dígitos.
POST /fornecedores
{
"nome": "Sellud Distribuidora",
"razao_social": "Sellud Comércio de Componentes LTDA",
"cnpj": "08.310.615/0001-26",
"email": "compras@sellud.com.br",
"telefone": "(11) 3333-4444",
"contato": "Marina Alves",
"cep": "06385-490",
"cidade": "Carapicuíba",
"uf": "SP",
"prazo_entrega": 7,
"condicoes_pagamento": "30/60 dias"
}
Clientes
Totais de pedidos, OS, faturas e valor comprado.
Permissões: clientes.ver|criar|editar|excluir.
O campo tipo_pessoa é deduzido do documento (11 dígitos = física, 14 = jurídica).
Pedidos de venda
Filtros: status, cliente_id, de, ate, busca.
Aceita o id ou o número do pedido. Traz itens e historico.
Cria o pedido, grava os itens e baixa o estoque — tudo em uma transação. Se um item não tiver saldo, nada é gravado.
Muda a situação respeitando o fluxo. O cancelamento devolve os itens ao estoque.
Exemplo — pedido vindo do e-commerce
POST /pedidos
Content-Type: application/json
{
"cliente_nome": "Maria Souza",
"cliente_email": "maria@exemplo.com.br",
"cliente_telefone": "(11) 98888-7777",
"cliente_cpf_cnpj": "322.496.568-19",
"cep": "06385-490",
"logradouro": "Rua Cajobi",
"numero": "1122",
"bairro": "Jardim Ângela Maria",
"cidade": "Carapicuíba",
"uf": "SP",
"forma_pagamento": "pix",
"frete": 18.50,
"desconto": 0,
"observacoes": "Entregar no período da tarde",
"itens": [
{ "produto_id": 42, "quantidade": 2 },
{ "produto_id": 17, "quantidade": 1 }
]
}
Resposta 201 Created
{
"sucesso": true,
"dados": {
"id": 31,
"numero_pedido": "PED-202609-0031",
"cliente_id": 12,
"total": 68.30,
"status": "solicitado",
"data_pedido": "2026-09-07 08:44:19",
"itens": [
{ "produto_id": 42, "nome_produto": "Pilha Alcalina AA", "quantidade": 2,
"preco_unitario": 24.90, "subtotal": 49.80 },
{ "produto_id": 17, "nome_produto": "Cabo HDMI 2 m", "quantidade": 1,
"preco_unitario": 0.00, "subtotal": 0.00 }
]
}
}
Mudar status
PUT /pedidos/31/status
{ "status": "em_andamento", "observacao": "Separação iniciada" }
Fluxo permitido:
solicitado → em_andamento → pronto_para_entrega →
entregue → concluido.
cancelado é possível a partir de qualquer etapa anterior à entrega
e devolve o estoque. Transições fora do fluxo devolvem 409.
Ordens de serviço
POST /os
{
"cliente_id": 12,
"titulo": "Troca da fonte do notebook",
"equipamento": "Notebook Dell Inspiron 15",
"numero_serie": "BR-4471-XZ",
"defeito_relatado": "Não liga na tomada; bateria carrega no carregador reserva.",
"prioridade": "alta",
"previsao_conclusao": "2026-09-12",
"itens": [
{ "tipo": "produto", "produto_id": 88, "quantidade": 1 },
{ "tipo": "servico", "descricao": "Mão de obra — substituição", "quantidade": 1, "valor_unitario": 120.00 }
]
}
PUT /os/{id}/status com "status": "concluida").
Até lá ficam apenas previstos.
Compras e recebimento
Traz itens (com quantidade pendente) e recebimentos.
Permissão específica: compras.aprovar.
Permissão: recebimento.criar.
Exemplo — conferência de recebimento
POST /compras/7/recebimento
{
"nota_fiscal": "NF-4471",
"observacoes": "Uma caixa chegou amassada",
"itens": [
{ "item_id": 15, "quantidade_recebida": 100, "lote": "L2609A", "validade": "2028-03-31" },
{ "item_id": 16, "quantidade_recebida": 8, "conforme": false,
"observacao": "Embalagem violada — devolver ao fornecedor" }
]
}
{
"sucesso": true,
"dados": { "recebimento_id": 4, "itens": 2, "nao_conformidades": 1 }
}
"conforme": false ficam registrados como
não conformidade e não entram no estoque —
servem de base para a devolução parcial ao fornecedor.
Relatórios
Posição atual valorizada, item a item.
Resumo, série diária e ranking por produto.
Classificação A/B/C por valor imobilizado, com percentual acumulado.
KPIs consolidados, faturamento diário, mais vendidos e distribuição por categoria.
Permissões: relatorios.ver e dashboard.ver.
Utilitários
Não exige autenticação. Use para monitoramento externo (uptime).
Mostra qual credencial está em uso e quais permissões ela tem. Ótimo primeiro teste.
GET /health
{
"sucesso": true,
"dados": {
"status": "ok",
"banco": true,
"versao_api": "v1",
"ambiente": "producao",
"hora": "2026-09-07T08:52:11-03:00",
"latencia_ms": 1.84
}
}
Códigos de erro
| HTTP | codigo | Quando acontece / o que fazer |
|---|---|---|
| 200 | — | Sucesso. |
| 201 | — | Registro criado. O corpo traz o recurso completo. |
| 204 | — | Sucesso sem conteúdo. |
| 400 | REQUISICAO_INVALIDA | JSON malformado ou parâmetro fora do formato. |
| 401 | NAO_AUTENTICADO | Falta um dos três cabeçalhos, ou a credencial é inválida/revogada/expirada. |
| 403 | PERMISSAO_NEGADA | A credencial existe mas não tem a permissão do recurso. O corpo lista as permissões que ela possui. |
| 404 | NAO_ENCONTRADO | Recurso ou registro inexistente. |
| 405 | METODO_NAO_PERMITIDO | O caminho existe, mas não nesse método. O cabeçalho Allow traz os aceitos. |
| 409 | CONFLITO | Regra de negócio impediu a operação: estoque insuficiente, transição de status inválida, registro com vínculos. |
| 422 | VALIDACAO | Campos inválidos. detalhes traz o erro de cada campo. |
| 429 | LIMITE_EXCEDIDO | Estourou o limite por minuto. Aguarde o tempo do cabeçalho Retry-After. |
| 500 | ERRO_INTERNO | Falha no servidor. O detalhe técnico fica no log; nada sensível é devolvido. |
| 503 | OFFLINE | Devolvido pelo Service Worker do PWA quando não há conexão nem dado em cache. |
Limites de uso
- Padrão: 120 requisições por minuto por credencial (configurável de 10 a 10.000 na emissão).
- Janela: deslizante de 60 segundos.
- Cabeçalhos de controle em toda resposta autenticada:
X-RateLimit-LimiteX-RateLimit-Remaining. - Ao estourar: 429 com
Retry-After: 60. - Paginação: máximo de 200 itens por página.
- Listas auxiliares (lotes, clientes ativos) devolvem no máximo 500 registros.
por_pagina=200 e respeite o X-RateLimit-Remaining —
uma pausa de 500 ms entre páginas mantém a integração dentro do limite com folga.
Permissões
Cada credencial recebe uma lista de permissões no formato
modulo.acao. Existem também os coringas modulo.*
(tudo de um módulo) e * (acesso total).
| Módulo | Ações |
|---|---|
produtos | ver, criar, editar, excluir |
estoque | ver, criar, editar, excluir, ajustar |
lotes | ver, criar, editar, excluir |
categorias | ver, criar, editar, excluir |
fornecedores | ver, criar, editar, excluir |
clientes | ver, criar, editar, excluir |
pedidos | ver, criar, editar, excluir |
os | ver, criar, editar, excluir |
compras | ver, criar, editar, excluir, aprovar |
recebimento | ver, criar, editar, excluir |
faturas | ver, criar, editar, excluir, emitir |
relatorios | ver, exportar |
dashboard | ver |
produtos.ver, estoque.ver,
clientes.criar e pedidos.criar. Evite *
fora de integrações internas.
Receitas prontas
PHP — sincronizar saldo com a loja
<?php
$base = 'https://estoque.lopes.tec.br/api/v1';
$cabecalhos = [
'Authorization: Bearer ' . getenv('SGEI_TOKEN'),
'X-Share-Name: ' . getenv('SGEI_SHARE'),
'X-Key: ' . getenv('SGEI_KEY'),
'Accept: application/json',
];
$pagina = 1;
do {
$ch = curl_init("{$base}/produtos?pagina={$pagina}&por_pagina=200&ativo=1");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $cabecalhos,
CURLOPT_TIMEOUT => 20,
]);
$resposta = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($resposta['dados'] ?? [] as $produto) {
atualizarNaLoja($produto['codigo'], (int) $produto['quantidade'], (float) $produto['preco_venda']);
}
$totalPaginas = $resposta['meta']['total_paginas'] ?? 1;
$pagina++;
usleep(500000); // respeita o limite de requisições
} while ($pagina <= $totalPaginas);
JavaScript / Node — criar pedido
const BASE = 'https://estoque.lopes.tec.br/api/v1';
const cabecalhos = {
'Authorization': `Bearer ${process.env.SGEI_TOKEN}`,
'X-Share-Name': process.env.SGEI_SHARE,
'X-Key': process.env.SGEI_KEY,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
async function criarPedido(carrinho, cliente) {
const resposta = await fetch(`${BASE}/pedidos`, {
method: 'POST',
headers: cabecalhos,
body: JSON.stringify({
cliente_nome: cliente.nome,
cliente_email: cliente.email,
cliente_telefone: cliente.telefone,
cliente_cpf_cnpj: cliente.documento,
forma_pagamento: 'pix',
itens: carrinho.map((i) => ({ produto_id: i.id, quantidade: i.qtd })),
}),
});
const corpo = await resposta.json();
if (!resposta.ok) {
// 409 = sem estoque; 422 = dado inválido (corpo.detalhes diz qual campo)
throw new Error(`${corpo.codigo}: ${corpo.erro}`);
}
return corpo.dados;
}
Python — baixa de estoque do PDV
import os, requests
BASE = "https://estoque.lopes.tec.br/api/v1"
CABECALHOS = {
"Authorization": f"Bearer {os.environ['SGEI_TOKEN']}",
"X-Share-Name": os.environ["SGEI_SHARE"],
"X-Key": os.environ["SGEI_KEY"],
"Accept": "application/json",
}
def dar_baixa(sku, quantidade, documento):
produto = requests.get(f"{BASE}/produtos/{sku}", headers=CABECALHOS, timeout=15).json()
resposta = requests.post(
f"{BASE}/estoque/movimentacoes",
headers=CABECALHOS,
json={
"produto_id": produto["dados"]["id"],
"tipo": "saida",
"quantidade": quantidade,
"documento": documento,
"destino": "PDV loja 01",
},
timeout=15,
)
if resposta.status_code == 409:
raise RuntimeError(resposta.json()["erro"]) # estoque insuficiente
resposta.raise_for_status()
return resposta.json()["dados"]
Testar agora
As chamadas partem do seu navegador. As credenciais digitadas aqui ficam apenas nesta aba — nada é enviado para outro lugar nem gravado. Se você já estiver logado no SGEI nesta mesma janela, pode deixar os campos vazios: a sessão do navegador autentica sozinha.
Aguardando…