Cash Out Guide (PIX Out)

This guide explains step by step how to make PIX transfers (cash out) with prior key lookup. Boleto payment, the other cash out in the API, is covered in Boleto payment at the end of this page.

Overview

Cash Out allows you to send money via PIX to any key registered in the Brazilian PIX system. The recommended flow is:

  1. Look up the key - Validate and obtain the recipient’s details
  2. Confirm the details - Display to the user for confirmation
  3. Execute the transfer - Send the PIX

Integration Flow

┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Look Up │ │ Confirm │ │ Execute │
│ Key │ ───► │ Details │ ───► │ Transfer │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
Name, Bank, User E2E generated,
Account, CPF/CNPJ Confirms Webhook sent

PIX Key Types

TypeFormatExample
CPF11 digits12345678901
CNPJ14 digits12345678000199
EMAILValid e-mailjoao@email.com
PHONE+55 + area code + number+5511999998888
EVPUUID123e4567-e89b-12d3-a456-426614174000

Before transferring, look up the key to validate the recipient and display the details for user confirmation:

curl -X GET "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/key/12345678901" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa"

The API automatically performs the key lookup during the transfer — the prior lookup is optional, but improves UX. The result is cached for 24 h (use ?noCache=true to force a fresh DICT lookup) and consumes lookup quota according to the tenant policies. DICT consumption is measured in real time and watched by CorpX: before integrating, read PIX key lookups.

What the Transfer Returns

The transfer response does not echo the recipient’s details: it carries the outcome of the operation (status, paymentId, endToEndId). The payee’s name and document appear in the statement and in the payment lookup.

Step 2: Execute the Transfer

Execute the PIX transfer via key:

Request

curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/out" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: transfer-order-12345" \
-d '{
"amount": 100.00,
"keyType": "CPF",
"key": "12345678901",
"description": "Service payment",
"identifier": "order-12345"
}'

Body Parameters

The source account comes from the path ({accountId}) and the currency is always BRL — neither is sent in the body.

FieldTypeRequiredDescription
amountnumberYesAmount in BRL (e.g., 100.00)
keyTypestringYesKey type: CPF, CNPJ, EMAIL, PHONE, EVP
keystringYesRecipient’s PIX key
descriptionstringNoTransfer description (max 140 characters)
identifierstringNoIntegrator-provided identifier for tracking and reconciliation. Appears in the statement when the payment is reconciled.

Success Response

The HTTP status reflects the outcome: 200 (COMPLETED), 422 (FAILED), 202 (TIMEOUT/PENDING — indeterminate; check the statement before retrying).

Immediate rejections by the settlement bank (anti-fraud or insufficient settlement funds) return 422 FAILED right away, with errorCode (partner_rejected / insufficient_funds) and errorReason filled in. Transfers held at the settlement bank show as PENDING_APPROVAL on lookups and wait for the outcome for up to ~25 minutes before marking TIMEOUT; the result arrives via the pix.out.completed / pix.out.failed / pix.out.timeout webhooks.

In that state, the lookup and the pix.out.timeout webhook carry hold (owner: "partner" + reason) and the raw state in partnerStatus/partnerStatusId. PENDING_APPROVAL never means an approval is pending on your side or on ours: the decision belongs to the settlement bank. And TIMEOUT is not the end — we keep polling for up to 7 days and, if it settles or rejects, you receive pix.out.completed / pix.out.failed with late: true and the same paymentId.

{
"paymentId": "pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"transactionId": "txn-abc123-def456",
"endToEndId": "E12345678202301011234abcdefghijkl",
"status": "COMPLETED",
"completedAt": "2026-01-28T15:00:02Z",
"identifier": "order-12345",
"workflowId": "pix-out-{accountId}-{identifier}"
}
FieldDescription
paymentIdInternal payment intent ID (tracking/reconciliation)
transactionIdTransaction ID
endToEndIdBACEN E2E ID (present once settled)
statusCOMPLETED, FAILED, TIMEOUT, PENDING, PROCESSING
errorCode / errorReasonset when FAILED

