Avistadocs
Integration Guides

PIX Cash-Out (Payment)

Overview

The PIX Cash-Out endpoint allows you to make instant PIX payments to any valid PIX key (CPF, CNPJ, phone, email, or random key). The payment is processed in real time and the amount is debited from your account immediately.

For payments via PIX QR Code (scanning or copy-and-paste), use the dedicated Cash-Out via QR Code endpoint. This endpoint is exclusively for PIX key payments.

This endpoint requires a valid Bearer token. See the authentication documentation for more details.

Features

  • Instant payments 24/7
  • Support for all PIX key types
  • Automatic recipient data validation
  • Unique identification via externalId
  • Customizable description for the recipient
  • Automatic balance verification

Endpoint

POST /api/pix/cash-out

Makes a PIX payment.

Required Headers

Authorization: Bearer {token}
Content-Type: application/json

Request Body

{
  "value": 250.50,
  "details": {
    "key": "12345678901",
    "keyType": "DOCUMENT",
    "name": "Ana Costa",
    "document": "12345678901"
  },
  "externalId": "PAYMENT-987654-20240119",
  "description": "Pagamento de fornecedor"
}

Request

curl -X POST https://api.avista.global/api/pix/cash-out \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "value": 250.50,
    "details": {
      "key": "12345678901",
      "keyType": "DOCUMENT",
      "name": "Ana Costa",
      "document": "12345678901"
    },
    "externalId": "PAYMENT-987654-20240119",
    "description": "Pagamento de fornecedor"
  }'

Response (201 Created)

{
  "transactionId": "9876",
  "externalId": "PAYMENT-987654-20240119",
  "status": "PENDING",
  "generateTime": "2024-01-19T15:45:00.000Z"
}

Request Parameters

valuenumberobrigatorio

Payment amount in BRL (Brazilian Reais). Must have at most 2 decimal places.

Minimum: 0.01

Example: 250.50

detailsobjectobrigatorio

Destination PIX key information.

details.keystringobrigatorio

Destination PIX key.

Accepted formats:

  • CPF: 12345678901 (11 digits)
  • CNPJ: 12345678000199 (14 digits)
  • Email: usuario@exemplo.com
  • Phone: 5511999999999 (with country and area code)
  • Random key: UUID format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
details.keyTypestringobrigatorio

PIX key type.

Accepted values:

  • DOCUMENT - CPF or CNPJ
  • EMAIL - Email address
  • PHONE - Phone number
  • RANDOM - Random key (UUID)

Example: "DOCUMENT"

details.namestringobrigatorio

Full name of the destination PIX key holder.

Validation: The name must match the one registered with the PIX key

Example: "Ana Costa"

details.documentstringobrigatorio

CPF or CNPJ of the holder (numbers only).

CPF: 11 digits

CNPJ: 14 digits

Validation: The document must match the one registered with the PIX key

Example: "12345678901"

externalIdstringobrigatorio

Unique external transaction identifier.

Maximum: 255 characters

Recommendation: Use a format that ensures uniqueness

Example: "PAYMENT-987654-20240119-154500"

descriptionstring

Payment description that will appear on the recipient's statement.

Maximum: 140 characters

Default: Empty

Example: "Pagamento de fornecedor - Nota Fiscal 12345"

Response Structure

transactionIdstringsempre presente

Internal transaction ID generated by Avista.

Example: "9876"

externalIdstringsempre presente

External ID provided in the request (same value as the input).

Example: "PAYMENT-987654-20240119"

statusstringsempre presente

Current transaction status.

Possible values:

  • PENDING: Payment being processed
  • CONFIRMED: Payment confirmed and completed
  • ERROR: Processing error

Example: "PENDING"

Note: Most PIX payments are confirmed within a few seconds

generateTimestringsempre presente

Payment creation date and time (ISO 8601 UTC).

Example: "2024-01-19T15:45:00.000Z"

Implementation Examples

Node.js / TypeScript

import axios from 'axios';

interface CashOutRequest {
  value: number;
  details: {
    key: string;
    keyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM';
    name: string;
    document: string;
  };
  externalId: string;
  description?: string;
}

