Jefácil Jefácil
Documentação / Admin / API e webhooks para BI

O Jefácil tem uma API só de leitura para o seu BI (Power BI, Looker Studio, planilha, sistema próprio) buscar vendas, estoque e produtos, e webhooks que avisam esse sistema na hora em que algo muda. Nada do que passa por aqui altera dados da loja.

Faz parte do plano Multi. Só o dono da empresa cria e revoga chaves e webhooks.

Pedir acesso à API

Antes da primeira chave, a empresa pede acesso. Em Configurações › API, no cartão Acesso à API, diga qual ferramenta vai usar (Power BI, planilha, script, sistema contábil ou outra), para que, e o que precisa ler (Vendas, Estoque, Produtos), e clique em Solicitar acesso.

O cartão mostra em que pé está o pedido:

  • Em análise: o time do Jefácil ainda vai olhar. Dá para Cancelar pedido e pedir de novo.
  • Liberado: aparece o que foi liberado e o botão Nova chave. A chave só pode ler o que foi liberado.
  • Recusado: o motivo aparece no cartão. Use Pedir de novo para corrigir e enviar outro pedido.
  • Suspenso: o acesso foi pausado; o motivo aparece no cartão e não dá para criar chave até liberar.

São até 3 pedidos em 30 dias. Quem já tinha chave antes dessa etapa já aparece como Liberado e as chaves continuam funcionando.

Criar a chave

Só é possível criar chave com o acesso Liberado (veja acima).

  1. Abra Configurações › API (grupo Acesso).
  2. No cartão API para BI, clique em Nova chave.
  3. Dê um nome que diga onde ela vai ser usada (ex.: “Power BI do escritório”).
  4. Em O que pode ler, marque Vendas, Estoque e/ou Produtos. Marque só o que o BI precisa.
  5. Em Lojas, marque as lojas que a chave enxerga. Nenhuma marcada vale para todas, inclusive as que você abrir depois.
  6. Se quiser, ponha uma validade em dias. Em branco, a chave não vence.
  7. Clique em Criar chave e copie a chave na hora. Ela começa com jfk_ e só aparece essa vez: o Jefácil guarda apenas uma impressão dela e não consegue mostrar de novo. Perdeu? Revogue e crie outra.

A empresa pode ter até 5 chaves ativas. Cada uma mostra quando foi usada pela última vez e de qual IP.

Revogar corta o acesso na hora: a próxima chamada com aquela chave já recebe 401. Se a assinatura ficar inativa ou o plano descer abaixo do Multi, as chaves param de funcionar (403) até regularizar, sem precisar criar outras.

ID da integração e rotação de chave

No topo do cartão API para BI fica o ID da integração (começa com ji_), com botão de copiar. Ele identifica a sua empresa na API e também vem em GET /api/v1/eu. Não é senha: sozinho não abre nada.

Para trocar uma chave sem parar o Power BI ou a planilha, use Rotacionar na chave. Escolha em quantos dias a antiga deixa de valer (1, 7 ou 30). O Jefácil cria uma chave nova com os mesmos acessos e mostra o segredo uma única vez, junto com exemplos prontos de curl, Power BI e Python. Nesse prazo as duas chaves funcionam; depois, só a nova. Uma chave só pode ser rotacionada uma vez.

Cada chave mostra o último uso e as chamadas dos últimos 7 dias. Chave parada há 90 dias ou mais ganha um aviso para você revogar. E quando uma chave vai vencer em até 7 dias, o sino avisa o dono.

Chave de teste

Quer montar o Power BI ou a planilha antes da liberação? Em API para BI, use Chave de teste. Ela não precisa de aprovação e começa com jfk_test_. Ela lê só uma empresa de demonstração com dados de exemplo — nunca os dados da sua empresa — e tem limite menor de chamadas (20 por minuto). As respostas trazem o cabeçalho X-Ambiente: teste, e GET /api/v1/eu mostra "ambiente": "teste". Na lista, a chave aparece com o selo TESTE. Quando o acesso for liberado, crie a chave de produção e troque a chave no seu BI; o resto da integração continua igual.

Como chamar

Todas as chamadas vão para https://app.jefacil.com.br/api/v1/ com a chave no cabeçalho Authorization:

curl -H "Authorization: Bearer jfk_..." \
  https://app.jefacil.com.br/api/v1/eu

O /eu responde com a empresa, o nome da chave, o que ela pode ler e as lojas dela. É a primeira chamada para conferir se a chave está certa.

Rota Precisa de O que traz
GET /api/v1/eu qualquer chave A empresa e o que a chave enxerga
GET /api/v1/vendas Vendas Vendas com itens, pagamentos e vendedor
GET /api/v1/estoque Estoque Saldo de cada produto (e de cada variante) por loja
GET /api/v1/produtos Produtos Catálogo com preços, variantes e código de barras

