EnglishAPI referenceChargesCreate a boleto charge

Create a boleto charge

POST/v1/boleto
Issues a Brazilian bank slip (boleto) and returns the PDF link and the typeable line.
Enabled per account

Boleto is enabled per account. If yours is not enabled yet, the call returns 400 with "Boleto não está habilitado para esta conta." — contact support to activate it. The PIX contract (POST /v1/pix) is unchanged.

Request

Headers

HeaderRequiredDescription
AuthorizationyesBearer <YOUR_API_KEY>
Content-Typeyesapplication/json

Body

{
  "amountCents": 5000,
  "externalRef": "order-123",
  "description": "Order 123",
  "dueDate": "2026-09-30",
  "customer": {
    "name": "Maria Silva",
    "email": "maria@example.com",
    "phone": "11999998888",
    "document": {
      "number": "12345678901",
      "type": "cpf"
    },
    "address": {
      "street": "Rua das Flores",
      "number": "100",
      "complement": "Apto 12",
      "district": "Centro",
      "city": "São Paulo",
      "zipCode": "01310100",
      "state": "SP"
    }
  }
}
FieldTypeReq.Description
amountCentsintegeryesAmount in cents. Minimum: 100 (R$ 1.00). Per-account limits apply.
customerobjectyesPayer data (see below).
customer.namestringyesPayer name. Max. 100 characters.
customer.emailstringyesValid payer e-mail.
customer.phonestringconditionalPhone with area code, digits only: 11 digits = mobile, 10 = landline. Required on accounts that require full payer data (see below). Otherwise optional, and if the provider rejects the phone, the boleto is issued without it (never blocks the charge).
customer.document.numberstringyesCPF (11 digits) or CNPJ (14 digits). Formatting is stripped.
customer.document.typestringyes"cpf" or "cnpj" (case and whitespace normalized).
customer.addressobjectconditionalPayer address. Required on accounts that require full payer data; ignored otherwise.
customer.address.streetstringconditionalStreet. Max 120 characters.
customer.address.numberstringnoNumber. Max 20 characters.
customer.address.complementstringnoComplement (apartment, suite). Max 60 characters.
customer.address.districtstringconditionalNeighborhood. Max 60 characters.
customer.address.citystringconditionalCity. Max 60 characters.
customer.address.zipCodestringconditionalCEP (Brazilian ZIP code) with 8 digits. Formatting is stripped.
customer.address.statestringconditionalState (UF), 2 letters, e.g. "SP".
externalRefstringnoIdentifier on your side. Used as the idempotency key.
descriptionstringnoFree text. Max. 200 characters.
dueDatestringnoDue date as YYYY-MM-DD, today or later. Default: today + 3 days.
Full payer data

Some accounts require the payer’s email, phone and address to issue a boleto. On those, a missing or invalid field returns 400 naming the field (e.g. "customer.address.zipCode inválido (CEP com 8 dígitos).") without creating the charge, and the due date must be today or later. We recommend always sending full payer data: on accounts that don’t require it, the address is ignored.

Idempotency via externalRef

Sending the same externalRef again returns the original boleto with "idempotent": true, without issuing another one. Use it whenever possible, especially on timeout retries. If the previous issuance is under verification, the retry returns 400 with "Boleto em verificação..." until it is confirmed: check the transaction named in the message and do not issue another boleto for the same order meanwhile. Once the issuance is confirmed, the same retry returns the boleto (200, "idempotent": true).

Response 201 Created

{
  "success": true,
  "transactionId": "5glmNaIVteNioH6UBNxH",
  "status": "pending",
  "method": "boleto",
  "amountCents": 5000,
  "amount": 50,
  "dueDate": "2026-09-30",
  "bankSlipUrl": "https://southamerica-east1-coufai.cloudfunctions.net/payments-boletoPdf?tid=5glmNaIVteNioH6UBNxH&t=...",
  "identificationField": "46191110000000000000013187639011415640000005000",
  "nossoNumero": "13187639",
  "externalRef": "order-123",
  "idempotent": false
}
FieldDescription
transactionIdInternal transaction ID. Use it with GET /v1/transactions/{id}.
statusAlways "pending" on creation.
bankSlipUrlPublic link to the boleto PDF, served by Coufai. Safe to send to the payer; no authentication required. May be null right after issuance while the bank finishes registering the boleto (usually under a minute); fetch the transaction afterwards. On some accounts the boleto is delivered only as the digitable line, with no PDF: bankSlipUrl then stays null for good and you should show identificationField to the payer.
identificationFieldTypeable line (47 digits) for payment through internet banking. May be null for the same reason as bankSlipUrl.
nossoNumeroBoleto number at the issuing bank. May be null, depending on the issuing bank.
dueDateApplied due date.
amountCents / amountEcho of the amount, in cents and in BRL.