interface CashOutResponse {
  transactionId: string;
  externalId: string;
  status: 'PENDING' | 'CONFIRMED' | 'ERROR';
  generateTime: string;
}

async function sendPixPayment(
  token: string,
  recipientKey: string,
  recipientKeyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM',
  recipientName: string,
  recipientDocument: string,
  amount: number,
  description?: string
): Promise<CashOutResponse> {
  const payload: CashOutRequest = {
    value: amount,
    details: {
      key: recipientKey,
      keyType: recipientKeyType,
      name: recipientName,
      document: recipientDocument
    },
    externalId: `PAY-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
    description: description || `Pagamento PIX de R$ ${amount.toFixed(2)}`
  };

  try {
    const response = await axios.post<CashOutResponse>(
      'https://api.avista.global/api/pix/cash-out',
      payload,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('PIX payment initiated successfully!');
    console.log(`Transaction ID: ${response.data.transactionId}`);
    console.log(`Status: ${response.data.status}`);
    console.log(`Amount: R$ ${amount.toFixed(2)}`);
    console.log(`Recipient: ${recipientName}`);

    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      const errorData = error.response?.data;
      console.error('Error making payment:', errorData);

      // Handle specific errors
      if (error.response?.status === 400) {
        if (errorData?.message?.includes('saldo insuficiente')) {
          throw new Error('Insufficient balance to make the payment');
        }
        throw new Error('Invalid data: ' + errorData?.message);
      }

      throw new Error(errorData?.message || 'Error making PIX payment');
    }
    throw error;
  }
}

// Usage - Payment by CPF
sendPixPayment(
  'your_token_here',
  '12345678901',
  'DOCUMENT',
  'Ana Costa',
  '12345678901',
  250.50,
  'Pagamento de fornecedor'
);

// Usage - Payment by Email
sendPixPayment(
  'your_token_here',
  'ana.costa@email.com',
  'EMAIL',
  'Ana Costa',
  '12345678901',
  100.00,
  'Reembolso'
);

// Usage - Payment by Phone
sendPixPayment(
  'your_token_here',
  '5511999999999',
  'PHONE',
  'Ana Costa',
  '12345678901',
  50.00
);

Python

import requests
from datetime import datetime
from typing import Dict, Optional
import uuid

def send_pix_payment(
    token: str,
    recipient_key: str,
    recipient_key_type: str,
    recipient_name: str,
    recipient_document: str,
    amount: float,
    description: Optional[str] = None
) -> Dict:
    """
    Send a PIX payment

    Args:
        token: Valid Bearer token
        recipient_key: Recipient's PIX key
        recipient_key_type: Key type (DOCUMENT, EMAIL, PHONE, RANDOM)
        recipient_name: Recipient's name
        recipient_document: Recipient's CPF or CNPJ
        amount: Amount in BRL
        description: Payment description (optional)

    Returns:
        Initiated payment data
    """
    url = 'https://api.avista.global/api/pix/cash-out'

    payload = {
        'value': round(amount, 2),
        'details': {
            'key': recipient_key,
            'keyType': recipient_key_type,
            'name': recipient_name,
            'document': recipient_document
        },
        'externalId': f'PAY-{int(datetime.now().timestamp())}-{uuid.uuid4().hex[:8]}',
        'description': description or f'Pagamento PIX de R$ {amount:.2f}'
    }

    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'application/json'
    }

    try:
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()

        data = response.json()

        print('PIX payment initiated successfully!')
        print(f"Transaction ID: {data['transactionId']}")
        print(f"Status: {data['status']}")
        print(f"Amount: R$ {amount:.2f}")
        print(f"Recipient: {recipient_name}")

        return data

    except requests.exceptions.HTTPError as e:
        error_data = e.response.json() if e.response else {}

        # Handle specific errors
        if e.response.status_code == 400:
            if 'saldo insuficiente' in error_data.get('message', '').lower():
                raise Exception('Insufficient balance to make the payment')
            raise Exception(f"Invalid data: {error_data.get('message')}")

        raise Exception(f"Error making payment: {error_data.get('message', str(e))}")

# Usage
token = 'your_token_here'

# Payment by CPF
payment = send_pix_payment(
    token=token,
    recipient_key='12345678901',
    recipient_key_type='DOCUMENT',
    recipient_name='Ana Costa',
    recipient_document='12345678901',
    amount=250.50,
    description='Pagamento de fornecedor'
)

PHP

<?php

function sendPixPayment(
    string $token,
    string $recipientKey,
    string $recipientKeyType,
    string $recipientName,
    string $recipientDocument,
    float $amount,
    ?string $description = null
): array {
    $url = 'https://api.avista.global/api/pix/cash-out';

    $payload = [
        'value' => round($amount, 2),
        'details' => [
            'key' => $recipientKey,
            'keyType' => $recipientKeyType,
            'name' => $recipientName,
            'document' => $recipientDocument
        ],
        'externalId' => 'PAY-' . time() . '-' . bin2hex(random_bytes(4)),
        'description' => $description ?? "Pagamento PIX de R$ " . number_format($amount, 2, ',', '.')
    ];

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $token,
        'Content-Type: application/json'
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 201) {
        $errorData = json_decode($response, true);
        $errorMessage = $errorData['message'] ?? "HTTP $httpCode";

        if ($httpCode === 400 && stripos($errorMessage, 'saldo insuficiente') !== false) {
            throw new Exception('Insufficient balance to make the payment');
        }

        throw new Exception("Error making payment: $errorMessage");
    }

    $data = json_decode($response, true);

    echo "PIX payment initiated successfully!" . PHP_EOL;
    echo "Transaction ID: {$data['transactionId']}" . PHP_EOL;
    echo "Status: {$data['status']}" . PHP_EOL;
    echo "Amount: R$ " . number_format($amount, 2, ',', '.') . PHP_EOL;
    echo "Recipient: $recipientName" . PHP_EOL;

    return $data;
}

// Usage
$token = 'your_token_here';
$payment = sendPixPayment(
    $token,
    '12345678901',
    'DOCUMENT',
    'Ana Costa',
    '12345678901',
    250.50,
    'Pagamento de fornecedor'
);

Use Cases

1. Payroll

class PayrollProcessor {
  constructor(private token: string) {}

  async processPayroll(employees: Employee[]) {
    const results = {
      successful: [],
      failed: []
    };

    for (const employee of employees) {
      try {
        // Check balance before each payment
        const balance = await getBalance(this.token);

        if (balance.netBalance < employee.salary) {
          throw new Error('Insufficient balance');
        }

        // Make payment
        const payment = await sendPixPayment(
          this.token,
          employee.pixKey,
          employee.pixKeyType,
          employee.fullName,
          employee.document,
          employee.salary,
          `Salário ${new Date().toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })}`
        );

        results.successful.push({
          employee: employee.fullName,
          amount: employee.salary,
          transactionId: payment.transactionId
        });

        // Wait 1 second between payments
        await this.sleep(1000);

      } catch (error) {
        results.failed.push({
          employee: employee.fullName,
          error: error.message
        });
      }
    }

    return results;
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
interface Employee {
  fullName: string;
  document: string;
  pixKey: string;
  pixKeyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM';
  salary: number;
}

const payroll = new PayrollProcessor('your_token_here');
const employees: Employee[] = [
  {
    fullName: 'Pedro Santos',
    document: '12345678901',
    pixKey: '12345678901',
    pixKeyType: 'DOCUMENT',
    salary: 3500.00
  },
  // ... more employees
];

const results = await payroll.processPayroll(employees);
console.log(`Successful payments: ${results.successful.length}`);
console.log(`Failed payments: ${results.failed.length}`);

2. Marketplace - Seller Payouts

class MarketplacePayouts:
    """Processes payouts for marketplace sellers"""

    def __init__(self, token: str):
        self.token = token

    def process_seller_payouts(self, sales_data: list) -> dict:
        """Processes payouts based on sales"""
        results = {'successful': [], 'failed': []}

        # Group sales by seller
        seller_totals = self.group_sales_by_seller(sales_data)

        for seller_id, total_amount in seller_totals.items():
            try:
                # Fetch seller data
                seller = self.get_seller_data(seller_id)

                # Calculate amount after commission
                commission = total_amount * 0.10  # 10% commission
                payout_amount = total_amount - commission

                # Make payment
                payment = send_pix_payment(
                    token=self.token,
                    recipient_key=seller['pix_key'],
                    recipient_key_type=seller['pix_key_type'],
                    recipient_name=seller['name'],
                    recipient_document=seller['document'],
                    amount=payout_amount,
                    description=f'Repasse de vendas - {len(sales_data)} transações'
                )

                results['successful'].append({
                    'seller': seller['name'],
                    'gross_amount': total_amount,
                    'commission': commission,
                    'net_amount': payout_amount,
                    'transaction_id': payment['transactionId']
                })

                # Record payout in the database
                self.record_payout(seller_id, payment)

            except Exception as e:
                results['failed'].append({
                    'seller_id': seller_id,
                    'error': str(e)
                })

        return results

    def group_sales_by_seller(self, sales_data: list) -> dict:
        """Groups sales by seller"""
        totals = {}
        for sale in sales_data:
            seller_id = sale['seller_id']
            totals[seller_id] = totals.get(seller_id, 0) + sale['amount']
        return totals

3. Refund System

class RefundSystem {
  constructor(token) {
    this.token = token;
  }

  async processRefund(orderId, refundReason) {
    // Fetch order data
    const order = await this.getOrderData(orderId);

    // Validate if refund is allowed
    if (!this.canRefund(order)) {
      throw new Error('Refund not allowed for this order');
    }

    // Make payment back to the customer
    const refund = await sendPixPayment(
      this.token,
      order.customer.pixKey,
      order.customer.pixKeyType,
      order.customer.name,
      order.customer.document,
      order.amount,
      `Reembolso - Pedido ${orderId} - ${refundReason}`
    );

    // Update order status
    await this.updateOrderStatus(orderId, 'REFUNDED', refund.transactionId);

    // Notify customer
    await this.notifyCustomer(order.customer.email, refund);

    return refund;
  }

  canRefund(order) {
    // Check if the order was paid and is still within the refund window
    const daysSincePurchase = (Date.now() - new Date(order.paidAt)) / (1000 * 60 * 60 * 24);
    return order.status === 'PAID' && daysSincePurchase <= 7;
  }
}

PIX Key Validation

Before sending a payment, validate the PIX key format:

function validatePixKey(key: string, keyType: string): boolean {
  switch (keyType) {
    case 'DOCUMENT':
      // CPF: 11 digits or CNPJ: 14 digits
      return /^\d{11}$|^\d{14}$/.test(key);

    case 'EMAIL':
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(key);

    case 'PHONE':
      // Format: +5511999999999 (country code + area code + number)
      return /^55\d{10,11}$/.test(key);

    case 'RANDOM':
      // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(key);

    default:
      return false;
  }
}

Balance Verification

Always check the balance before making payments to avoid 400 errors.

async function safePayment(
  token: string,
  amount: number,
  recipient: RecipientData
) {
  // Query balance
  const balance = await getBalance(token);

  // Check if there is sufficient balance
  if (balance.netBalance < amount) {
    throw new Error(
      `Insufficient balance. Available: R$ ${balance.netBalance.toFixed(2)} | ` +
      `Required: R$ ${amount.toFixed(2)}`
    );
  }

  // Proceed with payment
  return await sendPixPayment(token, ...recipient, amount);
}

Status Monitoring

To track payment confirmation:

async function monitorPaymentStatus(transactionId, timeout = 60000) {
  const startTime = Date.now();

  while (Date.now() - startTime < timeout) {
    const status = await checkTransactionStatus(transactionId);

    if (status === 'CONFIRMED') {
      console.log('Payment confirmed!');
      return true;
    }

    if (status === 'ERROR') {
      throw new Error('Payment failed');
    }

    // Wait 2 seconds before checking again
    await new Promise(resolve => setTimeout(resolve, 2000));
  }

  throw new Error('Timeout: Payment not confirmed within expected time');
}

Response Codes

CodeDescriptionMeaning
201Payment InitiatedPIX transfer initiated successfully
400Insufficient BalanceInsufficient balance to complete the transaction
400Invalid DataCheck required fields and formats
401Invalid TokenToken not provided, expired, or invalid

See the API Reference for full response field details.

Best Practices

Important Notes

  • Minimum amount: R$ 0.01

Next Steps

On this page