A descrição completa (campos, tipos, erros) está em https://app.jefacil.com.br/api/v1/openapi.json, no formato OpenAPI 3.1. Essa rota abre sem chave e serve para gerar cliente automático ou importar no Postman.

Chave de outra coisa não entra: o login do sistema e o token do aparelho do balcão não valem aqui, e a chave jfk_ não abre o resto do sistema.

Filtros

Valem para as três listas:

Parâmetro Para quê
limite Quantos registros por página, de 1 a 200. Padrão 100
cursor Pegar a página seguinte (veja abaixo)
atualizado_desde Só o que mudou desde esta data e hora (ISO 8601, ex.: 2026-10-01T00:00:00-03:00)
loja Só uma loja. Também dá pelo cabeçalho X-Store-Id. Loja que a chave não enxerga dá 403

E os de cada uma:

  • Vendas: desde e ate (período da venda) e status (OPEN, PAID ou CANCELED).
  • Estoque: produto (o id de um produto).
  • Produtos: busca (nome, código ou código de barras) e ativo (true ou false).
curl -H "Authorization: Bearer jfk_..." \
  "https://app.jefacil.com.br/api/v1/vendas?desde=2026-10-01&ate=2026-10-31&status=PAID&limite=200"

Páginas

A resposta vem assim:

{
  "dados": [ { "id": "…", "total": 189.9, "status": "PAID" } ],
  "proximo_cursor": "eyJ…"
}

Enquanto proximo_cursor vier preenchido, chame de novo passando ele em cursor (com os mesmos filtros). Quando vier null, acabou. O cursor é opaco: não tente montar nem alterar, senão a resposta é 400 cursor_invalido.

A lista sai em ordem de última alteração, então o jeito certo de manter o BI em dia é:

  1. Na primeira vez, puxe tudo, página por página.
  2. Guarde a data e hora em que começou.
  3. Das próximas vezes, chame com atualizado_desde = essa data e hora.

Venda cancelada depois de puxada volta nessa sincronização com status CANCELED e cancelada_em preenchido. Atualize no BI, não some.

Limites

Cada chave faz até 60 chamadas por minuto e 20.000 por dia. Passou disso, a resposta é 429 rate_limited; os cabeçalhos RateLimit e RateLimit-Policy dizem quanto falta e quando libera. Espere e tente de novo. Para sincronizar, use limite=200 e atualizado_desde em vez de puxar tudo a cada vez.

Todo acesso (os que deram certo e os recusados) fica registrado com data, rota e IP.

Erros

Os erros vêm em JSON com um code que o sistema pode testar:

Status code Quer dizer
400 cursor_invalido, periodo_invalido Cursor alterado ou período desde/ate inválido
401 missing_api_key, invalid_api_key Sem chave, chave errada, revogada ou vencida
403 fora_do_escopo_da_chave A chave não pode ler isto (ex.: chave só de Estoque pedindo vendas)
403 loja_fora_da_chave A loja pedida não está na chave
403 api_indisponivel_no_plano Assinatura inativa ou plano abaixo do Multi
503 sandbox_indisponivel O ambiente de teste não está disponível agora (só chave de teste)
422 validation_error Filtro com valor inválido
429 rate_limited Passou do limite por minuto ou por dia

O que não sai pela API

Por causa da LGPD, a API não entrega dado pessoal de cliente: nome, CPF/CNPJ, telefone, e-mail, endereço e data de nascimento ficam de fora, assim como o CPF na nota, as observações da venda e os dados de cheque.

No lugar do cliente, cada venda traz cliente_ref: um código que é sempre o mesmo para o mesmo cliente dentro da sua empresa, mas não revela quem é. Serve para contar recompra e ticket por cliente. Do vendedor vêm só o id e o primeiro nome.

O custo dos produtos também não sai.

Webhooks

O webhook faz o Jefácil avisar o seu sistema na hora, em vez de ele ficar perguntando de tempos em tempos.

Cadastrar

  1. Em Configurações › API, no cartão Webhooks, clique em Novo webhook.
  2. Informe o endereço que vai receber os avisos. Precisa começar com https:// e ser público na internet.
  3. Marque quais avisos quer receber. Nenhum marcado = todos.
  4. Copie o segredo (começa com whsec_). Ele aparece uma vez só e é com ele que o seu sistema confere que o aviso veio do Jefácil. Perdeu? Use Trocar segredo; o antigo para de valer na hora.

Dá para ter até 5 endereços. O botão Entregas mostra as últimas tentativas de cada um e o erro, se houve.

Os avisos

