Cobranças

Cobranças (billings) são o recurso central da API: representam um valor a receber de um contato ou grupo, emitido via uma regra bancária (bank_rule) que define qual banco/convênio gera o boleto.

Veja conventions.md para paginação, autenticação, erros, rate limit e isolamento entre contas. Os campos do recurso contact e group aninhados na resposta seguem o que está em contacts.md e groups.md.

Modelo

Tipos: billing_type

A API só opera com cobranças avulsas — toda cobrança criada via API tem billing_type="billing".

Cobranças recorrentes (schedule) e carnês (carne) podem aparecer no GET se já existirem (criadas pela interface web), mas o POST força sempre billing e ignora qualquer valor enviado em billing_type. Para gerenciar schedule/carne via API, abra uma issue.

Modos de emissão: billing_issue_type

ValorComportamento
individual (default)Cobrança endereçada a um único contato (contact_id obrigatório). group_id é zerado pelo servidor
bulkCobrança em massa para todos os contatos de um grupo (group_id obrigatório). contact_id é zerado pelo servidor

Status

status é a coluna persistida; virtual_status é o status mostrado nas respostas, e equivale a status exceto quando status="opened" e due_date < hoje — nesse caso virtual_status="expired".

statusSignificado
openedCobrança em aberto, aguardando pagamento
under_reviewEm análise pelo banco (intermediário)
awaiting_captureAguardando captura no provedor (gateway digital)
protestedProtestada
in_disputeEm disputa
paidPaga via gateway
availableValor disponível (compensado)
dischargedBaixada manualmente (POST/pay_off)
canceledCancelada
returnedDevolvida pelo banco

Valores adicionais expostos apenas em virtual_status: - expired — derivado, não persiste.

Campos retornados

CampoTipo
id, code, your_code, referenceint / string
statusstring (ver tabela acima)
virtual_statusstring (status persistido + derivação expired)
billing_type, billing_issue_typestring
amountdecimal — calculado pelo servidor a partir dos billing_items (sum(quantity * amount))
due_datedate — se cair em sábado, ajustado para segunda; se domingo, para segunda
competencestring MM/YYYY (ou null)
evincivestring — texto descritivo, 10..80 chars, sem caracteres especiais
observationstring até 255
discount_typepercentage ou fixed_amount
discount, discount2decimal 0..999999999
discount_antecedence, discount_antecedence2inteiro 0..365 dias antes do vencimento. O segundo desconto vale para uma janela menor: discount_antecedence > discount_antecedence2 (ou discount_antecedence2 = 0) e discount > discount2
penalty_typepercentage ou fixed_amount
penaltydecimal 0..999999999
cancel_penaltyboolean — quando true, anula juros e multa após vencido
interest_typepercentage ou fixed_amount
interest_periodmonthly ou daily
interestdecimal 0..999999999
instruction_typeprotest ou devolution
instruction_daysinteiro 0..99
bank_rule_id, contact_id, group_idFKs (precisam ser da mesma conta)
installments, installment_type, installmentint / string — só preenchidos em carnês legados criados pelo admin web. Sempre 1 / "none" / 1 em cobranças criadas via API
gw_payment_linkstring | null — link gerado pelo gateway externo (PagSeguro, MercadoPago) para o pagamento. Use isso pra redirecionar o cliente ou abrir em iframe
gw_received_withstring | null — método com que o pagamento foi recebido (credit_card, bank_billet, pix, etc.)
gw_payment_datedate | null — data do pagamento no gateway
gw_gross_amountdecimal — valor bruto recebido
gw_fee_amountdecimal — tarifa cobrada pelo gateway
gw_net_amountdecimal — valor líquido (gross - fee)
gw_discount_amountdecimal — desconto aplicado pelo gateway
gw_extra_amountdecimal — acréscimos (juros/multa) aplicados pelo gateway
gw_escrow_end_datedatetime | null — fim do período de retenção (escrow) no gateway
gw_installment_countint | null — número de parcelas escolhidas no checkout
gw_payment_method_typeint | null — código numérico do tipo de método de pagamento no gateway
gw_payment_method_codeint | null — código numérico do método de pagamento no gateway
gw_type_idint | null — identificador do tipo no gateway
discount_amount, extra_amount, amount_payablecalculados com base em hoje (descontos antecipados, juros/multa por atraso)
days_overdueinteiro (negativo se ainda não venceu)
overdueboolean — days_overdue > 0 e virtual_status in (opened, expired)
paidboolean — virtual_status in (paid, available, discharged)
display_amount_payabledecimal — versão "congelada" de amount_payable: usa a data do registro do boleto AFPAY quando aplicável, ou gw_gross_amount - gw_discount_amount + gw_extra_amount quando pago. Use este valor para exibir ao cliente final no checkout
return_datedue_date + instruction_days se instruction_type=devolution; caso contrário null
redirect_urlURL para onde o pagador é redirecionado após pagar no checkout do Sacador (eco do que foi configurado)
notification_urlURL adicional de notificação configurada na cobrança (eco do que foi configurado)
created_at, updated_atISO-8601
contactobjeto {id, description, email, document} ou ausente
groupobjeto {id, description} ou ausente
billing_itemsarray — só aparece em show, create, update, cancel e pay_off. Não retornado na listagem (GET/v1/billings). Ver billing_items
billing_reminder_deliveriesarray de tentativas de notificação — só aparece em show, create, update, cancel e pay_off. Ver billing_reminder_deliveries
bank_billetsarray de boletos bancários — só aparece em show, create, update, cancel e pay_off. Formato detalhado em bank_billets.md. Para listagem dedicada use GET/v1/billings/:billing_id/bank_billets

