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).
- Abra Configurações › API (grupo Acesso).
- No cartão API para BI, clique em Nova chave.
- Dê um nome que diga onde ela vai ser usada (ex.: “Power BI do escritório”).
- Em O que pode ler, marque Vendas, Estoque e/ou Produtos. Marque só o que o BI precisa.
- Em Lojas, marque as lojas que a chave enxerga. Nenhuma marcada vale para todas, inclusive as que você abrir depois.
- Se quiser, ponha uma validade em dias. Em branco, a chave não vence.
- 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:
desdeeate(período da venda) estatus(OPEN,PAIDouCANCELED). - Estoque:
produto(o id de um produto). - Produtos:
busca(nome, código ou código de barras) eativo(trueoufalse).
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 é:
- Na primeira vez, puxe tudo, página por página.
- Guarde a data e hora em que começou.
- 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
- Em Configurações › API, no cartão Webhooks, clique em Novo webhook.
- Informe o endereço que vai receber os avisos. Precisa começar com
https://e ser público na internet. - Marque quais avisos quer receber. Nenhum marcado = todos.
- 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:
- Pega o corpo exatamente como chegou (os bytes, antes de converter o JSON).
- Calcula o HMAC-SHA256 de
timestamp + "." + corpocom o segredo e compara com a assinatura, usando comparação de tempo constante. - 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.