Cash Out Guide (PIX Out)
This guide explains step by step how to make PIX transfers (cash out) with prior key lookup.
Overview
Cash Out allows you to send money via PIX to any key registered in the Brazilian PIX system. The recommended flow is:
- Look up the key - Validate and obtain the recipient's details
- Confirm the details - Display to the user for confirmation
- 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
| Type | Format | Example |
|---|---|---|
CPF | 11 digits | 12345678901 |
CNPJ | 14 digits | 12345678000199 |
EMAIL | Valid e-mail | joao@email.com |
PHONE | +55 + area code + number | +5511999998888 |
EVP | UUID | 123e4567-e89b-12d3-a456-426614174000 |
Step 1: Look Up the PIX Key (Optional but Recommended)
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.
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.
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount in BRL (e.g., 100.00) |
keyType | string | Yes | Key type: CPF, CNPJ, EMAIL, PHONE, EVP |
key | string | Yes | Recipient's PIX key |
description | string | No | Transfer description (max 140 characters) |
identifier | string | No | Integrator-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 for risk analysis show as PENDING_APPROVAL on lookups and
wait for the outcome for up to ~30 minutes before marking TIMEOUT; the
result arrives via the pix.out.completed / pix.out.failed /
pix.out.timeout webhooks.
{
"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}"
}
| Field | Description |
|---|---|
paymentId | Internal payment intent ID (tracking/reconciliation) |
transactionId | Transaction ID |
endToEndId | BACEN E2E ID (present once settled) |
status | COMPLETED, FAILED, TIMEOUT, PENDING, PROCESSING |
errorCode / errorReason | set 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).
Async flow (recommended for high volume)
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
| Parameter | Type | Required | Description |
|---|---|---|---|
endToEndId | string | Yes* | Transaction E2E ID |
identifier | string | Yes* | 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
| Status | Description |
|---|---|
COMPLETED | Settled successfully |
PROCESSING | Being processed at the partner |
PENDING_APPROVAL | In internal approval queue |
FAILED | Failed / rejected |
REVERSED | Reversed / returned |
UNKNOWN | Partner status outside the known mapping |
State flow
- The outcome arrives via
pix.out.completed/pix.out.failed. OnTIMEOUT, the API also emitspix.out.timeout(indeterminate — check the statement; a latecompleted/failedmay still arrive afterwards). - Retrying with the same
Idempotency-Keydepends on the previous outcome: if the payment ended inFAILED, the key is released and the new request re-executes the payment (since v2.43.3); if it ended inTIMEOUT, 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):
Async flow (recommended)
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)
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": "evt_out_123",
"type": "pix.out.completed",
"occurredAt": "2026-01-28T15:00:02.900Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-yourcompany",
"accountId": "{accountId}",
"data": {
"endToEnd": "E36741675202601281500001234567",
"key": {
"type": "CPF",
"key": "12345678901"
},
"identifier": "order-12345",
"amount": 100.00,
"payee": {
"name": "MARIA DA SILVA",
"document": "12345678901",
"bankCode": "001"
},
"completedAt": "2026-01-28T15:00:02.900Z"
}
}
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)
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.
Common Errors
| Error | HTTP | Cause | Solution |
|---|---|---|---|
key_not_found | 404 | PIX key does not exist in DICT | Check the key and its type |
invalid_pix_key | 422 | Malformed key or mismatched key type | Use: CPF, CNPJ, EMAIL, PHONE, EVP |
insufficient_funds | 422 | Insufficient balance as computed by the settlement bank | Check the account balance |
limit_exceeded_daily / limit_exceeded_nightly / limit_exceeded_monthly / limit_exceeded_transaction | 422 | Account limit exceeded in the stated window | Wait for the window to roll over or request an increase |
partner_rejected | 422 | Refused by the settlement bank on risk/fraud grounds | Check the partner block for the stated reason |
policy_denied | 422 | A tenant/account policy rule refused the transfer | Check 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
- Always look up the key before transferring to validate the recipient
- Confirm with the user the details before executing
- Use a unique Idempotency Key per transfer — and reuse the same key when repeating the request, never a new one
- Save the E2E for tracking and support
- Configure webhooks to receive asynchronous confirmations
- Implement retry with exponential backoff for temporary failures
Limits
| Type | Default Limit |
|---|---|
| Per transaction | No fixed limit |
| Daily | R$ 100,000.00 |
| Monthly | No 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
- Refund Guide - Reverse received transfers
- Webhooks - Configure notifications
- Errors - Full list of error codes