billing_items

Cada cobrança tem pelo menos 1 item.

CampoTipoRegras
idintAtribuído pelo servidor após criação
descriptionstringObrigatório, até 100 chars
quantityint1..9999999
amountdecimal≥ 0,01

O amount total da cobrança é sempre sum(quantity * amount) dos itens — não envie amount no nível da cobrança, é ignorado.

billing_reminder_deliveries

Histórico de envios de lembretes (email/WhatsApp). Cada item:

{
  "id": 5,
  "channel": "email",
  "status": "delivered",
  "delivered_at": "2026-04-12T09:00:00.000Z",
  "error_message": null,
  "attempts_count": 1,
  "created_at": "2026-04-12T09:00:00.000Z",
  "updated_at": "2026-04-12T09:00:01.000Z"
}

Read-only. Use para auditoria, não para disparar lembretes.

Checkout transparente

Para construir um checkout próprio (sem redirecionar para o Sacador), use a seguinte combinação de campos:

NecessidadeOnde está
Valor a cobrar (já com desconto/juros aplicados)display_amount_payable em GET/v1/billings/:id
Boleto bancário (linha digitável, barcode, QR Pix)Itens do array bank_billets — ver bank_billets.md
Link do gateway (PagSeguro/MercadoPago)gw_payment_link
Confirmação de pagamentoWebhook billing.updated (ver webhooks.md) — quando o status muda para paid/available/discharged
Reconciliação financeira do que efetivamente entrouCampos gw_gross_amount, gw_fee_amount, gw_net_amount, gw_payment_date, gw_received_with
Redirecionar pagador pós-pagamentoConfigure redirect_url no POST ou PATCH — o Sacador redireciona ao final do fluxo
Notificação adicional além dos webhooksConfigure notification_url

Fluxo típico:

  1. POST/v1/billings com redirect_url apontando para sua página de obrigado e your_code para correlação.
  2. Mostre display_amount_payable e ofereça os métodos disponíveis a partir de bank_billets[].pix_qr_code, bank_billets[].digitable_line e/ou redirect para gw_payment_link.
  3. Aguarde o webhook billing.updated para confirmar e baixar internamente no seu sistema.
  4. Para reconciliar valores recebidos, use gw_gross_amount e gw_net_amount.