Evento Quando O que vem em dados
venda.criada Venda fechada no PDV venda_id, loja_id
venda.cancelada Venda cancelada venda_id, loja_id e, se a venda paga foi excluída, excluida: true (ela não volta mais na lista de vendas)
produto.alterado Produto criado, editado, excluído, inativado ou reativado no cadastro, ou preço mudado em lote produto_ids (até 500), total_produtos
estoque.alterado Saldo mudou (agrupado por minuto e por loja) loja_id, produto_ids (até 500), total_produtos, truncado, janela_inicio, janela_fim

Exemplo do corpo:

{
  "id": "0b5c…",
  "evento": "venda.criada",
  "ocorrido_em": "2026-10-15T14:32:08.000Z",
  "dados": { "venda_id": "…", "loja_id": "…" }
}

O aviso leva só os códigos. Os detalhes o seu sistema busca pela API, com a chave dele. Assim nada de dado da venda trafega fora da API.

O aviso é um atalho, não a fonte da verdade. Venda que entra fora do PDV (loja online, marketplace, nota avulsa) não gera venda.criada, e um aviso pode atrasar. Mantenha a sincronização por atualizado_desde rodando de tempos em tempos para fechar qualquer buraco.

Entrega e novas tentativas

O Jefácil manda um POST com o corpo em JSON e espera resposta 2xx em até 5 segundos. Não segue redirecionamento. Se falhar, tenta de novo mais 4 vezes, cerca de 2, 4, 8 e 16 minutos depois de cada falha (5 tentativas no total). Depois disso a entrega fica como Falhou em Entregas. Cada tentativa vai com horário e assinatura novos, e com o mesmo X-Jefacil-Delivery: use-o para não processar o mesmo aviso duas vezes.

Conferir a assinatura

Cada aviso vem com estes cabeçalhos:

Cabeçalho O que é
X-Jefacil-Event O evento (ex.: venda.criada)
X-Jefacil-Delivery O id desta entrega
X-Jefacil-Timestamp Horário do envio, em segundos desde 1970
X-Jefacil-Signature sha256= seguido do HMAC-SHA256, em hexadecimal, de "{timestamp}.{corpo}" com o segredo

Para aceitar um aviso, o seu sistema:

  1. Pega o corpo exatamente como chegou (os bytes, antes de converter o JSON).
  2. Calcula o HMAC-SHA256 de timestamp + "." + corpo com o segredo e compara com a assinatura, usando comparação de tempo constante.
  3. Recusa se o horário tiver mais de 5 minutos de diferença do relógio dele. Isso impede que alguém reenvie um aviso antigo.

Em Node.js (Express):

import crypto from 'node:crypto';
import express from 'express';

const SEGREDO = process.env.JEFACIL_WEBHOOK_SECRET; // whsec_...
const app = express();

app.post('/jefacil', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Jefacil-Timestamp') ?? '';
  const assinatura = req.get('X-Jefacil-Signature') ?? '';
  const corpo = req.body.toString('utf8');

  const idade = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(idade) || idade > 300) return res.sendStatus(400);

  const esperada =
    'sha256=' + crypto.createHmac('sha256', SEGREDO).update(`${timestamp}.${corpo}`).digest('hex');
  const a = Buffer.from(assinatura);
  const b = Buffer.from(esperada);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);

  const aviso = JSON.parse(corpo);
  // aviso.evento, aviso.dados.venda_id ... busque os detalhes pela API
  res.sendStatus(200);
});

Em Python (Flask):

import hashlib, hmac, json, os, time
from flask import Flask, request, abort

SEGREDO = os.environ["JEFACIL_WEBHOOK_SECRET"].encode()  # whsec_...
app = Flask(__name__)

@app.post("/jefacil")
def jefacil():
    timestamp = request.headers.get("X-Jefacil-Timestamp", "")
    assinatura = request.headers.get("X-Jefacil-Signature", "")
    corpo = request.get_data()  # bytes, do jeito que chegou

    try:
        if abs(time.time() - int(timestamp)) > 300:
            abort(400)
    except ValueError:
        abort(400)

    esperada = "sha256=" + hmac.new(
        SEGREDO, timestamp.encode() + b"." + corpo, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(assinatura, esperada):
        abort(401)

    aviso = json.loads(corpo)
    # aviso["evento"], aviso["dados"]["venda_id"] ... busque os detalhes pela API
    return "", 200

Responda rápido (2xx) e faça o trabalho pesado depois: passou de 5 segundos, conta como falha e o Jefácil tenta de novo.

Quem fez o quê

Criar e revogar chave, cadastrar, alterar, trocar o segredo e excluir webhook ficam na auditoria, com quem fez e quando.

Faltou algo ou ficou com dúvida neste guia? Fala com a gente no WhatsApp →