SGEI — Sistema de Gerenciamento de Estoque Inteligente

API de Integração v1

Interface REST para integrar o estoque com e-commerce, ERP, PDV, marketplaces e qualquer outro sistema. Todas as respostas em JSON, autenticação por três cabeçalhos e limite de uso por credencial.

Começar pela autenticação Voltar ao sistema

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-DD e AAAA-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çalhoConteúdoPara 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.
O token e a key são exibidos uma única vez, no momento da criação. O sistema guarda apenas o hash SHA-256 deles — se o banco vazar, ninguém reconstrói as credenciais. Perdeu? Revogue a credencial e emita outra.

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âmetroPadrãoDescrição
pagina1Página desejada (aceita page como alias).
por_pagina25Itens por página, máximo 200 (alias limit).
busca—Busca textual nos campos principais do recurso (alias q).
ordemvariacoluna 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

GET /produtos

Lista paginada. Filtros: busca, categoria_id, fornecedor_id, estoque_baixo=1, zerado=1, ativo=0|1.

Permissão: produtos.ver

GET /produtos/{id}

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

GET /produtos/estoque-baixo

Produtos no ou abaixo do estoque mínimo. Parâmetro: limite.

Permissão: produtos.ver

GET /produtos/{id}/movimentacoes

Extrato (kardex) do produto. Parâmetros: de, ate, limite.

Permissão: estoque.ver

POST /produtos

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

PUTPATCH /produtos/{id}

Atualiza o cadastro. O campo quantidade é ignorado: saldo só muda por movimentação de estoque.

Permissão: produtos.editar

DELETE /produtos/{id}

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

GET /estoque/movimentacoes

Histórico com filtros produto_id, tipo, documento_tipo, de, ate, busca.

Permissão: estoque.ver

POST /estoque/movimentacoes

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)

POST /estoque/ajuste

Define o saldo exato (contagem física). A quantidade informada passa a ser o novo saldo.

Permissão: estoque.ajustar

GET /estoque/saldo/{produto}

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

GET /estoque/lotes

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
  }
}
Tipos aceitos: 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

GET /categorias

Permissão: categorias.ver

GET /categorias/arvore

Hierarquia pronta para montar um <select>, com nivel e rótulo indentado.

GET /categorias/{id}
POST /categorias

Campos: nome (obrigatório), descricao, categoria_pai_id, cor.

PUT /categorias/{id}
DELETE /categorias/{id}

Devolve 409 se houver produtos ou subcategorias vinculados.

Fornecedores

GET /fornecedores
GET /fornecedores/{id}
POST /fornecedores
PUT /fornecedores/{id}
DELETE /fornecedores/{id}

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

GET /clientes
GET /clientes/{id}
GET /clientes/{id}/resumo

Totais de pedidos, OS, faturas e valor comprado.

POST /clientes
PUT /clientes/{id}
DELETE /clientes/{id}

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

GET /pedidos

Filtros: status, cliente_id, de, ate, busca.

GET /pedidos/{id}

Aceita o id ou o número do pedido. Traz itens e historico.

GET /pedidos/{id}/itens
POST /pedidos

Cria o pedido, grava os itens e baixa o estoque — tudo em uma transação. Se um item não tiver saldo, nada é gravado.

PUT /pedidos/{id}/status

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 }
  ]
}
O preço não é aceito do cliente: cada item é precificado pelo cadastro (promoção, se houver). Isso impede que uma integração comprometida feche pedidos a R$ 0,01. O cliente é localizado pelo CPF/CNPJ ou e-mail e cadastrado automaticamente se for novo.

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

GET /os
GET /os/{id}
POST /os
POST /os/{id}/itens
PUT /os/{id}/status
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 }
  ]
}
Os produtos de uma OS só saem do estoque quando a OS é concluída (PUT /os/{id}/status com "status": "concluida"). Até lá ficam apenas previstos.

Compras e recebimento

GET /compras
GET /compras/{id}

Traz itens (com quantidade pendente) e recebimentos.

POST /compras
PUT /compras/{id}/status
PUT /compras/{id}/aprovacao

Permissão específica: compras.aprovar.

POST /compras/{id}/recebimento

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 }
}
Itens marcados com "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

GET /relatorios/estoque

Posição atual valorizada, item a item.

GET /relatorios/vendas?de=&ate=

Resumo, série diária e ranking por produto.

GET /relatorios/curva-abc

Classificação A/B/C por valor imobilizado, com percentual acumulado.

GET /dashboard

KPIs consolidados, faturamento diário, mais vendidos e distribuição por categoria.

Permissões: relatorios.ver e dashboard.ver.

Utilitários

GET /health

Não exige autenticação. Use para monitoramento externo (uptime).

GET /me

Mostra qual credencial está em uso e quais permissões ela tem. Ótimo primeiro teste.

GET /notificacoes
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

HTTPcodigoQuando acontece / o que fazer
200—Sucesso.
201—Registro criado. O corpo traz o recurso completo.
204—Sucesso sem conteúdo.
400REQUISICAO_INVALIDAJSON malformado ou parâmetro fora do formato.
401NAO_AUTENTICADOFalta um dos três cabeçalhos, ou a credencial é inválida/revogada/expirada.
403PERMISSAO_NEGADAA credencial existe mas não tem a permissão do recurso. O corpo lista as permissões que ela possui.
404NAO_ENCONTRADORecurso ou registro inexistente.
405METODO_NAO_PERMITIDOO caminho existe, mas não nesse método. O cabeçalho Allow traz os aceitos.
409CONFLITORegra de negócio impediu a operação: estoque insuficiente, transição de status inválida, registro com vínculos.
422VALIDACAOCampos inválidos. detalhes traz o erro de cada campo.
429LIMITE_EXCEDIDOEstourou o limite por minuto. Aguarde o tempo do cabeçalho Retry-After.
500ERRO_INTERNOFalha no servidor. O detalhe técnico fica no log; nada sensível é devolvido.
503OFFLINEDevolvido 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-Limit e X-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.
Boa prática: para sincronizar catálogos grandes, pagine com 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óduloAções
produtosver, criar, editar, excluir
estoquever, criar, editar, excluir, ajustar
lotesver, criar, editar, excluir
categoriasver, criar, editar, excluir
fornecedoresver, criar, editar, excluir
clientesver, criar, editar, excluir
pedidosver, criar, editar, excluir
osver, criar, editar, excluir
comprasver, criar, editar, excluir, aprovar
recebimentover, criar, editar, excluir
faturasver, criar, editar, excluir, emitir
relatoriosver, exportar
dashboardver
Princípio do menor privilégio. Uma integração de e-commerce normalmente precisa apenas de 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.

/api/v1
Atalhos:
Aguardando…