Quando uma cobrança é editável

Determina o que pode ser alterado em PATCH/v1/billings/:id. Uma cobrança é editável quando todas as condições abaixo valem:

  • não é carne (carnês nunca são editáveis); e
  • é schedule (sempre editável), ou é billing no status opened/expired com o boleto bancário (quando existir) ainda editável e não está em gateway PagSeguro.

Em cobranças não-editáveis, o PATCH aceita somente dois campos, e apenas se o status atual permitir (opened ou expired):

  • due_date — reagendamento
  • cancel_penalty — zerar juros/multa

Qualquer outro campo enviado nessa situação é silenciosamente ignorado.

Endpoints

GET/v1/billings

Lista cobranças da conta. Ordem: id DESC. Pré-carrega bank_billet, contact, group, billing_items e billing_reminder_deliveries para evitar N+1.

Query params:

ParamTipoComportamento
billing_typestringDefault "billing". Use "schedule" ou "carne" para listar recorrências/carnês legados criados pelo admin web
statusstring ou string separada por espaçosAceita os valores de status + os agregados opened (apenas com due_date >= hoje), expired (status=opened, due_date < hoje), paid (paid+available). Múltiplos valores: ?status=opened%20under_review
contact_idintFiltra por contato
group_idintFiltra cobranças endereçadas a contatos do grupo
billing_idintFiltra parcelas filhas de um carnê (campo billing_id próprio = id do carnê pai)
your_codestringIgualdade exata
start_created_at, end_created_atdate YYYY-MM-DDFaixa por data de criação (ambos obrigatórios para o filtro ativar)
start_due_date, end_due_datedate YYYY-MM-DDFaixa por data de vencimento (ambos obrigatórios)
sstringBusca por documento (CPF/CNPJ) OU description do contato OU evincive (LIKE)
page, per_pageintPadrão de paginação

Exemplo cURL:

curl -X GET 'https://api.sacador.com.br/v1/billings?status=opened&page=1&per_page=25' \
  -H 'Authorization: Bearer sct_seu_token_aqui'

Resposta 200 OK:

{
  "billings": [
    {
      "id": 123,
      "code": "ABC123",
      "your_code": "ERP-2026-0001",
      "reference": "Mensalidade",
      "status": "opened",
      "virtual_status": "opened",
      "billing_type": "billing",
      "billing_issue_type": "individual",
      "amount": "150.00",
      "due_date": "2026-06-10",
      "competence": "05/2026",
      "evincive": "Mensalidade de maio",
      "observation": null,
      "penalty_type": "percentage",
      "penalty": "2.0",
      "cancel_penalty": false,
      "interest_type": "percentage",
      "interest_period": "monthly",
      "interest": "1.0",
      "instruction_type": "protest",
      "instruction_days": 5,
      "bank_rule_id": 7,
      "contact_id": 101,
      "group_id": null,
      "discount_amount": "0.0",
      "extra_amount": "0.0",
      "amount_payable": "150.00",
      "days_overdue": -30,
      "overdue": false,
      "return_date": null,
      "created_at": "2026-05-11T10:00:00.000Z",
      "updated_at": "2026-05-11T10:00:00.000Z",
      "contact": {
        "id": 101,
        "description": "Maria Silva",
        "email": "maria@exemplo.com",
        "document": "12345678901"
      }
    }
  ]
}

Note que billing_items, billing_reminder_deliveries e bank_billets não aparecem na listagem. Para obtê-los use GET/v1/billings/:id (que retorna tudo) ou os endpoints dedicados.


GET/v1/billings/:id

Retorna uma cobrança individual. Esta é a resposta canônica usada também por POST, PATCH, POST/cancel e POST/pay_off — todos retornam o mesmo formato. É o que você consome para construir o checkout transparente.

