Pular para o conteúdo principal

Endpoints PDV/ERP

Download da spec OpenAPI · Versão JSON · Swagger UI Interativo

Endpoints destinados à integração de sistemas de caixa (PDV) e ERP. Todos utilizam autenticação via X-Api-Key.

URL base de produção: https://api.elofiel.com.br URL base de sandbox: https://api-sandbox.elofiel.com.br

Header obrigatório em todas as chamadas:

X-Api-Key: SUA_API_KEY_AQUI
Content-Type: application/json

POST /api/v1/venda

Registra uma venda e credita o benefício de fidelidade ao cliente.

Request

Body: VendaPdvRequest

CampoTipoObrigatórioDescrição
cnpjLojastringSimCNPJ do estabelecimento (com ou sem formatação)
cpfClientestringSimCPF do cliente (com ou sem formatação)
valorVendanumberSimValor total da venda em R$ (decimal > 0)
documentoExternostringNãoIdentificador do PDV/ERP (ex.: número do cupom fiscal, máx. 100 chars)
observacaostringNãoObservação livre (máx. 500 chars); null é aceito
clienteClienteAutoRequestNãoDados para auto-cadastro do cliente se o CPF ainda não existir

Sub-objeto cliente (opcional — para auto-cadastro):

CampoTipoObrigatórioDescrição
cliente.nomestringSim (se cliente presente)Nome completo (mín. 2, máx. 150 chars)
cliente.emailstringNãoE-mail do cliente
cliente.telefonestringNãoTelefone do cliente
Auto-cadastro

Se o CPF já existir no sistema, o objeto cliente é ignorado — o cadastro existente é mantido. Se o CPF não existir e cliente não for enviado, o cliente é criado sem nome.

Exemplo (com auto-cadastro):

{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "123.456.789-09",
"valorVenda": 150.00,
"documentoExterno": "PDV-001234",
"observacao": null,
"cliente": {
"nome": "Maria Silva",
"email": "maria@email.com",
"telefone": "(44) 99999-0000"
}
}

Response

HTTP 200ApiResult<VendaResponse>

{
"sucesso": true,
"mensagem": "Venda registrada com sucesso.",
"dados": {
"vendaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"clienteId": "8e9f1234-abcd-4321-efgh-000000000001",
"clienteNome": "Maria Silva",
"valorVenda": 150.00,
"beneficiosAplicados": [
{
"movimentacaoId": "aaaaaaaa-1111-2222-3333-444444444444",
"campanhaId": "cccccccc-aaaa-bbbb-dddd-eeeeeeeeeeee",
"campanhaDescricao": "Cashback 5% Verão",
"tipoBeneficio": "Cashback",
"pontosCreditados": 0,
"cashbackCreditado": 7.50,
"statusBeneficio": "Disponivel",
"dataLiberacao": null,
"dataExpiracao": "2026-09-29"
},
{
"movimentacaoId": "bbbbbbbb-5555-6666-7777-888888888888",
"campanhaId": "ffffffff-1111-2222-3333-444444444444",
"campanhaDescricao": "1 ponto por real",
"tipoBeneficio": "Pontos",
"pontosCreditados": 150,
"cashbackCreditado": 0,
"statusBeneficio": "Disponivel",
"dataLiberacao": null,
"dataExpiracao": "2027-05-20"
}
],
"pontosCreditados": 150,
"cashbackCreditado": 7.50,
"saldoAtual": {
"saldoPontos": 150,
"saldoCashback": 22.50,
"saldoPendente": 0.00
}
},
"erros": []
}

Campos da resposta (dados):