Error Response

{
"errorCode": "insufficient_funds",
"message": "Saldo insuficiente na conta do liquidante para concluir a operação."
}

(The API returns messages in Portuguese; the example above means “insufficient balance at the settlement bank to complete the operation”.)

Timeout on the sync flow

If the settlement bank does not confirm within the window, the API responds 202 with status: "TIMEOUT" and a warning field — there is no 207. The PIX may already have been sent: check the statement or the payment lookup before retrying. The late outcome arrives via webhook (pix.out.completed / pix.out.failed).

Use POST /v1/accounts/{accountId}/pix/out/async to schedule payment and get immediate 202:

curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/out/async" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-yourcompany" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: transfer-order-async-12345" \
-d '{
"amount": 100.00,
"keyType": "CPF",
"key": "12345678901",
"description": "Service payment",
"identifier": "order-12345-async"
}'

Response:

{
"paymentId": "pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"workflowId": "pix-out-{accountId}-{identifier}",
"runId": "b7c1f0e2-...",
"idempotencyKey": "transfer-order-async-12345",
"identifier": "order-12345-async",
"status": "ACCEPTED"
}

The 202 response includes a Location header pointing to /v1/accounts/{accountId}/payments/{identifier} — an alias kept for compatibility that responds with Deprecation headers. The canonical lookup route is GET /v1/accounts/{accountId}/pix/payments/lookup?identifier=. The final result (success/failure) is delivered by webhook and can also be checked by identifier/paymentId.

Step 3: Check Transfer Status

After executing a transfer, you can check its status using the E2E ID:

Request

curl -X GET "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/transactions?endToEndId=E12345678202301011234abcdefghijkl" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa"

Query Parameters

ParameterTypeRequiredDescription
endToEndIdstringYes*Transaction E2E ID
identifierstringYes*Charge or reference identifier

*At least one of the two (endToEndId or identifier) is required. The accountId goes in the path, not in the query.

Success Response (200 OK)

This route responds with the same envelope as the statement (items[]), with 0 or 1 item. Timestamps (timestamp) are in São Paulo time (-03:00).

{
"accountId": "{accountId}",
"source": "live",
"page": 0,
"size": 1,
"totalElements": 1,
"totalPages": 1,
"hasNext": false,
"items": [
{
"partnerId": "a697b489-681a-451c-a043-d4ae65be8c80",
"endToEndId": "E12345678202301011234abcdefghijkl",
"direction": "OUT",
"transactionType": "D",
"operation": "PIX",
"status": "COMPLETED",
"amount": -100.00,
"currency": "BRL",
"description": "PIX - MARIA DA SILVA",
"identifier": "order-12345",
"timestamp": "2026-01-28T15:00:00-03:00",
"counterParty": {
"name": "MARIA DA SILVA",
"document": "123***01",
"bankCode": "001"
}
}
],
"fetchedAt": "2026-01-28T18:00:05Z"
}

To get a single object (instead of the items[] envelope), use GET /v1/accounts/{accountId}/pix/payments/lookup?identifier=... (or ?endToEnd=...).

Possible Statuses

StatusDescription
COMPLETEDSettled successfully
PROCESSINGBeing processed at the partner
PENDING_APPROVALHeld inside the settlement bank (its internal authorisation queue or risk review). Read hold.owner / hold.reason — no approval is pending on your side or on ours
FAILEDFailed / rejected
REVERSEDReversed / returned
UNKNOWNPartner status outside the known mapping

State flow

  • The outcome arrives via pix.out.completed / pix.out.failed. On TIMEOUT, the API also emits pix.out.timeout (indeterminate — check the statement; a late completed/failed may still arrive afterwards, with late: true and the same paymentId).
  • Retrying with the same Idempotency-Key depends on the previous outcome: if the payment ended in FAILED, the key is released and the new request re-executes the payment (since v2.43.3); if it ended in TIMEOUT, no retry happens — the state is indeterminate and the API returns the recorded result. With a new key you may duplicate the transfer. Details in Idempotency.