Exemplo cURL:

curl -X GET 'https://api.sacador.com.br/v1/billings/123' \
  -H 'Authorization: Bearer sct_seu_token_aqui'

Resposta 200 OK:

{
  "id": 123,
  "code": "0F7B91A2C3D4E5F60718293A4B5C6D7E",
  "your_code": "ERP-2026-0001",
  "reference": "Mensalidade",
  "status": "opened",
  "virtual_status": "opened",
  "billing_type": "billing",
  "billing_issue_type": "individual",
  "amount": "150.00",
  "due_date": "2026-06-10",
  "competence": "05/2026",
  "evincive": "Mensalidade de maio",
  "observation": null,
  "discount_type": "percentage",
  "discount": "10.0",
  "discount_antecedence": 5,
  "discount_type2": null,
  "discount2": "0.0",
  "discount_antecedence2": 0,
  "penalty_type": "percentage",
  "penalty": "2.0",
  "cancel_penalty": false,
  "interest_type": "percentage",
  "interest_period": "monthly",
  "interest": "1.0",
  "instruction_type": "protest",
  "instruction_days": 5,
  "bank_rule_id": 7,
  "contact_id": 101,
  "group_id": null,
  "installments": 1,
  "installment_type": "none",
  "installment": 1,
  "redirect_url": "https://store.example.com/thanks",
  "notification_url": null,
  "gw_payment_link": null,
  "gw_received_with": null,
  "gw_payment_date": null,
  "gw_gross_amount": "0.0",
  "gw_fee_amount": "0.0",
  "gw_net_amount": "0.0",
  "gw_discount_amount": "0.0",
  "gw_extra_amount": "0.0",
  "gw_escrow_end_date": null,
  "gw_installment_count": null,
  "gw_payment_method_type": null,
  "gw_payment_method_code": null,
  "gw_type_id": null,
  "created_at": "2026-05-12T10:00:00.000Z",
  "updated_at": "2026-05-12T10:00:00.000Z",
  "virtual_status": "opened",
  "return_date": null,
  "discount_amount": "15.0",
  "extra_amount": "0.0",
  "amount_payable": "135.0",
  "display_amount_payable": "135.0",
  "days_overdue": -29,
  "overdue": false,
  "paid": false,
  "contact": {
    "id": 101,
    "description": "Maria Silva",
    "email": "maria@exemplo.com",
    "document": "12345678901"
  },
  "billing_items": [
    {
      "id": 555,
      "description": "Mensalidade",
      "quantity": 1,
      "amount": "150.00"
    }
  ],
  "billing_reminder_deliveries": [
    {
      "id": 5,
      "channel": "email",
      "status": "delivered",
      "delivered_at": "2026-05-12T09:00:00.000Z",
      "error_message": null,
      "attempts_count": 1,
      "created_at": "2026-05-12T09:00:00.000Z",
      "updated_at": "2026-05-12T09:00:01.000Z"
    }
  ],
  "bank_billets": [
    {
      "id": 9001,
      "status": "opened",
      "bank_symbol": "bb",
      "amount": "150.00",
      "due_date": "2026-06-10",
      "barcode": "00190000000000010000000000000000000000000000",
      "digitable_line": "00190.00009 00000.000009 00000.000009 0 00000000000000",
      "pix_qr_code": "00020101021226...",
      "our_number": "1234567",
      "days_overdue": -29,
      "overdue": false,
      "editable": false,
      "created_at": "2026-05-12T10:00:00.000Z",
      "updated_at": "2026-05-12T10:00:00.000Z"
    }
  ]
}

Campos do array bank_billets foram resumidos acima — o formato completo (recipient/payer, datas de pagamento, valores líquidos, provider, etc.) está em bank_billets.md.

Erros: 404 se inexistente ou de outra conta.