Response 200 OK (idempotent)

When externalRef was already used, the response is the original charge with "idempotent": true.

Lifecycle and webhooks

MomentTransaction statusWebhook event
Issuedpending—
Payment cleared by the bank (usually next business day)paidtransaction.paid, with netCents net of the fee
Due date passed without paymentexpiredtransaction.expired — carries unpaidFeeCents if your account has an unpaid-boleto fee

A boleto can still be paid after the due date while the bank accepts it; transaction.paid is delivered normally in that case. The amount credited to your balance is netCents (amount minus your account’s boleto fee). The transaction method is "boleto" in every event.

Do not reconcile by aggressive polling

React to transaction.paid and keep your balance from netCents. Coufai reconciles payments with the bank in the background; a payment whose notice was delayed is credited anyway.

Unpaid-boleto fee

Accounts with an unpaid-boleto fee are charged when a boleto passes its due date without payment: at the moment of expired, the fee is debited from the balance and the transaction.expired event carries unpaidFeeCents (omitted when there is no fee). Rules:

  • It shows in the statement and in the API as its own line in GET /v1/transactions, with type: "fee", feeKind: "boleto_unpaid" and relatedTransactionId pointing to the boleto.
  • Automatic reversal: if the boleto is paid after the due date, the fee is returned together with the payment credit — a type: "fee_reversal" line with the same relatedTransactionId.
  • The balance can go negative because of the fee. While it is negative, withdrawals are refused for insufficient balance; the next payment received covers the difference.
  • The amount is the one contracted for your account — check with support. Without a configured fee nothing changes: expired remains a status-only event.

Errors

HTTPerror
400"amountCents deve ser inteiro ≥ 100 (R$1,00 mínimo)."
400"customer.name obrigatório."
400"customer.document.number e customer.document.type obrigatórios."
400"customer.document.type deve ser 'cpf' ou 'cnpj'. Recebido: '<valor>'."
400"customer.email é obrigatório e deve ser válido."
400"dueDate inválido (use YYYY-MM-DD)."
400"customer.address é obrigatório para emitir boleto nesta conta." — and the per-field variations (customer.address.zipCode, customer.address.state, customer.phone…): the account requires full payer data; fix the named field.
400"dueDate deve ser hoje ou uma data futura (YYYY-MM-DD)."
400"Boleto em verificação: a emissão não confirmou. NÃO reenvie — consulte a transação <id> em instantes." — the previous issuance did not confirm and the boleto may exist; do not issue another one for the same order.
400"Dados recusados pelo provedor: <campo> inválido. Corrija e reenvie. (<código>)" — the payer or charge data was rejected by the provider validation. <campo> names what to fix (documento = document, e-mail, nome = name, endereço = address, vencimento = due date, valor = amount); <código> is stable, e.g. invalid_cpfCnpj.
400"Limite de emissão de cobranças da conta atingido. Não reenvie; fale com o suporte. (issuance_limit)" — the account reached its issuance ceiling; retrying will not help.
400"Emissão não permitida para esta conta no momento. Não reenvie; fale com o suporte. (account_restricted)" — the account is currently not allowed to issue; retrying will not help.
400"Boleto não está habilitado para esta conta."
400"Boleto não disponível para esta conta no momento." — activation in progress; retry later or contact support.
400"Valor acima do máximo permitido (R$ X.YY)."
401"API key inválida ou revogada."
403"Conta não está aprovada para uso da API."
429"Limite de requisições atingido. Tente em 1 hora."
500"Erro ao gerar boleto. Tente novamente."
503"Boleto temporariamente indisponível. Tente novamente em instantes." — transient failure: nothing was issued, you can retry.

Error messages are returned in Portuguese.

Examples

curl -X POST https://api.coufai.com.br/v1/boleto \
  -H "Authorization: Bearer $COUFAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amountCents": 5000,
    "externalRef": "order-123",
    "description": "Order 123",
    "dueDate": "2026-09-30",
    "customer": {
      "name": "Maria Silva",
      "email": "maria@example.com",
      "phone": "11999998888",
      "document": { "number": "12345678901", "type": "cpf" },
      "address": {
        "street": "Rua das Flores", "number": "100", "district": "Centro",
        "city": "São Paulo", "zipCode": "01310100", "state": "SP"
      }
    }
  }'