Pay-ins
A pay-in collects money from a payer. You ask Borderli for a QR code, show it to the payer, and the payer scans it with any bank or wallet app in their country. The funds settle to your account with our payment partner. Borderli never holds them.
Flow
- Issue.
POST /v1/collectreturns apendingtransaction and the QR payload inqr.data. - Show. Render
qr.dataas a QR image, or offer it as a copy-and-paste string. It can be paid for 10 minutes, untilqr.expires_at. - Outcome. When the payer pays, Borderli sends a
transaction.completedwebhook within seconds. If nobody pays, the transaction becomescanceledand you gettransaction.canceled. - Refund if you need to.
POST /v1/transactions/{id}/refundsends money back. Atransaction.refundedwebhook confirms it.
Issue a QR
curl -X POST https://apidocs.borderli.io/v1/collect \
-H "Content-Type: application/json" \
-H "x-api-key: brly_test_key_001" \
-H "Idempotency-Key: order-98765" \
-d '{
"amount": 1000,
"local_currency": "BRL",
"local_country": "BR",
"qr_from": "BR",
"merchant_tax_id": "12345678000195"
}'
| Field | Meaning |
|---|---|
amount | Minor units of local_currency. 1000 is R$10.00. |
local_currency, local_country | The merchant's currency and country. ISO 4217 and ISO 3166-1 alpha-2. |
qr_from | The country of the wallet that will scan. Use the same value as local_country for a domestic payment. |
merchant_tax_id | Optional. The tax id of the merchant collecting: CNPJ or CPF in Brazil, CUIT in Argentina, and so on. When omitted, the tax id on your tenant is used. The request is rejected with 422 missing_tax_id when neither is set. |
The response is 201:
{
"transaction": { "id": "txn_01J...", "status": "pending", "direction": "payin" },
"qr": {
"data": "00020101021126...",
"charged": { "value": 1000, "currency": "BRL" },
"exchange_rate": null,
"expires_at": "2026-09-14T10:25:00.000Z"
},
"request_id": "..."
}
qr.charged is what the payer will pay in their own currency. For a domestic payment it equals amount. For a cross-border payment the rail converts it and exchange_rate holds the rate.
Cancel a QR
POST /v1/transactions/{id}/cancel withdraws a QR that has not been paid. It works only while the transaction is pending; otherwise you get 409.
The response still shows pending. The transaction moves to canceled once the payment partner confirms, by webhook or by Borderli's status poller, usually within a few minutes. The transaction.canceled webhook tells you it is done.
Refund
curl -X POST https://apidocs.borderli.io/v1/transactions/txn_01J.../refund \
-H "Content-Type: application/json" \
-H "x-api-key: brly_test_key_001" \
-d '{ "partial": false, "amount": 1000, "reason": "customer request" }'
A refund is allowed on a completed transaction, once per transaction, for at most the collected amount. Set partial to true when amount is less than what was collected.
The response is an acknowledgement, normally { "status": "PENDING" }. The refund is confirmed by the transaction.refunded webhook. The transaction keeps status: completed; result.refund is added with kind set to full or partial.
Statuses
| status | Meaning | Detail field |
|---|---|---|
pending | The QR is open and can be paid. | |
completed | The payer paid. A refund does not change this status. | result |
canceled | Nobody paid. | cancellation.reason |
failed | The payment did not go through. This QR is now closed. Issue a new one and the payer can try again. | error |
cancellation.reason is one of:
requested: you called cancel.expired: the 10 minutes passed.declined: the payer rejected the payment in their wallet.
error.reason is rejected or rail_failure. Which one you get depends on the country: each local rail reports failures differently, and Borderli's payment partner passes that through. Argentina can report rejected; the other countries report rail_failure. Treat both the same way: the collection is over, issue a new QR. error.code carries the bank's own code when the bank supplies one. Most pay-in failures do not have one.
Errors
On a 409 or 422 the code is in details.reason. On a 502 it is the top-level reason.
| reason | HTTP | Meaning |
|---|---|---|
collect_not_enabled | 422 | Your account is not set up for pay-ins. Contact Borderli. |
unsupported_qr_from | 422 | No supported QR scheme covers this qr_from. The countries are listed by GET /v1/schemes. |
missing_tax_id | 422 | No merchant_tax_id in the request and no tax id on your tenant. Send one, or ask Borderli to set yours. |
corridor_unavailable | 502 | This qr_from and local_country pair is not enabled. |
fx_unavailable | 502 | No exchange rate for this qr_from and local_currency pair. |
not_cancelable | 409 | The QR is no longer open. It was paid, expired or already canceled. |
not_refundable | 422 | The transaction is not completed. |
amount_exceeds_collected | 422 | The refund amount is more than what was collected. |
refund_already_requested | 409 | A refund was already requested for this transaction. If your earlier refund request ended in a 502, the request is kept as in progress until we confirm with the payment partner. Contact Borderli. |
A malformed field, such as a qr_from that is not a two-letter country code, returns 400 before any of these checks run.
Reconciliation
GET /v1/transactions?direction=payin lists your collections with their status and result. There are no chargebacks on Pix or interoperable QR. A completed pay-in can only be reversed by a refund you request.