Always save the identifier of transfers and use it to check the status — it is the ID you define, stable and available from creation (the endToEndId only exists after settlement).

Full Example: Cash Out Script

#!/bin/bash
# Configuration
API_URL="https://tenant.api.corpx.com"
TOKEN="your_token_here"
TENANT_ID="tenant-suaempresa"
ACCOUNT_ID="your_account"
# Transfer details
PIX_KEY="12345678901"
PIX_KEY_TYPE="CPF"
AMOUNT=100.00
echo "=== PIX CASH OUT ==="
echo ""
echo "PIX Key: $PIX_KEY ($PIX_KEY_TYPE)"
echo "Amount: R$ $AMOUNT"
echo ""
# 1. Confirm (in production, wait for user confirmation)
read -p "Confirm transfer? (y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "Transfer cancelled"
exit 0
fi
# 2. Execute transfer
echo ""
echo "Executing transfer..."
IDEMPOTENCY_KEY="cashout-$(date +%s)-$RANDOM"
transfer_response=$(curl -s -X POST "$API_URL/v1/accounts/$ACCOUNT_ID/pix/out" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: $TENANT_ID" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d "{
\"amount\": $AMOUNT,
\"keyType\": \"$PIX_KEY_TYPE\",
\"key\": \"$PIX_KEY\",
\"description\": \"Transfer via script\"
}")
# Check result
STATUS=$(echo "$transfer_response" | jq -r '.status')
E2E=$(echo "$transfer_response" | jq -r '.endToEndId')
if [ "$STATUS" = "COMPLETED" ]; then
echo ""
echo "=== TRANSFER COMPLETED ==="
echo "Status: $STATUS"
echo "E2E: $E2E"
echo "$transfer_response" | jq
else
echo ""
echo "=== RESULT ==="
echo "$transfer_response" | jq
fi

Decode QR Code

Before paying, you can decode the QR Code to display beneficiary details to the user:

curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/out/qr-code/decode" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-yourcompany" \
-H "Content-Type: application/json" \
-d '{
"emv": "00020126580014br.gov.bcb.pix0136123e4567-e89b-12d3-a456-426614174000..."
}'

Response (dynamic-immediate QR):

{
"key": "123e4567-e89b-12d3-a456-426614174000",
"amount": 150.00,
"originalAmount": 150.00,
"identifier": "8e4d8c19-1d3f-4b22-bf6f-79a4d0e1f001",
"decodeId": "8e4d8c19-1d3f-4b22-bf6f-79a4d0e1f001",
"qrCodeType": "dynamic-immediate",
"qrCodeTypeId": 1,
"allowChange": false,
"description": "Online purchase",
"payeeName": "EMPRESA EXEMPLO LTDA",
"payeeDocument": "12345678000190",
"bankIspb": "50871921",
"bankBranch": "0001",
"bankAccount": "123456-7",
"accountType": "CHECKING"
}

For a charge-with-due-date QR (dynamic-due-date), the response also includes the charge components (discount, deduction, interest, penalty), originalAmount (face value before adjustments), dueDate, paymentDeadline and payeeTradeName (the legal entity’s trade name). See the matching example in the OpenAPI.

You can reuse decodeId in the body of POST /pix/out/qr-code/async to skip a second decode at payment time.

After confirming, use the payment endpoint below to execute.

Pay QR Code (PIX Out via EMV)

If you have an EMV code (QR Code copy and paste), use the async endpoint (canonical):

POST /v1/accounts/{accountId}/pix/out/qr-code/async — immediate 202:

curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/out/qr-code/async" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-yourcompany" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pay-qr-async-12345" \
-d '{
"emv": "00020126580014br.gov.bcb.pix...",
"amount": 150.00,
"description": "QR Code payment",
"identifier": "pay-qr-async-12345"
}'

