Emissão em Lote
Cria e acompanha cobranças em lote de forma assíncrona — até 500 itens por requisição.
HABILITAÇÃO NECESSÁRIA
A emissão em lote é liberada por empresa via feature-flag. Se receber 403, entre em contato com o suporte DiviPay para habilitar o recurso na sua conta.
Conceitos
Assíncrono
O POST /api/charge/batch devolve 202 Accepted imediatamente. As cobranças são geradas em background. Acompanhe o progresso por polling (GET .../{id} e GET .../{id}/items) e/ou pelo webhook de conclusão enviado à callbackUrl.
Idempotência
Use o header Idempotency-Key (por lote) e o campo externalId (por item). É seguro reenviar a mesma requisição em caso de timeout ou erro de rede — o lote não será duplicado e cada item é processado apenas uma vez.
Sucesso Parcial
Cada item é processado de forma independente. O lote pode terminar com status PARTIALLY_COMPLETED. Consulte os itens com falha via GET .../{id}/items?status=FAILED e reprocesse com POST .../{id}/retry.
POST /api/charge/batch
Cria um novo lote de cobranças.
Endpoint
POST https://api.divipay.com.br/api/charge/batchHeaders
| Header | Valor | Obrigatório |
|---|---|---|
| Authorization | Bearer | Sim |
| Content-Type | application/json | Sim |
| Idempotency-Key | string único por lote | Sim |
Body Parameters
| Campo | Tipo | Descrição | Obrigatório |
|---|---|---|---|
| items | BatchChargeItem[] | Lista de cobranças (1 a 500 itens) | Sim |
| callbackUrl | string | URL para receber o webhook de conclusão | Não |
Objeto BatchChargeItem
| Campo | Tipo | Descrição | Obrigatório |
|---|---|---|---|
| externalId | string | Sua referência por item — único no lote, usado para idempotência | Sim |
| amount | number | Valor em reais (> 0) | Sim |
| description | string | Descrição da cobrança | Sim |
| name | string | Nome do pagador | Não |
| document | string | CPF ou CNPJ (somente números) | Não |
| string | E-mail do pagador | Não | |
| phone | string | Telefone com DDD (somente números) | Não |
| dueDate | string | Data de vencimento (ISO, ex: "2026-07-10"). Padrão: +7 dias | Não |
| maxInstallments | number | Número máximo de parcelas | Não |
VALIDAÇÃO ATÔMICA
A validação do envelope é atômica: se items estiver vazio, ultrapassar 500 itens, algum item não tiver externalId, amount ou description, ou houver externalId duplicado no mesmo lote, a API retorna 400 e nenhuma cobrança é criada.
Exemplo de Requisição
curl -X POST https://api.divipay.com.br/api/charge/batch \
-H "Authorization: Bearer SEU_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: lote-nov-2026-001" \
-d '{
"callbackUrl": "https://seusite.com/webhook/lote",
"items": [
{
"externalId": "ERP-1001",
"amount": 150.00,
"description": "Mensalidade Novembro/2026",
"name": "João Silva",
"document": "12345678901",
"email": "joao@email.com",
"dueDate": "2026-07-10"
},
{
"externalId": "ERP-1002",
"amount": 320.50,
"description": "Mensalidade Novembro/2026",
"name": "Maria Oliveira",
"document": "98765432100",
"email": "maria@email.com",
"dueDate": "2026-07-10"
},
{
"externalId": "ERP-1003",
"amount": 89.90,
"description": "Renovação de Plano",
"name": "Carlos Souza",
"dueDate": "2026-07-15"
}
]
}'const response = await fetch('https://api.divipay.com.br/api/charge/batch', {
method: 'POST',
headers: {
'Authorization': 'Bearer SEU_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'lote-nov-2026-001'
},
body: JSON.stringify({
callbackUrl: 'https://seusite.com/webhook/lote',
items: [
{
externalId: 'ERP-1001',
amount: 150.00,
description: 'Mensalidade Novembro/2026',
name: 'João Silva',
document: '12345678901',
email: 'joao@email.com',
dueDate: '2026-07-10'
},
{
externalId: 'ERP-1002',
amount: 320.50,
description: 'Mensalidade Novembro/2026',
name: 'Maria Oliveira',
document: '98765432100',
email: 'maria@email.com',
dueDate: '2026-07-10'
},
{
externalId: 'ERP-1003',
amount: 89.90,
description: 'Renovação de Plano',
name: 'Carlos Souza',
dueDate: '2026-07-15'
}
]
})
});
const data = await response.json();import requests
response = requests.post(
'https://api.divipay.com.br/api/charge/batch',
headers={
'Authorization': 'Bearer SEU_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'lote-nov-2026-001'
},
json={
'callbackUrl': 'https://seusite.com/webhook/lote',
'items': [
{
'externalId': 'ERP-1001',
'amount': 150.00,
'description': 'Mensalidade Novembro/2026',
'name': 'João Silva',
'document': '12345678901',
'email': 'joao@email.com',
'dueDate': '2026-07-10'
},
{
'externalId': 'ERP-1002',
'amount': 320.50,
'description': 'Mensalidade Novembro/2026',
'name': 'Maria Oliveira',
'document': '98765432100',
'email': 'maria@email.com',
'dueDate': '2026-07-10'
},
{
'externalId': 'ERP-1003',
'amount': 89.90,
'description': 'Renovação de Plano',
'name': 'Carlos Souza',
'dueDate': '2026-07-15'
}
]
}
)
data = response.json()Resposta de Sucesso
Status: 202 Accepted
{
"batchId": "8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11",
"status": "PENDING",
"totalItems": 3
}POST /api/charge/batch/validate
Valida o envelope do lote sem criar nenhuma cobrança (dry-run).
Endpoint
POST https://api.divipay.com.br/api/charge/batch/validateHeaders
| Header | Valor | Obrigatório |
|---|---|---|
| Authorization | Bearer | Sim |
| Content-Type | application/json | Sim |
Body Parameters
Mesmo body do POST /api/charge/batch (campos items e callbackUrl).
Exemplo de Requisição
curl -X POST https://api.divipay.com.br/api/charge/batch/validate \
-H "Authorization: Bearer SEU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"externalId": "ERP-1001",
"amount": 150.00,
"description": "Mensalidade Novembro/2026"
},
{
"externalId": "ERP-1001",
"amount": 320.50,
"description": "externalId duplicado — vai falhar"
}
]
}'const response = await fetch('https://api.divipay.com.br/api/charge/batch/validate', {
method: 'POST',
headers: {
'Authorization': 'Bearer SEU_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
items: [
{ externalId: 'ERP-1001', amount: 150.00, description: 'Mensalidade Novembro/2026' },
{ externalId: 'ERP-1001', amount: 320.50, description: 'externalId duplicado — vai falhar' }
]
})
});
const result = await response.json();import requests
response = requests.post(
'https://api.divipay.com.br/api/charge/batch/validate',
headers={
'Authorization': 'Bearer SEU_TOKEN',
'Content-Type': 'application/json'
},
json={
'items': [
{'externalId': 'ERP-1001', 'amount': 150.00, 'description': 'Mensalidade Novembro/2026'},
{'externalId': 'ERP-1001', 'amount': 320.50, 'description': 'externalId duplicado — vai falhar'}
]
}
)
result = response.json()Resposta
Status: 200 OK
{
"valid": false,
"items": [
{
"index": 0,
"externalId": "ERP-1001",
"ok": true,
"errors": []
},
{
"index": 1,
"externalId": "ERP-1001",
"ok": false,
"errors": ["externalId duplicado no lote"]
}
]
}GET /api/charge/batch/
Consulta o status e os contadores de um lote.
Endpoint
GET https://api.divipay.com.br/api/charge/batch/{id}Headers
| Header | Valor | Obrigatório |
|---|---|---|
| Authorization | Bearer | Sim |
Path Parameters
| Parâmetro | Tipo | Descrição |
|---|---|---|
| id | string | ID do lote retornado no POST |
Status do Lote
| Status | Descrição |
|---|---|
| PENDING | Lote criado, aguardando processamento |
| PROCESSING | Cobranças sendo geradas em background |
| COMPLETED | Todos os itens processados com sucesso |
| PARTIALLY_COMPLETED | Processamento concluído, mas alguns itens falharam |
| FAILED | Todos os itens falharam |
Exemplo de Requisição
curl -X GET https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11 \
-H "Authorization: Bearer SEU_TOKEN"const response = await fetch(
'https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11',
{
headers: { 'Authorization': 'Bearer SEU_TOKEN' }
}
);
const batch = await response.json();import requests
response = requests.get(
'https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11',
headers={'Authorization': 'Bearer SEU_TOKEN'}
)
batch = response.json()Resposta de Sucesso
Status: 200 OK
{
"batchId": "8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11",
"status": "PARTIALLY_COMPLETED",
"totalItems": 3,
"processedItems": 3,
"succeededItems": 2,
"failedItems": 1,
"createdAt": "2026-06-17T10:00:00.000Z",
"startedAt": "2026-06-17T10:00:05.000Z",
"finishedAt": "2026-06-17T10:00:48.000Z"
}GET /api/charge/batch/{id}/items
Lista os resultados item a item, com suporte a filtro por status e paginação.
Endpoint
GET https://api.divipay.com.br/api/charge/batch/{id}/itemsHeaders
| Header | Valor | Obrigatório |
|---|---|---|
| Authorization | Bearer | Sim |
Query Parameters
| Parâmetro | Tipo | Descrição | Padrão |
|---|---|---|---|
| status | string | Filtra por status do item | — |
| page | number | Página atual (0-based) | 0 |
| limit | number | Itens por página (máx. 500) | 100 |
Status do Item
| Status | Descrição |
|---|---|
| PENDING | Aguardando processamento |
| PROCESSING | Em processamento |
| SUCCEEDED | Cobrança criada com sucesso |
| FAILED | Falhou ao criar a cobrança |
| SKIPPED_DUPLICATE | Ignorado por idempotência (externalId já processado) |
Exemplo de Requisição
curl -X GET "https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11/items?status=FAILED&page=0&limit=100" \
-H "Authorization: Bearer SEU_TOKEN"const params = new URLSearchParams({ status: 'FAILED', page: 0, limit: 100 });
const response = await fetch(
`https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11/items?${params}`,
{
headers: { 'Authorization': 'Bearer SEU_TOKEN' }
}
);
const result = await response.json();import requests
response = requests.get(
'https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11/items',
headers={'Authorization': 'Bearer SEU_TOKEN'},
params={'status': 'FAILED', 'page': 0, 'limit': 100}
)
result = response.json()Resposta de Sucesso
Status: 200 OK
{
"registers": [
{
"index": 0,
"externalId": "ERP-1001",
"status": "SUCCEEDED",
"chargeId": "a1b2c3d4-1234-5678-abcd-ef0123456789"
},
{
"index": 1,
"externalId": "ERP-1002",
"status": "SUCCEEDED",
"chargeId": "b2c3d4e5-2345-6789-bcde-f01234567890"
},
{
"index": 2,
"externalId": "ERP-1003",
"status": "FAILED",
"chargeId": null,
"error": {
"code": "INVALID_DOCUMENT",
"message": "CPF/CNPJ inválido"
}
}
],
"total": 3,
"pages": 1
}ACESSANDO A COBRANÇA
Para cada item com status: "SUCCEEDED", use o chargeId em GET /api/charge/{chargeId} para obter o QR Code Pix, link de pagamento ou boleto.
POST /api/charge/batch/{id}/retry
Re-enfileira apenas os itens com status FAILED para nova tentativa.
Endpoint
POST https://api.divipay.com.br/api/charge/batch/{id}/retryHeaders
| Header | Valor | Obrigatório |
|---|---|---|
| Authorization | Bearer | Sim |
Exemplo de Requisição
curl -X POST https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11/retry \
-H "Authorization: Bearer SEU_TOKEN"const response = await fetch(
'https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11/retry',
{
method: 'POST',
headers: { 'Authorization': 'Bearer SEU_TOKEN' }
}
);
const result = await response.json();import requests
response = requests.post(
'https://api.divipay.com.br/api/charge/batch/8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11/retry',
headers={'Authorization': 'Bearer SEU_TOKEN'}
)
result = response.json()Resposta de Sucesso
Status: 200 OK
{
"batchId": "8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11",
"requeued": 1
}Webhook de Conclusão
Quando o processamento do lote termina, a DiviPay envia um POST para a callbackUrl informada na criação (ou o webhook padrão configurado na conta):
{
"type": "charge_batch.completed",
"batchId": "8f7c6a2d-4a1b-4de8-9f2d-1e84f7b80c11",
"status": "PARTIALLY_COMPLETED",
"totalItems": 100,
"succeededItems": 98,
"failedItems": 2
}WEBHOOKS INDIVIDUAIS
As cobranças geradas continuam disparando seus webhooks normais (ex.: charge.paid / APPROVED) quando o pagador efetuar o pagamento. Consulte Configurar Webhooks para mais detalhes.
Exemplo Completo
Cria o lote, faz polling até concluir e baixa os itens com falha.
const BASE_URL = 'https://api.divipay.com.br';
const TOKEN = 'SEU_TOKEN';
async function apiRequest(method, path, body) {
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
},
body: body ? JSON.stringify(body) : undefined
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw Object.assign(new Error(`HTTP ${res.status}`), { response: err });
}
return res.json();
}
async function createBatch(items, callbackUrl) {
// Idempotency-Key gerada uma vez e armazenada antes de enviar
const idempotencyKey = `lote-${Date.now()}-${Math.random().toString(36).slice(2)}`;
const res = await fetch(`${BASE_URL}/api/charge/batch`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
},
body: JSON.stringify({ items, callbackUrl })
});
if (!res.ok) throw new Error(`Erro ao criar lote: HTTP ${res.status}`);
return res.json(); // { batchId, status, totalItems }
}
async function pollBatch(batchId, { intervalMs = 5000, timeoutMs = 300000 } = {}) {
const deadline = Date.now() + timeoutMs;
const terminalStatuses = new Set(['COMPLETED', 'PARTIALLY_COMPLETED', 'FAILED']);
while (Date.now() < deadline) {
const batch = await apiRequest('GET', `/api/charge/batch/${batchId}`);
console.log(`[${new Date().toISOString()}] Status: ${batch.status} — ${batch.processedItems}/${batch.totalItems} processados`);
if (terminalStatuses.has(batch.status)) return batch;
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
throw new Error(`Timeout: lote ${batchId} não concluiu em ${timeoutMs / 1000}s`);
}
async function getFailedItems(batchId) {
const failed = [];
let page = 0;
while (true) {
const result = await apiRequest(
'GET',
`/api/charge/batch/${batchId}/items?status=FAILED&page=${page}&limit=500`
);
failed.push(...result.registers);
if (result.registers.length === 0 || failed.length >= result.total) break;
page++;
}
return failed;
}
// --- Uso ---
async function main() {
const items = [
{ externalId: 'ERP-1001', amount: 150.00, description: 'Mensalidade Julho/2026', name: 'João Silva', document: '12345678901', email: 'joao@email.com', dueDate: '2026-07-10' },
{ externalId: 'ERP-1002', amount: 320.50, description: 'Mensalidade Julho/2026', name: 'Maria Oliveira', document: '98765432100', email: 'maria@email.com', dueDate: '2026-07-10' },
{ externalId: 'ERP-1003', amount: 89.90, description: 'Renovação de Plano', name: 'Carlos Souza', dueDate: '2026-07-15' }
];
// 1. Criar o lote
const { batchId, totalItems } = await createBatch(items, 'https://seusite.com/webhook/lote');
console.log(`Lote criado: ${batchId} (${totalItems} itens)`);
// 2. Aguardar conclusão via polling
const batch = await pollBatch(batchId);
console.log(`Lote finalizado com status: ${batch.status}`);
console.log(`Sucesso: ${batch.succeededItems} | Falha: ${batch.failedItems}`);
// 3. Tratar falhas
if (batch.failedItems > 0) {
const failedItems = await getFailedItems(batchId);
console.log('\nItens com falha:');
for (const item of failedItems) {
console.log(` [${item.externalId}] ${item.error?.code}: ${item.error?.message}`);
}
// Opção A: retry automático
const retry = await apiRequest('POST', `/api/charge/batch/${batchId}/retry`);
console.log(`\nRe-enfileirados: ${retry.requeued} itens`);
}
}
main().catch(console.error);Respostas de Erro
400 Bad Request
Envelope inválido. Nenhuma cobrança é criada.
{
"statusCode": 400,
"message": "Validation failed",
"errors": [
"items must contain at least 1 element",
"items[2].externalId must be unique within the batch",
"items[4].amount must be a positive number"
]
}403 Forbidden
Token inválido/expirado ou feature de lote não habilitada para a empresa.
{
"statusCode": 403,
"message": "Forbidden"
}TIP
Se o token estiver correto mas o erro persistir, entre em contato com o suporte DiviPay para habilitar a emissão em lote na sua conta.
404 Not Found
Lote não encontrado.
{
"statusCode": 404,
"message": "Batch not found",
"error": "Not Found"
}