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.
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.
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
| Tipo | Formato | Exemplo |
|---|---|---|
| Parâmetro de data | YYYY-MM-DD | 2026-07-20 |
| Timestamp em resposta | ISO 8601 com offset | 2026-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.", …}}
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
| Escopo | Limite |
|---|---|
| Requisições por token | 120 / min |
| Janela por consulta | 92 dias |
| Registros por página | 500 |
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ção | Parâmetro incremental |
|---|---|
/v1/sales | createdSince |
/v1/returns | createdSince |
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/events —
totalAmountCents, 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
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
| Parâmetro | Descrição |
|---|---|
from, to | Janela de datas |
customerDocument | Só os eventos em que esse consumidor esteve (pelo check-in) |
cursor, limit | Paginação |
| Campo | Tipo | Descrição |
|---|---|---|
id | string | Identificador do evento |
name | string | Nome |
date | string | Data (YYYY-MM-DD) |
status | enum | scheduled, open, closed |
totalAmountCents | int | Faturamento |
customerCount | int | Consumidores |
productsSold | int | Itens vendidos |
Vendas
endpoint principal Uma venda vem completa: itens, consumidor, taxa de serviço e totais. Não é preciso juntar endpoints para montar o registro.
| Parâmetro | Descrição |
|---|---|
storeId | Obrigatório se a chave cobrir mais de uma loja |
eventId | Filtra por evento |
customerDocument | Filtra pelo documento do consumidor (CPF/CNPJ, com ou sem máscara) |
from, to | Janela sobre a data do evento |
createdSince | Sincronização incremental (ISO 8601) |
cursor, limit | Paginaçã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
customerpode sernull— venda a consumidor não identificado.- O consumidor é identificado pelo
document. -
customer.emailé frequentementenull: só é capturado no cadastro pelo aplicativo; quem entra pelo check-in no PDV costuma não fornecer. Trate a ausência sem falhar. serviceFeeénullquando não há taxa.rateBpsem basis points (1000 = 10%).- A taxa de serviço nunca está embutida em
unitPriceCentsnem nos totais de mercadoria.
Estornos
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.
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
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.
| Campo | Tipo | Descrição |
|---|---|---|
id, sku, name | string | Identificação. sku pode ser nulo. |
categoryId, categoryName | string | Categoria |
priceCents | int | Preço atual |
unit | string | Unidade comercial |
type | enum | Ver tabelas |
active | bool | Ativo |
updatedAt | string | Última alteração |
id, não pela presença no catálogo.
Consumidores
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âmetro | Descrição |
|---|---|
document | Busca exata por documento (CPF/CNPJ, com ou sem máscara) |
updatedSince | Sincronização incremental |
cursor, limit | Paginação |
| Campo | Tipo | Descrição |
|---|---|---|
document | string | Identificador do consumidor. Apenas dígitos quando CPF/CNPJ. |
name | string | Nome |
documentType | enum | CPF, CNPJ, PASSPORT, NONE |
email, phone | string | Contato. email frequentemente nulo. |
birthDate | string | Nascimento |
updatedAt | string | Última alteração |
Tabelas de referência
Meios de pagamento paymentMethod
| Valor | Descrição |
|---|---|
cash | Dinheiro |
credit | Cartão de crédito |
debit | Cartão de débito |
pix | PIX |
prepaid | Debitado de saldo pré-carregado |
postpaid | Conta pós-paga |
voucher | Vale-refeição |
comp | Cortesia (valor pago zero) |
other | Outros |
/v1/sales com
paymentMethod: "prepaid", indicando que o dinheiro entrou antes, na recarga.
Não há endpoint de recargas.
Tipos de produto type
| Valor | Descrição |
|---|---|
product | Mercadoria |
entrance | Entrada / couvert |
combo | Combo |
supply | Insumo (não vendido diretamente) |
configurable | Produto montável |
Status de evento status
| Valor | Descrição |
|---|---|
scheduled | Agendado, ainda não iniciado |
open | Em operação — os agregados do evento ainda mudam. As vendas em /v1/sales já são finais. |
closed | Encerrado — agregados do evento finais |
Erros
{
"error": {
"code": "invalid_parameter",
"message": "`from` deve estar no formato YYYY-MM-DD.",
"field": "from",
"requestId": "req_01HQ8XZ4K9M2N7P3"
}
}
| Status | code | Significado |
|---|---|---|
400 | invalid_parameter | Parâmetro ausente ou malformado |
401 | unauthorized | Token ausente, inválido ou revogado |
403 | forbidden | Sem acesso ao recurso |
404 | not_found | Recurso inexistente |
422 | unprocessable | Semanticamente inválido (ex.: to antes de from) |
429 | rate_limited | Limite excedido — respeite o Retry-After |
500 | internal_error | Erro 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
- Autenticar e listar lojas.
- Percorrer um evento completo com paginação por cursor até
hasMore: false. - Conferir as identidades de valores em todas as vendas do evento.
- Sincronizar incrementalmente com
createdSincee confirmar que nada é relido nem perdido. - Registrar um estorno parcial e confirmar que ele aparece em
/v1/returns— é o teste que mais importa. - Tratar
429com backoff exponencial. - Confirmar que uma venda offline traz
occurredAtanterior acreatedAt.
Primeira chamada
curl -H "Authorization: Bearer dz_live_…" \
https://api.drezzopay.com/v1/stores