API de Integração Drezzo

Referência técnica para integradores. Versão 1.0 · https://api.drezzopay.com/v1

Visão geral

A API expõe os dados comerciais de um estabelecimento — vendas, itens, consumidores, estornos, catálogo e eventos — para consumo por sistemas externos.

Todos os endpoints são GET. A API é somente leitura.

A API não guarda estado do seu lado. Não há campo que marque um registro como “já processado”, nem endpoint para você escrever de volta. O controle do que já foi lido e tratado é seu — chaveie pelo id da venda, que é estável e permanente.

Fluxo típico

1. GET /v1/sales?createdSince=…     lê as vendas novas
2. seu sistema processa
3. GET /v1/returns?createdSince=…   lê os estornos novos
4. seu sistema processa a devolução
5. guarda o timestamp e repete

Uma venda fica disponível assim que é registrada no PDV e seus valores não mudam depois disso — não é preciso esperar o evento acabar.

Autenticação

Authorization: Bearer dz_live_7f3a9c2e8b1d4a6f0e5c3b8a2d7f1e9c

Formato do token:

dz_<ambiente>_<32 hexadecimais>
     │
     ├── live    produção
     └── test    sandbox

O token é vinculado a uma ou mais lojas e determina o que pode ser lido. Pedir uma loja fora do escopo retorna 403.

Tokens são gerados pelo estabelecimento no Dashboard Drezzo, em Configurações → Chaves de API. A Drezzo guarda apenas o hash: um token perdido não pode ser recuperado, apenas revogado e substituído.

Nunca envie o token em query string, grave em log ou versione em repositório. Ele dá acesso de leitura a todas as vendas da loja.

Convenções

Valores monetários

Todo valor monetário é inteiro, em centavos, e o nome do campo sempre termina em Cents. Não existe campo de dinheiro com casa decimal nesta API.

{ "unitPriceCents": 1500 }    // R$ 15,00
{ "totalAmountCents": 3300 }  // R$ 33,00

Datas e horários

TipoFormatoExemplo
Parâmetro de dataYYYY-MM-DD2026-07-20
Timestamp em respostaISO 8601 com offset2026-07-20T23:15:42-03:00

O filtro from/to opera sobre a data do EVENTO, não sobre o horário da transação. Uma venda às 03:40 do dia 21/07, pertencente ao evento do dia 20/07, é retornada em from=2026-07-20&to=2026-07-20. Eventos noturnos viram a madrugada, e é este o comportamento correto para conciliação.

occurredAt é a hora real da venda, não a do registro. O PDV opera offline e sincroniza depois; nessas vendas createdAt (hora do sync) é posterior — já vimos diferenças de mais de 2 horas em produção. Use occurredAt como a hora da venda.

Intervalos são inclusivos nas duas pontas. Janela máxima por consulta: 92 dias.

Paginação por cursor

{
  "data": [ … ],
  "pagination": { "nextCursor": "eyJ0IjoiMjAyNi0…", "hasMore": true }
}

Repita a chamada passando cursor=<nextCursor> até hasMore ser false. limit aceita até 500 (padrão 200).

Não usamos offset de propósito: numa varredura em que linhas novas chegam entre páginas, offset pula registros — e um registro pulado nunca chega ao seu sistema. O cursor ancora na última linha lida.

Parâmetros são validados

Parâmetro de query não reconhecido devolve 400, com o nome do que não foi entendido e a lista do que aquela rota aceita.

GET /v1/sales?customerId=8891

