PIX Cash-Out Copy and Paste
Performs a PIX transfer using the EMV code (copy and paste) extracted from a PIX QR Code.
Endpoint
POST /api/external/pix/cash-outHeaders
| Header | Type | Required | Description |
|---|---|---|---|
Authorization | String | Yes | ApiKey {client_id}:{client_secret} |
Content-Type | String | Yes | application/json |
hmac | String | Yes | HMAC-SHA512 signature of the body (hex) |
Idempotency-Key | String | No | Unique key to prevent duplicate processing (max 256 chars). Behavior identical to what is documented in PIX Cash-Out by Key |
Required permission
The API Key must have the transfer:write permission to send PIX. Without it, the request returns 403 Forbidden. See how to configure permissions.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
amount | Integer | Yes | Amount in centavos. R$ 30.00 = 3000. On dynamic QR, this value is overridden by the lookup to the QR URL — see below |
emv | String | Yes | EMV copy-and-paste code from the PIX QR Code. The alias codigo_copia_cola is also accepted but emv is canonical — prefer it |
description | String | No | Transfer description (max 140 characters) |
external_id | String | No | Your system identifier for tracking. Max 128 chars after trim. Only a-zA-Z0-9._:- characters. Returned in responses and webhooks. Invalid values are silently discarded (the transaction proceeds with external_id: null). See PIX Cash-Out by Key for details. |
end_to_end_id | String | No | End-to-End ID in BACEN format. Recommended to omit — the backend generates a deterministic one. Only send in coordinated reprocessing scenarios |
Copy-and-Paste code
The emv field accepts the full EMV code copied from a PIX QR Code. The API automatically extracts the PIX key, recipient data, and the original charge amount from the EMV payload. The backend validates CRC-16/CCITT-FFFF and the TLV structure before proceeding.
Static QR vs Dynamic QR
The API treats the two types differently:
Static QR (PoIM=11, no URL):
- The PIX key and metadata come from the EMV payload itself
- The
amountsent in the request is used directly as the transfer value - No external lookup — immediate settlement after DICT validation
Dynamic QR (PoIM=12, with URL in tag 26/25):
- The backend calls
Provider.consult_qrcodeon the URL embedded in the EMV payload and receives a JWS (BACEN Manual 2.9.0) - The authoritative value comes from the JWS response (
valor.original) and overrides theamountsent by the client - If the URL lookup fails (timeout, invalid JWS, QR expired at issuer), returns HTTP 422 with
detail: "QR Code dinamico nao pode ser resolvido" - Use the overridden value as the source of truth —
net_amountin the response reflects the actual charged value
Do not rely on the value sent in dynamic QR
On dynamic QR, always check the POST response to get the effective amount. The backend ignores any divergence between the value sent and the value returned by the QR issuer — this is a BACEN requirement.
Monetary values
Request values are in centavos (R$ 1.00 = 100). Response values are in base units (R$ 1.00 = 10000). To convert the response to BRL, divide by 10,000. Never use floating point.
Example
curl -X POST https://api.owem.com.br/api/external/pix/cash-out \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "hmac: $HMAC" \
-d '{
"amount": 3000,
"emv": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d5204000053039865802BR5913NOME RECEBEDOR6008BRASILIA62070503***6304ABCD",
"description": "Pagamento via QR Code",
"external_id": "invoice-4521"
}'Success Response -- 200 / 202
{
"worked": true,
"final": false,
"transaction_id": "PIXOUT20260309a1b2c3d4e5f6",
"end_to_end_id": "E37839059202603091530abcdef01",
"external_id": "invoice-4521",
"amount": 300000,
"fee_amount": 350,
"net_amount": 300350,
"status": "accepted",
"detail": "PIX enviado para processamento"
}HTTP 200 vs 202
- HTTP 200: Transaction already settled (
final: true,status: "settled"). - HTTP 202: Transaction accepted for processing (
final: false).statuscan be"accepted"(normal flow),"queued"(DICT rate-limit — automatic retry every 3s for up to 120min, session 155) or"pending_approval"(awaiting approval). Track the status via polling or webhook.
| Field | Type | Description |
|---|---|---|
worked | Boolean | true indicates the request was accepted |
final | Boolean | true when the transaction reached a terminal state (settled or rejected). false when still processing |
transaction_id | String | Unique transaction identifier |
end_to_end_id | String | End-to-End identifier in SPI/BACEN |
external_id | String | Your identifier, returned as sent. null if not provided |
amount | Integer | Transfer amount in base units (÷ 10,000 for BRL). 300000 = R$ 30.00. On dynamic QR, reflects the authoritative value returned by the JWS lookup — may diverge from the amount sent in the request |
fee_amount | Integer | Fee charged in base units (÷ 10,000 for BRL) |
net_amount | Integer | Gross amount debited from the paying account (amount + fee_amount) — this is not what the recipient receives. See the note in PIX Cash-Out by Key on the semantic asymmetry between cash-out and cash-in |
status | String | One of: accepted, settled, queued, pending_approval. See details in PIX Cash-Out by Key |
detail | String | Descriptive message |
Rejection Codes
Cash-out-by-EMV rejections follow exactly the same pattern as PIX Cash-Out by Key — two body formats (Format A {status: "failed", errors: [...]} and Format B {errors: {bad_request: "..."}}), three error classes (validation / integration / queued rate-limit), and the same UPPERCASE × lowercase vocabularies. The items below highlight only the codes specific to the EMV path.
EMV-specific validation errors (HTTP 400 / 422)
| HTTP | Format | Field with code | Meaning |
|---|---|---|---|
| 400 | B | errors.bad_request: "invalid emv payload" | Malformed EMV payload (invalid CRC-16, incomplete TLV, missing mandatory tag) — detected by the parser before the Orchestrator |
| 400 | B | errors.bad_request: "invalid or missing amount" | amount missing, zero, negative, or non-integer (static QR) |
| 422 | A | errors[0].code: "same_institution_transfer" | QR generated by an Owem account — intra-institutional PIX not supported (use TEF). Note: HTTP 422 (not 400), with shape {status: "failed", errors: [{code: "same_institution_transfer", params: []}]} |
| 422 | — | detail: "QR Code dinamico nao pode ser resolvido" | Failure in the dynamic QR URL lookup (timeout, invalid JWS, QR expired at issuer) — this path is handled by the controller before the Orchestrator, returns legacy shape {errors: {unprocessable_entity: "..."}} |
| 422 | A | errors[0].code: "insufficient_balance" | Available balance less than amount + fee_amount |
Other errors
For the codes dict_key_not_found, dict_key_blocked, dict_rate_limited (synchronous), dict_bucket_exhausted (synchronous), dict_lookup_failed, provider_rejected, provider_schema_error, provider_unknown_error — all return HTTP 400 in Format A (not 429 or 503). Details in PIX Cash-Out by Key — Integration errors.
Rate-limit with automatic retry (HTTP 202 queued)
When the per-merchant ClientLimiter (default DICT_CLIENT_MAX_PER_MIN=120) or the global DictBucket.Guard (refill DICT_BUCKET_REFILL_RATE=18/min, capacity 250) detects that the limit has been reached before reaching OnZ, the cash-out is enqueued in Fluxiq.Workers.PixOutRetryWorker and returns HTTP 202 with status: "queued". The pix_out_retry_queue_enabled flag is ON in PRD since session 155. Full details in PIX Cash-Out by Key — Rate-limit with automatic retry.
Asynchronous BACEN rejections
After initial acceptance (HTTP 202), the SPI/BACEN may reject via PACS.002 RJCT. This rejection:
- Does not change the original HTTP response (already sent as 202)
- Appears via
GET /transactions/:idwithstatus: "failed"+reason_code(AC03, AB03, ED05 etc.) +reason_descriptionin English - Fires webhook
pix.payout.rejectedwith the same structured fields
See the full list of BACEN codes in Query Cash-Out by ID.
Next Steps
After creating the transfer, track the status via:
- Query by ID
- Query by E2E ID
- Query by Tag
- Query by External ID --
GET /api/external/transactions/ref/{external_id}
Or receive confirmation automatically via Webhook.