Response:

{
"paymentId": "pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"workflowId": "pix-out-{accountId}-{identifier}",
"runId": "b7c1f0e2-...",
"idempotencyKey": "pay-qr-async-12345",
"identifier": "pay-qr-async-12345",
"status": "ACCEPTED"
}

The 202 response includes a Location header pointing to the payment lookup. The final result (success/failure/timeout) is delivered by webhook (pix.out.completed, pix.out.failed, pix.out.timeout) and can also be queried by identifier/paymentId.

Sync endpoint (deprecated)

Deprecated

POST /v1/accounts/{accountId}/pix/out/qr-code (sync) is deprecated. It still works for compatibility and returns Deprecation, Sunset, and Link headers (sunset planned: 2026-11-21). Migrate to /pix/out/qr-code/async.

Transfer Webhook

After the transfer, you receive a webhook in the canonical envelope (id, type, occurredAt, schemaVersion, data):

{
"id": "pix-out-9ccd1869-7593-4feb-9602-e525e818ab8e",
"type": "pix.out.completed",
"occurredAt": "2026-09-16T20:58:46.258668746Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-yourcompany",
"accountId": "{accountId}",
"data": {
"paymentId": "9ccd1869-7593-4feb-9602-e525e818ab8e",
"tenantId": "tenant-yourcompany",
"accountId": "{accountId}",
"status": "SUCCESS",
"endToEnd": "E36741675202601281500001234567",
"transactionId": "162982-pix",
"amount": 100.00,
"currency": "BRL",
"description": "Supplier payment",
"identifier": "order-12345",
"originalTransactionId": "",
"initiatedAt": "2026-09-16T20:58:42.646763686Z",
"completedAt": "2026-09-16T20:58:46.258668746Z",
"key": {
"type": "CPF",
"key": "12345678901"
},
"payee": {
"name": "MARIA DA SILVA",
"document": "12345678901",
"bankCode": "001",
"bankIspb": "00000000",
"branch": "0001",
"accountNumber": "12345678"
}
}
}

The failure counterpart is pix.out.failed (with error inside data) and the indeterminate one is pix.out.timeout. The full field reference is in Webhooks.

BigPix (deprecated)

Deprecated

The R$ 15,000 per-transaction limit has been removed — POST /v1/accounts/{accountId}/pix/out now accepts any amount in a single transaction. BigPix (which split large amounts into multiple PIX transfers) is no longer necessary and is deprecated.

The /pix/out/bigpix and /pix/out/bank-account/bigpix endpoints remain functional for backward compatibility, but return the Deprecation: true header. Migrate to POST /pix/out (or /pix/out/bank-account). The final removal date will be announced in the changelog in advance.

Boleto payment

Boleto is the other cash out available on v2 (on v1 these endpoints answered 503). There are three routes: one to read the boleto before paying, one to pay, and one to check the outcome.

Step 1: Preview (read the boleto)

Resolves the payment line at the partner and returns 200 with the details of the bill (beneficiary, payer, amount, due date) so you can show it before debiting.

curl -X POST "${API_URL}/v1/accounts/${ACCOUNT_ID}/boleto/preview" \
-H "Authorization: Bearer ${JWT}" \
-H "X-Tenant-Id: ${TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{ "line": "34191790010104351004791020150008291070026000" }'

The canonical field is line; barcode is accepted as an alias. A boleto the partner cannot find returns 404 not_found.

{
"type": "boleto-payment",
"bank": "Itaú Unibanco S.A.",
"bankCode": "341",
"receiverName": "FORNECEDOR EXEMPLO LTDA",
"receiverTaxId": "12345678000190",
"dueDate": "2026-08-10",
"amount": 1430.63,
"discountAmount": 0,
"interestAmount": 3.29,
"fineAmount": 28.61,
"totalUpdated": 1462.53,
"status": "PAYABLE"
}

Pay totalUpdated, not amount