POST/v1/billings

Cria uma cobrança.

Body:

{
  "billing": {
    "bank_rule_id": 7,
    "contact_id": 101,
    "billing_issue_type": "individual",
    "due_date": "2026-06-10",
    "evincive": "Mensalidade de maio",
    "instruction_type": "protest",
    "instruction_days": 5,
    "penalty_type": "percentage",
    "penalty": 2,
    "interest_type": "percentage",
    "interest_period": "monthly",
    "interest": 1,
    "billing_items": [
      { "description": "Mensalidade", "quantity": 1, "amount": 150.00 }
    ]
  }
}

Campos aceitos (qualquer outro é ignorado, incluindo account_id, code, status):

bank_rule_id, contact_id, group_id, billing_issue_type, evincive, observation, reference, your_code, competence, due_date, penalty, penalty_type, interest, interest_type, interest_period, instruction_type, instruction_days, redirect_url, notification_url, billing_items.

O campo amount no nível da cobrança é somente leitura — sempre recalculado pelo servidor como sum(quantity * amount) dos billing_items. Se você enviar, é descartado.

billing_type não é aceito: a API sempre cria com billing_type="billing". Qualquer valor enviado é descartado.

Exemplo cURL (cobrança individual):

curl -X POST 'https://api.sacador.com.br/v1/billings' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "billing": {
      "bank_rule_id": 7,
      "contact_id": 101,
      "billing_issue_type": "individual",
      "due_date": "2026-06-10",
      "evincive": "Mensalidade de maio",
      "your_code": "ERP-2026-0001",
      "competence": "05/2026",
      "penalty_type": "percentage",
      "penalty": 2,
      "interest_type": "percentage",
      "interest_period": "monthly",
      "interest": 1,
      "billing_items": [
        { "description": "Mensalidade", "quantity": 1, "amount": 150.00 }
      ]
    }
  }'

Exemplo cURL (cobrança em massa para um grupo):

curl -X POST 'https://api.sacador.com.br/v1/billings' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "billing": {
      "bank_rule_id": 7,
      "group_id": 12,
      "billing_issue_type": "bulk",
      "due_date": "2026-06-10",
      "evincive": "Mensalidade junho",
      "billing_items": [
        { "description": "Mensalidade", "quantity": 1, "amount": 99.90 }
      ]
    }
  }'

billing_items em POST

Cada elemento aceita: description, quantity, amount. Não envie id na criação.

Validações

Resumo das regras que mais aparecem em 422:

  • bank_rule_id obrigatório e pertencente à conta
  • contact_id obrigatório se billing_issue_type=individual; precisa ser da conta
  • group_id obrigatório se billing_issue_type=bulk; precisa ser da conta
  • due_date obrigatório
  • billing_items.length >= 1, e cada item válido (descrição até 100 chars, quantidade 1..9999999, valor ≥ 0,01)
  • evincive se preenchido: 10..80 chars, sem @#$%^&*"{}|<>\[]
  • interest, penalty, discount, discount2 ≤ 999.999.999
  • Quando ambos os descontos são usados: discount > discount2 e discount_antecedence > discount_antecedence2 (o segundo desconto deve ter valor menor e janela mais próxima do vencimento)
  • Conta não verificada (sem KYC aprovado): valor da cobrança limitado a R$ 10,00

Resposta 201 Created: corpo idêntico a GET/v1/billings/:id.

Observação sobre boleto bancário

A criação do bank_billet no banco é assíncrona. O POST retorna a cobrança imediatamente com status=opened; o boleto fica disponível assim que o banco confirma o registro. Reconsulte via GET/v1/billings/:id se precisar do provider_id.


PATCH/v1/billings/:id

Atualiza uma cobrança.

  • Cobranças editáveis (ver Quando uma cobrança é editável): aceita os mesmos campos do POST.
  • Cobranças não editáveis em status opened/expired: aceita apenas due_date e cancel_penalty.
  • Demais casos: o PATCH é aceito (200 OK), mas nenhuma alteração é efetuada.

