Cashout API - Overview

Welcome to the Cashout API documentation. This guide provides a comprehensive overview of how to use our PIX transfer system to send money from your accounts to recipients via the Brazilian instant payment network.

Table of Contents


What is Cashout?

Cashout is the process of transferring money from your account to external recipients using the PIX instant payment system. PIX is Brazil's instant payment platform that enables real-time transfers 24/7, including weekends and holidays.

With our Cashout API, you can:

  • Send money using PIX keys (email, phone, CPF/CNPJ, or random key)
  • Transfer funds using complete bank account details
  • Use PIX copy-and-paste codes (BR Codes) for payments
  • Implement single-step or two-step approval workflows
  • Ensure safe retries with idempotency keys

Approval Workflows

Our API supports two different approval workflows to suit your business needs:

Two-Step Approval (Create + Approve)

Use Case: Organizations that require manual review, compliance checks, or dual authorization before processing transfers.

Flow:

  1. Create Transaction - Use /create-cashout or /create-cashout-manual to create a transaction with status NEW
  2. Review - Perform any necessary validation or approval processes
  3. Approve Transaction - Use /approve-cashout to approve and initiate the transfer (status changes to PROCESSING)

Benefits:

  • Clear audit trail with separate creation and approval steps
  • Time to review transactions before processing
  • Supports different user roles (creator vs approver)
  • Compliance-friendly for organizations with strict controls

Endpoints:

One-Step Approval (Self-Approve)

Use Case: Trusted operations where immediate processing is required without separate approval.

Flow:

  1. Create and Approve - Single API call creates the transaction and immediately processes it (status goes from NEW to PROCESSING)
  2. Background Processing - Transfer is initiated automatically

Benefits:

  • Faster processing for trusted operations
  • Simplified integration with fewer API calls
  • Ideal for automated systems or low-risk transfers

Important: Always use idempotency keys with self-approve endpoints to prevent duplicate transfers!

Endpoints:


PIX Key Types and Formats

PIX keys are identifiers that simplify transfers by eliminating the need to provide complete bank account details. Our API supports all PIX key types:

1. Email

  • Format: Standard email format
  • Example: [email protected]
  • Validation: Must be a valid email address
  • Case Sensitivity: Case-insensitive

2. Phone Number

  • Format: Must include country code with + prefix
  • Example: +5511999999999
  • Pattern: +55 (Brazil) + 11 (area code) + 9 (mobile) + XXXXXXXX (number)
  • Validation:
    • Must start with +55 for Brazilian numbers
    • Mobile numbers: 11 digits after country code (includes the digit 9)
    • Landline numbers: 10 digits after country code
  • Important: Always include the + prefix and country code

3. CPF (Individual Tax ID)

  • Format: 11 digits, numbers only (no formatting)
  • Example: 11122233344
  • Validation: Must be a valid CPF number
  • Note: Do not include dots or dashes (. or -)

4. CNPJ (Company Tax ID)

  • Format: 14 digits, numbers only (no formatting)
  • Example: 11222333000144
  • Validation: Must be a valid CNPJ number
  • Note: Do not include dots, dashes, or slashes

5. EVP (Random Key)

  • Format: UUID v4 format
  • Example: 123e4567-e89b-12d3-a456-426614174000
  • Description: Randomly generated key created by the recipient's bank
  • Validation: Must be a valid UUID format with hyphens

PIX Key Format Summary Table

Key TypeFormatExampleNotes
Email[email protected][email protected]Case-insensitive
Phone+55AAXXXXXXXXX+5511999999999Must include +55 prefix
CPF11 digits11122233344Numbers only, no formatting
CNPJ14 digits11222333000144Numbers only, no formatting
EVPUUID v4123e4567-e89b-12d3-a456-426614174000Random key with hyphens

Account Types for Manual Transfers

When creating cashout transactions using manual recipient account details (instead of PIX keys), you must specify the account type and account model:

Account Type Values

The recipient_account_type field accepts these values:

ValueDescriptionUse Case
CURRENT_ACCOUNTCurrent AccountStandard checking account for daily transactions
PAYMENT_ACCOUNTPayment AccountDigital payment account (e.g., fintech or digital banks)
SAVING_ACCOUNTSavings AccountSavings account with limited transaction frequency

Account Model

The recipient_account_model field must always be set to the fixed value:

"recipient_account_model": "Movement"

Important: This is a required field with a fixed value when using manual account details.

Example

{
  "recipient_account_type": "CURRENT_ACCOUNT",
  "recipient_account_model": "Movement"
}

Applies to these endpoints:


Authentication

All Cashout API endpoints require two authentication mechanisms:

1. Bearer Token (Authorization Header)

Authenticate your requests using a JWT access token:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

How to obtain:

  • Use the authentication endpoint to obtain an access token
  • Tokens have an expiration time and must be refreshed when expired

2. HMAC Signature (hmac Header)

Ensure request integrity using HMAC-SHA256 signature:

hmac: 57373705c83bc5efe41001790c54642e670088c0c87d56bc8f990f2260c7740b...

How it works:

  • HMAC (Hash-based Message Authentication Code) combines your secret key with the request body
  • Generates a cryptographic hash that verifies the request hasn't been tampered with
  • Both headers are required for all requests

Important: Never share your HMAC secret key. Store it securely and use it only server-side.


Idempotency

Idempotency is a mechanism to safely retry API requests without risk of creating duplicate transactions. This is critically important for financial operations.

What is Idempotency?

When you include an idempotency-key header in your request, the API guarantees that retrying the same request (with the same key and body) will not create duplicate transactions.

How It Works

idempotency-key: 550e8400-e29b-41d4-a716-446655440000