amount is the face value — the very number encoded in the barcode. totalUpdated is what the settlement bank accepts today: face + interest + fine − discount.

On an overdue slip the two differ, and the settlement bank rejects any value other than the updated one (boleto_amount_mismatch). Since charges accrue by the day, yesterday’s totalUpdated is already stale: run the preview on the day you pay, and fund the account for the updated amount, not the face value.

Step 2: Pay

curl -X POST "${API_URL}/v1/accounts/${ACCOUNT_ID}/boleto/pay" \
-H "Authorization: Bearer ${JWT}" \
-H "X-Tenant-Id: ${TENANT_ID}" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"line": "34191790010104351004791020150008291070026000",
"amount": 1462.53,
"taxId": "12345678000199",
"description": "ACME supplier"
}'
ParameterRequiredDescription
line (or barcode)YesPayment line / barcode
amountYesAmount to pay — the preview’s totalUpdated, not the face value
taxIdNoPayer’s CNPJ/CPF
scheduledNoScheduling, where the partner supports it
descriptionNoFree-text description

A divergent amount comes back as boleto_amount_mismatch (422), and the message carries the amount the settlement bank expects — re-run the preview and resend with it.

The response is always 202, never 200: the payment runs asynchronously and the body carries paymentId (= boletoId, shaped bol_{uuid}), status: "PROCESSING" and a Location header pointing at the status lookup.

{
"paymentId": "bol_aabbccdd-...",
"boletoId": "bol_aabbccdd-...",
"idempotencyKey": "...",
"amount": 250.00,
"status": "PROCESSING"
}

The boletoId is derived from (accountId, Idempotency-Key), so repeating the request with the same key returns the same paymentId and reuses the run in flight — no double debit.

Step 3: Check the outcome

curl -X GET "${API_URL}/v1/accounts/${ACCOUNT_ID}/boleto/payments/bol_aabbccdd-..." \
-H "Authorization: Bearer ${JWT}" \
-H "X-Tenant-Id: ${TENANT_ID}"

Accepts both the canonical paymentId (bol_...) and the partner reference. While the partner has not returned a reference yet, the route answers 200 with the last known local status (PROCESSING) instead of an error.

The outcome also arrives by webhook: boleto.paid and boleto.failed (see Webhooks). Settling a boleto can take hours — up to the next business day — so treat PROCESSING as a normal, long-lived state rather than a failure.

Common Errors

ErrorHTTPCauseSolution
key_not_found404PIX key does not exist in DICTCheck the key and its type
invalid_pix_key422Malformed key or mismatched key typeUse: CPF, CNPJ, EMAIL, PHONE, EVP
insufficient_funds422Insufficient balance as computed by the settlement bankCheck the account balance
limit_exceeded_daily / limit_exceeded_nightly / limit_exceeded_monthly / limit_exceeded_transaction422Account limit exceeded in the stated windowWait for the window to roll over or request an increase
partner_rejected422Refused by the settlement bank on risk/fraud groundsCheck the partner block for the stated reason
policy_denied422A tenant/account policy rule refused the transferCheck violations in the body and adjust the rule in the panel — Policies and Rules

The full list with messages and semantics is in Errors.

Retrying with the same Idempotency-Key on PIX out does not return 409: the API returns the recorded result (or re-executes it, if the previous outcome was FAILED).

Best Practices

  1. Always look up the key before transferring to validate the recipient
  2. Confirm with the user the details before executing
  3. Use a unique Idempotency Key per transfer — and reuse the same key when repeating the request, never a new one
  4. Save the E2E for tracking and support
  5. Configure webhooks to receive asynchronous confirmations
  6. Implement retry with exponential backoff for temporary failures

Limits

TypeDefault Limit
Per transactionNo fixed limit
DailyR$ 100,000.00
MonthlyNo limit

There is no longer a fixed per-transaction limit — the amount is bounded only by your account’s operational limits (see GET /v1/accounts/{accountId}/pix/limits).

Limits can be customized. Contact support for more information.

Next Steps