Exemplo cURL (prorrogar vencimento):

curl -X PATCH 'https://api.sacador.com.br/v1/billings/123' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "billing": {
      "due_date": "2026-07-10"
    }
  }'

billing_items em PATCH — semântica de destruição implícita

Quando você envia billing_items no body de um PATCH:

  • Itens com id existentes na cobrança são atualizados.
  • Itens sem id são criados.
  • Itens existentes na cobrança que não aparecem no array são removidos automaticamente.

Exemplo: cobrança tem itens [1, 2, 3]. Você envia:

{
  "billing": {
    "billing_items": [
      { "id": 1, "description": "Mensalidade atualizada", "quantity": 1, "amount": 200 },
      { "description": "Item novo", "quantity": 1, "amount": 50 }
    ]
  }
}

Resultado: item 1 atualizado, item novo criado, itens 2 e 3 removidos. Para manter um item, inclua-o (ao menos com id) no array.

Para apagar todos os itens, envie billing_items: [] — mas o POST/PATCH falhará com 422 porque a cobrança precisa de ao menos 1 item. Use o cancelamento se o objetivo for descontinuar a cobrança.

Erros: 404 (outra conta), 422 (validações).


DELETE/v1/billings/:id

Apaga uma cobrança.

Restrição: somente cobranças com billing_type=schedule (recorrências legadas) podem ser deletadas. Cobranças billing criadas via API não podem ser deletadas — use POST/v1/billings/:id/cancel para descontinuá-las. Tentativa de delete retorna:

{
  "status": 422,
  "errors": ["Não é possível alterar esta cobrança"]
}

Exemplo cURL:

curl -X DELETE 'https://api.sacador.com.br/v1/billings/123' \
  -H 'Authorization: Bearer sct_seu_token_aqui'

Resposta 204 No Content quando bem-sucedido.

Erros: 404 (outra conta), 422 (não é schedule).


POST/v1/billings/:id/cancel

Cancela uma cobrança.

Body:

{
  "billing": {
    "cancellation_reason": "Cliente desistiu da compra"
  }
}
CampoRegras
cancellation_reasonObrigatório, mínimo 3 caracteres, máximo 1000

Exemplo cURL:

curl -X POST 'https://api.sacador.com.br/v1/billings/123/cancel' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "billing": {
      "cancellation_reason": "Cliente desistiu da compra"
    }
  }'

Resposta 200 OK: corpo idêntico a GET/v1/billings/:id, com status="canceled".

Erros: 404 (outra conta), 422 (motivo curto/ausente, ou cobrança em status que não permite cancelamento).


POST/v1/billings/:id/pay_off

Dá baixa manual (descarregamento).

Body:

{
  "billing": {
    "bank_account_id": 42,
    "discharge_reason": "bank_deposit",
    "gw_gross_amount": 150.00
  }
}
CampoRegras
bank_account_idObrigatório. Precisa pertencer à mesma conta. Não pode ser conta do tipo sacador
discharge_reasonObrigatório. "bank_deposit" ou "payment_in_hand"
gw_gross_amountValor efetivamente recebido (pode diferir de amount se houve juros/desconto manual)

Após baixa o status vira discharged e um lançamento de caixa (Cashier) é criado automaticamente na conta bancária informada.

Exemplo cURL:

curl -X POST 'https://api.sacador.com.br/v1/billings/123/pay_off' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "billing": {
      "bank_account_id": 42,
      "discharge_reason": "bank_deposit",
      "gw_gross_amount": 150.00
    }
  }'

Resposta 200 OK: corpo idêntico a GET/v1/billings/:id, com status="discharged".

Erros: 404 (outra conta), 422 (bank_account_id inválido, conta sacador, discharge_reason inválido, ou status que não permite baixa).