Scenarios:

  1. First Request (HTTP 200 - New Transaction Created):

    • Idempotency key is new (doesn't exist in database)
    • API creates a new transaction and returns details
    • Response includes a new transaction_id
    • Webhooks will be sent when transaction completes
  2. Retry with Same Key + Same Body (HTTP 200 - Existing Transaction Returned):

    • Idempotency key already exists in database with the same request body
    • API returns the existing transaction (no new transaction created)
    • Response has the same transaction_id as the first request
    • Status reflects current database state: Can be PROCESSING, SUCCESS, ERROR, CANCELLED, or REFUNDED
    • Important: No new webhooks are sent - this is a replay of the original request
    • Safe to retry without risk of duplicate transfers
    • Always check status field - Don't assume the transaction succeeded
  3. Same Key + Different Body (HTTP 409 Conflict):

    • Idempotency key already exists but with a different request body
    • API rejects the request to prevent data inconsistency
    • Returns error response with details
    • Action required: Generate a new idempotency key for the new transaction

Example Error Response:

{
  "worked": false,
  "detail": "Idempotency key already used with a different request",
  "message": "Idempotency key already used with a different request",
  "data": null
}

Key Format

  • Format: UUID version 4 (RFC 4122)
  • Example: 550e8400-e29b-41d4-a716-446655440000
  • Generation: Use a cryptographically secure UUID v4 generator
  • Scope: Unique per account (not globally unique)
  • Case: Case-insensitive, but lowercase is recommended

When to Use

Endpoint TypeIdempotency Key
Two-Step (Create)Recommended - Prevents duplicate creation
One-Step (Self-Approve)Strongly Recommended - Prevents duplicate transfers and financial losses
ApproveNot needed - Status validation prevents duplicates

Best Practices

  1. Generate on Client: Create the UUID before making the request
  2. Store with Transaction: Save both the idempotency key and transaction_id from the response
  3. Check Transaction ID on Retry: If you retry a request, compare the returned transaction_id with your stored value:
    • Same transaction_id = This is the existing transaction (no new transfer will be processed)
    • Different transaction_id = This is a new transaction (new transfer will be processed)
  4. One Key per Transaction: Never reuse idempotency keys for different transactions
  5. Retry with Same Key: Always use the same idempotency key when retrying the exact same request
  6. Handle 409 Conflicts: If you receive HTTP 409, it means you're trying to use an existing key with different data - generate a new idempotency key
  7. Permanent Storage: Idempotency keys are stored indefinitely in the system

Practical Example: Handling Retries

First Request - New Transaction Created

POST /create-cashout-self-approve
idempotency-key: 550e8400-e29b-41d4-a716-446655440000

Request Body:
{
  "source_account_branch_identifier": "0001",
  "source_account_number": "123456",
  "amount": 100.00,
  "key": "111*****999"
}

Response (HTTP 200):

{
  "worked": true,
  "transaction_id": 1134,
  "status": "PROCESSING",
  "amount": 100.00,
  "fee": 0.0,
  "key": "111*****999",
  "from_accout": "123456",
  "code_transaction": null,
  "recipient_name": null,
  "recipient_account_id": null,
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
  "erro_descriptor": ""
}

βœ… Action: Store transaction_id: 1134 and idempotency_key in your database for tracking.


Retry Request - Existing Transaction Returned

Network error occurs, you retry with the same idempotency key and same body:

POST /create-cashout-self-approve
idempotency-key: 550e8400-e29b-41d4-a716-446655440000

Request Body: (exactly the same as before)
{
  "source_account_branch_identifier": "0001",
  "source_account_number": "123456",
  "amount": 100.00,
  "key": "111*****999"
}

Response (HTTP 200) - Example 1: Successful Transaction

{
  "worked": true,
  "transaction_id": 1134,
  "status": "SUCCESS",
  "amount": 100.00,
  "fee": 0.0,
  "key": "111*****999",
  "from_accout": "123456",
  "code_transaction": "E12345678901234567890123456789AB",
  "recipient_instution": "12345678",
  "recipient_account_id": "654321",
  "recipient_branch_id": "0001",
  "recipient_legal_id": "111*****999",
  "recipient_name": "RECIPIENT NAME",
  "recipient_account_type": "CURRENT_ACCOUNT",
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
  "erro_descriptor": ""
}

Response (HTTP 200) - Example 2: Cancelled Transaction

{
  "worked": true,
  "transaction_id": 1134,
  "status": "CANCELLED",
  "amount": 100.00,
  "fee": 0.0,
  "key": "111*****999",
  "from_accout": "123456",
  "code_transaction": null,
  "recipient_instution": null,
  "recipient_account_id": null,
  "recipient_branch_id": null,
  "recipient_legal_id": null,
  "recipient_name": null,
  "recipient_account_type": null,
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
  "erro_descriptor": "Transaction cancelled - Account limit exceeded"
}

Response (HTTP 200) - Example 3: Error Transaction

{
  "worked": true,
  "transaction_id": 1134,
  "status": "ERROR",
  "amount": 100.00,
  "fee": 0.0,
  "key": "111*****999",
  "from_accout": "123456",
  "code_transaction": null,
  "recipient_instution": null,
  "recipient_account_id": null,
  "recipient_branch_id": null,
  "recipient_legal_id": null,
  "recipient_name": null,
  "recipient_account_type": null,
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
  "erro_descriptor": "Payment processing error - Insufficient funds"
}

Response (HTTP 200) - Example 4: Refunded Transaction

{
  "worked": true,
  "transaction_id": 1134,
  "status": "REFUNDED",
  "amount": 100.00,
  "fee": 0.0,
  "key": "111*****999",
  "from_accout": "123456",
  "code_transaction": "E12345678901234567890123456789AB",
  "recipient_instution": "12345678",
  "recipient_account_id": "654321",
  "recipient_branch_id": "0001",
  "recipient_legal_id": "111*****999",
  "recipient_name": "RECIPIENT NAME",
  "recipient_account_type": "CURRENT_ACCOUNT",
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
  "erro_descriptor": "Transaction was refunded by recipient"
}
{
  "worked": true,
  "transaction_id": 1134,
  "status": "CANCELLED",
  "amount": 100.00,
  "fee": 0.0,
  "key": "111*****999",
  "from_accout": "123456",
  "code_transaction": null,
  "recipient_instution": null,
  "recipient_account_id": null,
  "recipient_branch_id": null,
  "recipient_legal_id": null,
  "recipient_name": null,
  "recipient_account_type": null,
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
  "erro_descriptor": "Transaction cancelled - Account limit exceeded"
}

πŸ” How to Identify This is the Same Transaction:

Compare the transaction_id from both responses:

  • First request: transaction_id: 1134
  • Retry request: transaction_id: 1134 ← SAME ID!

βœ… This means: No new transaction was created. You are receiving the current status of the existing transaction from the database.

Key Observations:

  1. Same transaction_id (1134) - This is your primary indicator that no duplicate was created
  2. Status reflects database state - Can be PROCESSING, SUCCESS, ERROR, CANCELLED, or REFUNDED depending on transaction outcome
  3. Always check the status field - Don't assume the transaction succeeded
  4. Check erro_descriptor - If status is ERROR, CANCELLED, or REFUNDED, this field contains the reason
  5. Fields populated on success - code_transaction, recipient_name, etc. only have values when status is SUCCESS
  6. Same idempotency_key - Confirms this is the replay of your original request

Important: The response always reflects the real-time state of the transaction in the database, not a cached response.


Conflict Request - Different Body with Same Key

You try to create a different transaction using the same idempotency key:

POST /create-cashout-self-approve
idempotency-key: 550e8400-e29b-41d4-a716-446655440000

Request Body: (DIFFERENT from original - different amount)
{
  "source_account_branch_identifier": "0001",
  "source_account_number": "123456",
  "amount": 200.00,
  "key": "111*****999"
}

Response (HTTP 409 Conflict):

{
  "worked": false,
  "detail": "Idempotency key already used with a different request",
  "message": "Idempotency key already used with a different request",
  "data": null
}

❌ This means: The idempotency key is already associated with a different transaction (different request body).

Action Required:

  • Generate a new idempotency key for this new transaction
  • Never reuse idempotency keys for different transactions
  • Each unique transaction must have its own unique idempotency key

Implementation Example

// First attempt
const idempotencyKey = "550e8400-e29b-41d4-a716-446655440000";
const requestBody = {
  source_account_branch_identifier: "0001",
  source_account_number: "123456",
  amount: 100.00,
  key: "111*****999"
};

// Store in your database before making the request
await db.saveTransaction({
  idempotency_key: idempotencyKey,
  request_body: requestBody,
  status: "PENDING"
});

const response1 = await createCashout(idempotencyKey, requestBody);
// { transaction_id: 1134, status: "PROCESSING", ... }

// Update your database with the transaction_id
await db.updateTransaction({
  idempotency_key: idempotencyKey,
  transaction_id: response1.transaction_id,
  status: response1.status
});

// Network error or timeout - retry with SAME idempotency key
const response2 = await createCashout(idempotencyKey, requestBody);
// { transaction_id: 1134, status: "SUCCESS", ... }

// Compare transaction_id
const storedTransaction = await db.getTransactionByIdempotencyKey(idempotencyKey);

if (response2.transaction_id === storedTransaction.transaction_id) {
  // βœ… SAME TRANSACTION - Safe replay
  console.log("Same transaction returned - no duplicate created");
  
  // Update status with current information
  await db.updateTransaction({
    transaction_id: response2.transaction_id,
    status: response2.status,
    code_transaction: response2.code_transaction,
    recipient_name: response2.recipient_name
  });
  
  // Do NOT wait for a new webhook - use current status
  if (response2.status === "SUCCESS") {
    console.log("Transaction completed successfully!");
  } else if (response2.status === "ERROR") {
    console.error("Transaction error:", response2.erro_descriptor);
  } else if (response2.status === "CANCELLED") {
    console.error("Transaction cancelled:", response2.erro_descriptor);
  } else if (response2.status === "REFUNDED") {
    console.warn("Transaction was refunded:", response2.erro_descriptor);
  } else if (response2.status === "PROCESSING") {
    console.log("Transaction still being processed...");
  }
} else {
  // ❌ DIFFERENT TRANSACTION (this should not happen with same key+body)
  console.error("Unexpected: different transaction_id returned!");
}

How Idempotency Works Internally

Our API validates idempotency directly against the database:

  1. First Request:

    • Idempotency key doesn't exist in database
    • New transaction is created and stored
    • Response is returned with new transaction_id and initial status (NEW or PROCESSING)
  2. Retry with Same Key + Same Body:

    • System finds existing transaction with the idempotency key
    • Compares stored request body with incoming request body
    • Bodies match β†’ Returns existing transaction with current database state
    • Important: Status reflects real-time transaction outcome:
      • PROCESSING - Still being processed
      • SUCCESS - Transfer completed successfully
      • ERROR - Payment processing error (check erro_descriptor)
      • CANCELLED - Transaction cancelled due to business rules or restrictions (check erro_descriptor)
      • REFUNDED - Transaction was completed but later refunded (check erro_descriptor)
  3. Same Key + Different Body:

    • System finds existing transaction with the idempotency key
    • Compares stored request body with incoming request body
    • Bodies don't match β†’ Returns HTTP 409 Conflict

Key Differences:

  • βœ… Permanent storage: Idempotency keys are stored indefinitely (not just 24 hours)
  • βœ… Real-time status: Retries return the current transaction state from database, not a cached response
  • βœ… Database-based: Direct comparison with stored transactions, not cache lookup
  • βœ… Live transaction state: Status can change between retries as the transaction progresses

Critical Points for Clients

  1. Always compare transaction_id between requests to identify if you received a new or existing transaction
  2. Always check the status field - Don't assume success! The transaction may have been cancelled, encountered an error, or been refunded
  3. Status can vary - The same transaction can return different statuses depending on when you retry:
    • First call: PROCESSING
    • Retry 1 minute later: SUCCESS, ERROR, CANCELLED, or REFUNDED
  4. Check erro_descriptor on failures - If status is ERROR, CANCELLED, or REFUNDED, this field explains why
  5. Don't wait for duplicate webhooks - Retried requests don't trigger new webhook notifications
  6. Use the returned data - When you receive an existing transaction, its status reflects the real database state
  7. Store idempotency keys permanently - Keep them in your database for audit and reconciliation purposes

Transaction Status Flow

Understanding transaction statuses helps you track and manage transfers:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     NEW     β”‚  ← Transaction created (Two-Step only)
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”‚ Approval (manual or automatic)
       ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PROCESSING  β”‚  ← Transfer being executed
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”œβ”€β”€β”€ Success ──────────→ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚                        β”‚ SUCCESS β”‚  ← Transfer completed successfully
       β”‚                        β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
       β”‚                             β”‚
       β”‚                             β”‚ Refund requested
       β”‚                             ↓
       β”‚                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚                        β”‚ REFUNDED β”‚  ← Money returned to sender
       β”‚                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β”œβ”€β”€β”€ Payment Error ─────→ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
       β”‚                         β”‚  ERROR  β”‚  ← Payment processing failed
       β”‚                         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       └─── Business Rules ────→ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                 β”‚CANCELLED β”‚  ← Cancelled due to restrictions
                                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Status Descriptions

StatusDescriptionNext Steps
NEWTransaction created, waiting for approvalCall /approve-cashout to process
PROCESSINGTransfer is being executed by the payment systemWait for final status (async)
SUCCESSTransfer completed successfullyNo action needed (money sent)
ERRORPayment processing error occurredCheck erro_descriptor field for error details (e.g., insufficient funds, invalid recipient, payment system unavailable). Create a new transaction if needed
CANCELLEDTransaction cancelled due to business rules or restrictionsCheck erro_descriptor field for cancellation reason (e.g., account limits, blocked operations, compliance restrictions). Create a new transaction if needed
REFUNDEDTransaction was successful but later refundedCheck erro_descriptor field for refund reason. Money was returned to the sender account

Available Endpoints

Two-Step Approval Endpoints

MethodEndpointDescriptionDocumentation
POST/create-cashoutCreate transaction using PIX keyView Docs
POST/create-cashout-manualCreate transaction with manual account detailsView Docs
POST/approve-cashoutApprove a pending transactionView Docs
POST/approve-cashout-manualApprove a pending manual transactionView Docs

One-Step Self-Approve Endpoints

MethodEndpointDescriptionDocumentation
POST/create-cashout-self-approveCreate and auto-approve with PIX keyView Docs
POST/create-cashoutmanual-self-approveCreate and auto-approve with manual detailsView Docs
POST/create-copy-and-paste-transferCreate and auto-approve with BR CodeView Docs
POST/create-manual-transferWeb-based transfer with MFA validationView Docs

Common Business Rules

These rules apply to all Cashout endpoints:

Amount Validation

  • Must be greater than zero
  • Maximum of 2 decimal places
  • Example valid amounts: 100.00, 50.50, 1000
  • Example invalid amounts: 0, -10.00, 100.999

Account Identification

  • Branch Identifier: 4-digit code (e.g., 0001)
  • Account Number: Numeric string (e.g., 123456)
  • Both are required to identify the source account

Recipient Account Details (Manual Transfers)

When not using a PIX key, you must provide:

  • Recipient name
  • Recipient CPF or CNPJ
  • Recipient bank ISPB code (8 digits)
  • Recipient account number and digit
  • Recipient branch number
  • Account type (Current, Savings, or Payment)

HMAC Validation

  • All request bodies must be validated with HMAC signature
  • Invalid HMAC results in 400 Bad Request with response: {"worked": false, "detail": "HMAC invalid", "message": "HMAC invalid", "data": null}

Getting Started

Quick Start Guide

  1. Choose Your Workflow:

    • Need approval workflow? Use /create-cashout + /approve-cashout
    • Need immediate processing? Use /create-cashout-self-approve
  2. Prepare Authentication:

    • Obtain your Bearer token
    • Generate HMAC signature for your request body
  3. Generate Idempotency Key:

    // JavaScript example
    const { v4: uuidv4 } = require('uuid');
    const idempotencyKey = uuidv4();
    
  4. Format Your PIX Key:

    • Phone: +5511999999999 (with + and country code)
    • Email: [email protected]
    • CPF: 11122233344 (numbers only)
  5. Make Your First Request:

    POST /create-cashout-self-approve
    Authorization: Bearer <your_token>
    hmac: <computed_hmac>
    idempotency-key: 550e8400-e29b-41d4-a716-446655440000
    Content-Type: application/json
    
    {
      "source_account_branch_identifier": "0001",
      "source_account_number": "123456",
      "amount": 100.00,
      "key": "+5511999999999"
    }
    
  6. Handle the Response:

    • Check worked: true for success
    • Store the transaction_id for tracking
    • Check status field (NEW or PROCESSING)

Example Use Cases

Use Case 1: Automated Payouts

  • Use self-approve endpoints for faster processing
  • Always include idempotency keys
  • Monitor transaction status asynchronously

Use Case 2: Manual Review Required

  • Use two-step approval endpoints
  • Implement approval UI for authorized users
  • Maintain audit trail of who created vs who approved

Use Case 3: Customer-Initiated Transfers

  • Use /create-manual-transfer for web applications
  • Require MFA validation for security
  • Allow customers to choose between PIX key or manual details

Error Handling

All endpoints return standard HTTP status codes:

Status CodeMeaningAction
200SuccessProcess response data
400Bad RequestCheck request body, parameters, or HMAC signature
401UnauthorizedVerify Bearer token
409ConflictIdempotency key conflict - use new key

Support and Additional Resources

For detailed information about specific endpoints, refer to their individual documentation:


Last Updated: May 2026
API Version: v3