400 {"error":{"code":"invalid_parameter",
     "message":"Parâmetro não reconhecido: customerId. Aceitos nesta rota:
                storeId, eventId, customerDocument, from, to,
                createdSince, cursor, limit.", …}}
Isso é deliberado. Se aceitássemos e ignorássemos, quem digitasse o nome errado de um filtro receberia 200 com a lista inteira, acharia que filtrou e processaria dado de outro consumidor sem nunca perceber.

Busca por consumidor

O consumidor é identificado pelo documento — não expomos id interno de consumidor em lugar nenhum da API.

GET /v1/customers?document=12345678900              o consumidor
GET /v1/sales?customerDocument=12345678900          as compras dele
GET /v1/sales?customerDocument=…&eventId=1001       as compras dele num evento
GET /v1/events?customerDocument=12345678900         os eventos em que esteve

Aceita com ou sem máscara — 123.456.789-00 e 12345678900 chegam ao mesmo lugar. Documento desconhecido devolve lista vazia, nunca 404.

Limites

EscopoLimite
Requisições por token120 / min
Janela por consulta92 dias
Registros por página500

Ao exceder, retornamos 429 com Retry-After. Use backoff exponencial.

Compatibilidade

Mudanças aditivas — novos campos, novos endpoints, novos valores de enum — acontecem sem aviso na v1. Ignore campos desconhecidos e tenha um caso default para valores de enum não previstos. Mudanças incompatíveis ganham nova versão, com no mínimo 6 meses em paralelo.

Sincronização

Duas coleções são append-only: uma linha, uma vez criada, nunca muda.

ColeçãoParâmetro incremental
/v1/salescreatedSince
/v1/returnscreatedSince

Guarde o timestamp da última varredura, passe em createdSince e pagine por cursor. Nada precisa ser relido para detectar alteração, porque não existe alteração — só registros novos.

Estorno é registro novo, não alteração da venda

Quando parte de uma venda é devolvida, a venda original não muda. Um registro novo aparece em /v1/returns.

Se você só ler /v1/sales, nunca vai saber dos estornos.

Veja o que acontece:

22:00  venda 12345 criada (2 chopes, R$ 30,00)
       você lê /v1/sales e processa. tudo certo.

23:30  operador estorna 1 chope dessa venda

23:35  você lê /v1/sales?createdSince=22:00
       -> a venda 12345 NÃO aparece.
          ela não foi alterada — continua idêntica.
          o estorno é um registro separado.

       você lê /v1/returns?createdSince=22:00
       -> aqui está: 1 unidade, R$ 15,00 devolvidos

Se você reler a venda 12345 diretamente, aí sim os campos returnedQuantity e returnedAmountCents virão preenchidos — eles refletem a situação no momento da leitura. Mas você não tem como saber que precisa relê-la, porque ela não aparece em nenhuma varredura incremental.

Por isso: percorra os dois streams. Vendas em /v1/sales, estornos em /v1/returns.

Estorno é parcial por item: quantity pode ser menor que a quantidade vendida — no exemplo acima, 1 chope de 2. Use refundAmountCents como o valor da devolução, nunca o total da venda.

Evento aberto não é problema

Você pode processar vendas de um evento em andamento. A venda é final no instante em que é registrada

O que continua se movendo enquanto o evento está open são apenas os agregados do evento em /v1/eventstotalAmountCents, customerCount, productsSold. Se o seu sistema faz conferência ou fechamento contra esses números, aí sim espere status: "closed". Para as vendas em si, não há espera.

Lojas

GET /v1/stores

Lojas que a chave enxerga. Sem parâmetros. Loja é o estabelecimento com CNPJ próprio. Os pontos de venda (bares) compartilham o CNPJ da loja e não são entidades separadas.

{
  "data": [{
    "id": "6",
    "name": "Drezzo Bar",
    "document": "12345678000190",
    "timezone": "America/Sao_Paulo",
    "posLocations": [
      { "id": "12", "name": "Bar Central" }
    ]
  }]
}

Eventos

GET /v1/events?storeId={id}&from={data}&to={data}
ParâmetroDescrição
from, toJanela de datas
customerDocumentSó os eventos em que esse consumidor esteve (pelo check-in)
cursor, limitPaginação
CampoTipoDescrição
idstringIdentificador do evento
namestringNome
datestringData (YYYY-MM-DD)
statusenumscheduled, open, closed
totalAmountCentsintFaturamento
customerCountintConsumidores
productsSoldintItens vendidos

Vendas

GET /v1/sales?storeId={id}&eventId={id}
GET /v1/sales/{id}?storeId={id}

endpoint principal Uma venda vem completa: itens, consumidor, taxa de serviço e totais. Não é preciso juntar endpoints para montar o registro.

ParâmetroDescrição
storeIdObrigatório se a chave cobrir mais de uma loja
eventIdFiltra por evento
customerDocumentFiltra pelo documento do consumidor (CPF/CNPJ, com ou sem máscara)
from, toJanela sobre a data do evento
createdSinceSincronização incremental (ISO 8601)
cursor, limitPaginação
{
  "data": [{
    "id": "12345",
    "storeId": "6",
    "orderNumber": 42,
    "event": { "id": "1001", "name": "Réveillon 2026", "date": "2026-07-20" },
    "occurredAt": "2026-07-20T23:15:42-03:00",
    "posLocation": { "id": "12", "name": "Bar Central" },
    "operator": { "id": "44", "name": "Maria Souza" },
    "customer": {
      "name": "João Silva",
      "document": "12345678900",
      "documentType": "CPF",
      "email": "joao@email.com",
      "phone": "11987654321"
    },
    "paymentMethod": "prepaid",
    "items": [{
      "id": "77001",
      "productId": "98765",
      "sku": "CHOPP300",
      "name": "Chopp 300ml",
      "categoryId": "5",
      "categoryName": "Chopes",
      "quantity": 2,
      "unit": "UN",
      "unitPriceCents": 1500,
      "grossAmountCents": 3000,
      "discountAmountCents": 0,
      "netAmountCents": 3000,
      "returnedQuantity": 0,
      "returnedAmountCents": 0
    }],
    "serviceFee": { "rateBps": 1000, "amountCents": 300, "waived": false },
    "totals": {
      "grossAmountCents": 3000,
      "discountAmountCents": 0,
      "netAmountCents": 3000,
      "serviceFeeAmountCents": 300,
      "returnedAmountCents": 0,
      "totalAmountCents": 3300
    },
    "createdAt": "2026-07-20T23:15:42-03:00"
  }],
  "pagination": { "nextCursor": "eyJ0IjoiMjAyNi0…", "hasMore": true }
}

Identidades garantidas

netAmountCents   = grossAmountCents − discountAmountCents
totalAmountCents = netAmountCents + serviceFeeAmountCents − returnedAmountCents

E, dentro de uma venda, sum(items[].discountAmountCents) fecha exatamente com totals.discountAmountCents. O desconto é registrado por venda e rateado por item proporcionalmente ao valor, com o resto do arredondamento no item de maior valor.

totalAmountCents pode ser negativo. Acontece assim:

venda    1 × X-Tudo .......... R$ 28,50
         desconto ........... −R$ 10,00
         cliente pagou ...... R$ 18,50

estorno  registrado como ..... R$ 28,50   (preço cheio, sem o desconto)

totals   18,50 − 28,50 = −R$ 10,00

O valor do estorno é gravado a partir do preço do produto, sem abater o desconto que havia na venda. Numa venda com desconto que foi cancelada, o devolvido fica maior que o cobrado e o total vira negativo.

Não use totals como base da devolução. Use o refundAmountCents de /v1/returns, item a item — é o valor que o sistema efetivamente registrou como estornado.

Observações de campo

Estornos

GET /v1/returns?storeId={id}&createdSince={timestamp}

Stream append-only de devoluções. É o canal para detectar estorno.

{
  "data": [{
    "id": "551",
    "saleId": "12345",
    "storeId": "6",
    "eventId": "1001",
    "productId": "98765",
    "sku": "CHOPP300",
    "productName": "Chopp 300ml",
    "quantity": 1,
    "refundAmountCents": 1500,
    "reason": "desistencia_com_estoque",
    "occurredAt": "2026-07-21T01:02:00-03:00"
  }],
  "pagination": { "nextCursor": null, "hasMore": false }
}

Case saleId com o registro que você já processou e trate a devolução por refundAmountCents. Um mesmo produto pode ter mais de um estorno na mesma venda.

Ao usar eventId, uma página pode vir com menos itens que limit — ou vazia — ainda com hasMore: true. Nenhum registro se perde: percorra até hasMore ser false, como em qualquer outra rota.

Produtos

GET /v1/products?storeId={id}&updatedSince={timestamp}

Catálogo dos produtos da loja. Se o seu sistema precisa de atributos próprios por produto (classificação, tributação, códigos internos), mantenha-os do seu lado chaveados por id ou sku.

CampoTipoDescrição
id, sku, namestringIdentificação. sku pode ser nulo.
categoryId, categoryNamestringCategoria
priceCentsintPreço atual
unitstringUnidade comercial
typeenumVer tabelas
activeboolAtivo
updatedAtstringÚltima alteração
Produtos excluídos somem do catálogo mas continuam aparecendo em vendas antigas. Chaveie pelo id, não pela presença no catálogo.

Consumidores

GET /v1/customers?storeId={id}&document={documento}
GET /v1/customers?storeId={id}&updatedSince={timestamp}

Cadastro de consumidores da loja. O consumidor já vem embutido em cada venda de /v1/sales — este endpoint serve para buscar por documento e para sincronizar dado mestre.

ParâmetroDescrição
documentBusca exata por documento (CPF/CNPJ, com ou sem máscara)
updatedSinceSincronização incremental
cursor, limitPaginação
Consumidor de outra loja não é encontrado. O cadastro é global na Drezzo, mas a busca é sempre restrita às lojas da sua chave — buscar um documento que só existe em outro estabelecimento devolve lista vazia, não o consumidor.
CampoTipoDescrição
documentstringIdentificador do consumidor. Apenas dígitos quando CPF/CNPJ.
namestringNome
documentTypeenumCPF, CNPJ, PASSPORT, NONE
email, phonestringContato. email frequentemente nulo.
birthDatestringNascimento
updatedAtstringÚltima alteração

Tabelas de referência

Meios de pagamento paymentMethod

ValorDescrição
cashDinheiro
creditCartão de crédito
debitCartão de débito
pixPIX
prepaidDebitado de saldo pré-carregado
postpaidConta pós-paga
voucherVale-refeição
compCortesia (valor pago zero)
otherOutros
Recarga não é venda. No pré-pago, o consumidor carrega saldo e depois consome contra ele — a recarga é aporte de crédito, não a saída de um produto. Esta API expõe o consumo: cada venda aparece em /v1/sales com paymentMethod: "prepaid", indicando que o dinheiro entrou antes, na recarga. Não há endpoint de recargas.

Tipos de produto type

ValorDescrição
productMercadoria
entranceEntrada / couvert
comboCombo
supplyInsumo (não vendido diretamente)
configurableProduto montável

Status de evento status

ValorDescrição
scheduledAgendado, ainda não iniciado
openEm operação — os agregados do evento ainda mudam. As vendas em /v1/sales já são finais.
closedEncerrado — agregados do evento finais

Erros

{
  "error": {
    "code": "invalid_parameter",
    "message": "`from` deve estar no formato YYYY-MM-DD.",
    "field": "from",
    "requestId": "req_01HQ8XZ4K9M2N7P3"
  }
}
StatuscodeSignificado
400invalid_parameterParâmetro ausente ou malformado
401unauthorizedToken ausente, inválido ou revogado
403forbiddenSem acesso ao recurso
404not_foundRecurso inexistente
422unprocessableSemanticamente inválido (ex.: to antes de from)
429rate_limitedLimite excedido — respeite o Retry-After
500internal_errorErro interno; pode ser repetido

Cite sempre o requestId ao reportar um problema — ele localiza a requisição exata nos logs da Drezzo.

Checklist de homologação

  1. Autenticar e listar lojas.
  2. Percorrer um evento completo com paginação por cursor até hasMore: false.
  3. Conferir as identidades de valores em todas as vendas do evento.
  4. Sincronizar incrementalmente com createdSince e confirmar que nada é relido nem perdido.
  5. Registrar um estorno parcial e confirmar que ele aparece em /v1/returns — é o teste que mais importa.
  6. Tratar 429 com backoff exponencial.
  7. Confirmar que uma venda offline traz occurredAt anterior a createdAt.

Primeira chamada

curl -H "Authorization: Bearer dz_live_…" \
  https://api.drezzopay.com/v1/stores