CampoTipoDescrição
vendaIduuidID agrupador da venda. Compartilhado por todos os elementos de beneficiosAplicados[]; usado pelo cancelamento bulk
clienteIduuidID do cliente
clienteNomestring | nullNome do cliente (null se criado sem nome)
valorVendanumberValor bruto da venda registrado
beneficiosAplicadosBeneficioAplicado[]Lista de 1 ou 2 benefícios aplicados (max 1 Cashback + 1 Pontos por loja — issue #3)
pontosCreditadosnumberAgregado — soma dos pontos creditados em todas as campanhas
cashbackCreditadonumberAgregado — soma do cashback creditado em todas as campanhas
cashbackUsadonumberModelo 2 — cashback do saldo anterior usado como desconto. 0 quando usarCashbackDisponivel: false
descontoGeradonumberModelo 3 — cashback gerado por esta venda e aplicado como desconto imediato. 0 quando usarCashbackGerado: false
valorFinalnumberValor líquido pago: valorVenda − cashbackUsado − descontoGerado
saldoAtualSaldoResponseSaldo atualizado do cliente após a venda

Cada elemento de beneficiosAplicados:

CampoTipoDescrição
movimentacaoIduuidID único da movimentação gerada por esta campanha
campanhaIduuidID da campanha que gerou este benefício
campanhaDescricaostringNome da campanha
tipoBeneficiostring"Cashback" ou "Pontos"
pontosCreditadosnumberPontos por esta campanha (zero se Cashback)
cashbackCreditadonumberCashback em R$ por esta campanha (zero se Pontos)
statusBeneficiostring"Disponivel" ou "Pendente"
dataLiberacaodate | nullData de liberação do cashback diferido
dataExpiracaodate | nullData de expiração do lote
Issue #3 — Categoria de Benefício

A partir de 2026-05-20, cada loja pode operar com até 1 campanha de Cashback + 1 de Pontos simultaneamente. Cada venda elegível aplica TODAS as campanhas elegíveis, gerando 1 elemento em beneficiosAplicados[] por campanha. Cancelar uma venda cancela TODOS os benefícios dela (bulk forçado).

Campos de desconto

cashbackUsado e descontoGerado são mutuamente exclusivos — no máximo um será > 0 em cada resposta. valorFinal reflete sempre o valor efetivamente cobrado do cliente.

Campos do request — desconto (novos):

CampoTipoPadrãoDescrição
usarCashbackDisponivelbooleanfalseModelo 2 — aplica o saldo de cashback acumulado como desconto. Requer permiteCheckoutCashbackNaVenda: true na configuração
usarCashbackGeradobooleanfalseModelo 3 — aplica o cashback que esta venda geraria como desconto imediato. Requer permiteDescontoSobreGerado: true na configuração. Chamar POST /api/v1/venda/preview antes para exibir o valor ao cliente
Exclusividade mútua

usarCashbackDisponivel e usarCashbackGerado não podem ser true ao mesmo tempo. A API retorna 422 se ambos forem enviados como true.

Possíveis erros:

HTTPCausa
400Campo obrigatório ausente, formato inválido ou cliente.nome em branco quando cliente é enviado
401X-Api-Key inválida ou ausente
422usarCashbackDisponivel e usarCashbackGerado ambos true
500Erro interno

POST /api/v1/venda/preview

Calcula o desconto previsto do Modelo 3 (usarCashbackGerado) sem gravar nada. Use este endpoint para exibir o valor do desconto ao cliente antes de confirmar a venda.

Read-only

/venda/preview não cria movimentações, não altera saldo e não tem efeitos colaterais. Pode ser chamado quantas vezes necessário — inclusive quando o cliente ainda está decidindo.

Request

Body: VendaPreviewRequest

CampoTipoObrigatórioDescrição
cnpjLojastringSimCNPJ do estabelecimento (com ou sem formatação)
valorVendanumberSimValor total da venda em R$ (decimal > 0)

Exemplo:

{
"cnpjLoja": "12.345.678/0001-90",
"valorVenda": 200.00
}

Response

HTTP 200ApiResult<VendaPreviewResponse>

{
"sucesso": true,
"mensagem": "Preview calculado com sucesso.",
"dados": {
"valorVenda": 200.00,
"permiteAplicarDesconto": true,
"motivoBloqueio": null,
"cashbackGeradoPrevisto": 20.00,
"valorComDesconto": 180.00,
"cashbackSobreDesconto": 18.00
}
}

Quando o desconto não está disponível (campanha sem Modelo 3, valor abaixo do mínimo, etc.):

{
"sucesso": true,
"dados": {
"valorVenda": 200.00,
"permiteAplicarDesconto": false,
"motivoBloqueio": "Campanha não permite uso do cashback gerado como desconto imediato.",
"cashbackGeradoPrevisto": 0,
"valorComDesconto": 0,
"cashbackSobreDesconto": 0
}
}

Campos da resposta (dados):

CampoTipoDescrição
permiteAplicarDescontobooleantrue = desconto disponível; false = não aplicar (ver motivoBloqueio)
motivoBloqueiostring | nullExplicação quando permiteAplicarDesconto: false. null quando desconto está disponível
valorVendanumberValor bruto informado no request
cashbackGeradoPrevistonumberValor que será descontado (cashback que esta venda geraria). 0 se bloqueado
valorComDescontonumberValor que o cliente pagará após o desconto. 0 se bloqueado
cashbackSobreDescontonumberNovo cashback gerado sobre valorComDesconto quando permiteCashbackEmResgate: true na campanha. 0 quando permiteCashbackEmResgate: false
cashbackSobreDesconto = 0

Quando a campanha tem permiteCashbackEmResgate: false, o novo cashback é calculado sobre o valorVenda original — não sobre valorComDesconto. O campo cashbackSobreDesconto retorna 0 neste caso; o cliente ainda acumula cashback, mas o valor real só é conhecido após confirmar a venda.

Possíveis erros:

HTTPCausa
400Campo obrigatório ausente ou valorVenda <= 0
401X-Api-Key inválida
422CNPJ inválido ou loja não encontrada

GET /api/v1/resgate/saldo

Consulta o saldo de cashback e pontos de um cliente.

Request

Query parameters:

ParâmetroTipoObrigatórioDescrição
cnpjLojastringSimCNPJ do estabelecimento
cpfClientestringSimCPF do cliente

Exemplo:

GET /api/v1/resgate/saldo?cnpjLoja=12.345.678%2F0001-90&cpfCliente=123.456.789-09

Response

HTTP 200ApiResult<ClienteSaldoResponse>

{
"sucesso": true,
"dados": {
"clienteId": "8e9f1234-abcd-4321-efgh-000000000001",
"clienteNome": "Maria Silva",
"saldoPontos": 150,
"saldoCashback": 22.50,
"saldoPendente": 5.00,
"lotesAtivos": [
{
"id": "lote-uuid-001",
"tipoBeneficio": "Cashback",
"valorOriginal": 10.00,
"valorDisponivel": 7.50,
"status": "Disponivel",
"dataLiberacao": null,
"dataExpiracao": "2026-09-29"
},
{
"id": "lote-uuid-002",
"tipoBeneficio": "Cashback",
"valorOriginal": 5.00,
"valorDisponivel": 5.00,
"status": "Pendente",
"dataLiberacao": "2026-04-10",
"dataExpiracao": "2026-10-07"
}
]
},
"mensagem": null,
"erros": []
}

Campos principais:

CampoTipoDescrição
clienteIduuidID do cliente
clienteNomestringNome do cliente
saldoPontosnumberPontos disponíveis para resgate
saldoCashbacknumberCashback em R$ disponível (liberado)
saldoPendentenumberCashback diferido bloqueado (não disponível)
lotesAtivosLoteResponse[]Detalhamento por lote de crédito

Campos de cada lote (lotesAtivos[]):

CampoTipoDescrição
iduuidID do lote
tipoBeneficiostring"Cashback" ou "Pontos"
valorOriginalnumberValor original creditado no lote
valorDisponivelnumberSaldo atual disponível neste lote
statusstring"Disponivel", "Pendente", "Resgatado" ou "Expirado"
dataLiberacaodate | nullData de liberação (apenas lotes pendentes)
dataExpiracaodate | nullData de expiração do lote
Uso dos lotes

Para a maioria dos PDVs, os campos saldoCashback e saldoPontos são suficientes. Os lotesAtivos são úteis para exibir detalhes ao cliente (ex.: "R$ 5,00 disponível até set/2026").

Possíveis erros:

HTTPCausa
400Parâmetro ausente
401X-Api-Key inválida
404Cliente não encontrado para o tenant

POST /api/v1/resgate

Registra o resgate de cashback ou pontos como desconto.

Request

Body: ResgatePdvRequest

CampoTipoObrigatórioDescrição
cnpjLojastringSimCNPJ do estabelecimento
cpfClientestringSimCPF do cliente
tipoBeneficiostringSim"Cashback" ou "Pontos"
valorResgatenumber | nullNãoValor em R$ a resgatar (Cashback). null = resgatar tudo disponível
pontosResgatenumber | nullNãoQuantidade de pontos a resgatar. null = resgatar tudo disponível
documentoExternostringNãoIdentificador do PDV (máx. 100 chars)
observacaostringNãoObservação livre (máx. 500 chars)
descricaoTrocastringNãoDescrição do brinde ou produto entregue ao cliente (máx. 200 chars). Somente para tipoBeneficio: "Pontos" — exibido no histórico do app clube. Ignorado para Cashback.

Exemplo — resgate parcial de cashback:

{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "123.456.789-09",
"tipoBeneficio": "Cashback",
"valorResgate": 10.00,
"pontosResgate": null,
"documentoExterno": "PDV-001235",
"observacao": null
}

Response

HTTP 200ApiResult<ResgateResponse>

{
"sucesso": true,
"mensagem": "Resgate realizado com sucesso.",
"dados": {
"movimentacaoId": "7ab12345-1234-5678-9012-abcdef000001",
"clienteId": "8e9f1234-abcd-4321-efgh-000000000001",
"clienteNome": "Maria Silva",
"tipoBeneficio": "Cashback",
"pontosResgatados": 0,
"cashbackResgatado": 10.00,
"saldoAtual": {
"saldoPontos": 0,
"saldoCashback": 12.50,
"saldoPendente": 5.00
}
},
"erros": []
}

Possíveis erros:

HTTPCausaerros[0]
400Campo obrigatório ausente"O campo TipoBeneficio é obrigatório."
401X-Api-Key inválida"Não autorizado."
422Saldo insuficiente"Saldo insuficiente para resgate."
422Mínimo não atingido"Cashback mínimo não atingido."
HTTP 422 — não tente novamente sem corrigir

Erros 422 indicam violação de regra de negócio (saldo insuficiente, mínimo não atingido). A requisição está correta sintaticamente — reenviar sem alterar os dados resultará no mesmo erro.


POST /api/v1/cliente

Cadastra um novo cliente no programa de fidelidade. Útil para pré-registrar o cliente no balcão ou totem antes de qualquer venda.

Request

Body: CadastrarClientePdvRequest

CampoTipoObrigatórioDescrição
cpfstringSimCPF do cliente (com ou sem formatação, 11-14 chars)
nomestringSimNome completo (mín. 2, máx. 150 chars)
emailstringSimE-mail válido (máx. 150 chars). Enviar "" quando não disponível
telefonestringSimTelefone (máx. 20 chars). Enviar "" quando não disponível
dataNascimentostringNãoData de nascimento no formato YYYY-MM-DD
aceiteLgpdbooleanSimDeve ser true. Recusa resulta em erro 422
Idempotente

Se o CPF já estiver cadastrado, o endpoint retorna os dados existentes sem modificá-los e sinaliza com jaExistia: true. É seguro chamar múltiplas vezes.

Exemplo:

{
"cpf": "123.456.789-09",
"nome": "Maria Silva",
"email": "maria@email.com",
"telefone": "(44) 99999-0000",
"dataNascimento": "1990-05-20",
"aceiteLgpd": true
}

Response

HTTP 201 — Cliente novo cadastrado HTTP 200 — CPF já cadastrado (retorna dados existentes)

{
"sucesso": true,
"mensagem": "Cliente cadastrado com sucesso.",
"dados": {
"clienteId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"cpf": "12345678909",
"nome": "Maria Silva",
"email": "maria@email.com",
"telefone": "44999990000",
"jaExistia": false
},
"erros": []
}
CampoTipoDescrição
clienteIduuidID do cliente no EloFiel
cpfstringCPF normalizado (apenas dígitos)
jaExistiabooleantrue se o CPF já estava cadastrado (dados não foram alterados)

Possíveis erros:

HTTPCausa
422aceiteLgpd: false
422CPF inválido
400Nome ausente ou fora do limite de tamanho

GET /api/v1/cliente/buscar

Consulta um cliente já cadastrado na plataforma EloFiel por CPF ou QR Code. Útil quando o operador do PDV está atendendo o cliente e quer recuperar nome, e-mail e telefone para preencher o atendimento sem digitação.

A consulta busca na base global do clube EloFiel — retorna dados cadastrais de qualquer cliente da plataforma, não apenas dos já vinculados à sua loja. O vínculo per-tenant é materializado automaticamente na primeira venda (POST /api/v1/venda).

Read-only

Este endpoint não cria nenhum registro nem altera estado. É 100% leitura.

Request

Query string — exatamente um dos parâmetros:

ParâmetroTipoObrigatórioDescrição
cpfstringExcludente com qrCPF do cliente (com ou sem formatação)
qruuidExcludente com cpfQrToken do cliente (Guid)

Exemplos:

GET /api/v1/cliente/buscar?cpf=12345678909
GET /api/v1/cliente/buscar?cpf=123.456.789-09
GET /api/v1/cliente/buscar?qr=e2e00000-0000-0000-0001-000000000002

Response

HTTP 200ApiResult<ClienteBuscaResponse>

{
"sucesso": true,
"mensagem": null,
"dados": {
"cpf": "123.456.789-09",
"nome": "Maria Silva",
"email": "maria@email.com",
"telefone": "44999990000",
"dataNascimento": "1990-05-20",
"whatsappJid": "5544999990000@s.whatsapp.net",
"cadastradoEm": "2025-08-12T10:00:00Z"
},
"erros": []
}
CampoTipoDescrição
cpfstringCPF formatado (xxx.xxx.xxx-xx)
nomestringNome completo do cliente
emailstring?E-mail cadastral (pode ser null)
telefonestring?Telefone cadastral (pode ser null)
dataNascimentostring?Data de nascimento (YYYY-MM-DD, pode ser null)
whatsappJidstring?Número verificado no WhatsApp (pode ser null)
cadastradoEmstringTimestamp do cadastro global do cliente (ISO 8601 UTC)
Dados retornados

O endpoint retorna apenas dados cadastrais. Saldo, cashback, pontos e histórico de compras não atravessam tenants — são segregados por estabelecimento e acessíveis apenas via GET /api/v1/resgate/saldo e endpoints autenticados por JWT do operador.

Possíveis erros:

HTTPCausa
401X-Api-Key ausente ou inválido
404CPF ou QrToken não existe na plataforma
422Nem cpf nem qr informados, ou ambos informados simultaneamente
422CPF inválido (DV incorreto)
422qr não é um UUID — tipicamente o CPF mandado no parâmetro errado
429Rate limit estourado
qr não é o CPF

O parâmetro qr espera o QR Token (UUID gerado pelo app do clube), não o CPF do cliente. Mandar ?qr=12345678909 retorna 422 com a mensagem indicando o formato esperado. Para consultar por CPF use ?cpf=.

Rate limiting

Defesa em duas camadas para inviabilizar varredura de CPFs:

CamadaQuotaParticionado por
Limite por IP do integrador60 req/minIP de origem
Limite por CPF (filtro do issue #389)3 req/hCPF normalizado

O limite por CPF aplica-se apenas quando a consulta usa cpf — consultas por qr ficam protegidas só pelo limite por IP, já que o QrToken é um Guid opaco e enumeração é inviável.

Exemplo: curl

curl -X GET "https://api.elofiel.com.br/api/v1/cliente/buscar?cpf=12345678909" \
-H "X-Api-Key: $ELOFIEL_API_KEY"

Exemplo: Delphi (TNetHTTPClient)

var
Client: TNetHTTPClient;
Response: IHTTPResponse;
Cpf: string;
begin
Cpf := '12345678909';
Client := TNetHTTPClient.Create(nil);
try
Client.CustHeaders['X-Api-Key'] := ApiKey;
Response := Client.Get(
Format('https://api.elofiel.com.br/api/v1/cliente/buscar?cpf=%s', [Cpf]));
case Response.StatusCode of
200: // parse JSON, preencher tela com nome/email/telefone
;
404: // cliente novo — caminho de cadastro
;
429: // recuar / backoff
;
end;
finally
Client.Free;
end;
end;

GET /api/v1/configuracao

Retorna o resumo da configuração de fidelidade para a loja. Deve ser chamado uma única vez na inicialização do PDV/ERP para adaptar a UI ao tipo de campanha ativa.

Request

Query parameters:

ParâmetroTipoObrigatórioDescrição
cnpjLojastringSimCNPJ do estabelecimento

Exemplo:

GET /api/v1/configuracao?cnpjLoja=12.345.678%2F0001-90

Response

HTTP 200ApiResult<ConfiguracaoResponse>

{
"sucesso": true,
"dados": {
"cnpjLoja": "12345678000190",
"nomeLoja": "Loja Central",
"campanhaAtiva": true,
"tipoCampanha": "Cashback",
"permiteCheckoutCashbackNaVenda": true,
"pontosMinimosParaResgate": null,
"permiteUsoParcialDePontos": null
}
}

Campos da resposta (dados):

CampoTipoDescrição
campanhaAtivabooleantrue se há campanha ativa para a loja
tipoCampanhastring | null"Cashback" ou "Pontos". null quando não há campanha ativa
permiteCheckoutCashbackNaVendabooleanModelo 2true = exibir opção de usar cashback acumulado como desconto na venda
permiteDescontoSobreGeradobooleanModelo 3true = exibir opção de usar o cashback desta venda como desconto imediato. Chamar POST /api/v1/venda/preview para calcular o valor antes de confirmar
pontosMinimosParaResgatenumber | nullMínimo de pontos para resgate. null para campanhas Cashback
permiteUsoParcialDePontosboolean | nulltrue = resgate parcial de pontos permitido. null para campanhas Cashback

Possíveis erros:

HTTPCausa
422CNPJ inválido ou loja não encontrada/inativa