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/jsonRequest 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.safirapay.com/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
valuenumberobrigatorioPayment amount in BRL (Brazilian Reais). Must have at most 2 decimal places.
Minimum: 0.01
Example: 250.50
detailsobjectobrigatorioDestination PIX key information.
details.keystringobrigatorioDestination 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.keyTypestringobrigatorioPIX key type.
Accepted values:
DOCUMENT- CPF or CNPJEMAIL- Email addressPHONE- Phone numberRANDOM- Random key (UUID)
Example: "DOCUMENT"
details.namestringobrigatorioFull name of the destination PIX key holder.
Validation: The name must match the one registered with the PIX key
Example: "Ana Costa"
details.documentstringobrigatorioCPF 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"
externalIdstringobrigatorioUnique external transaction identifier.
Maximum: 255 characters
Recommendation: Use a format that ensures uniqueness
Example: "PAYMENT-987654-20240119-154500"
descriptionstringPayment description that will appear on the recipient's statement.
Maximum: 140 characters
Default: Empty
Example: "Pagamento de fornecedor - Nota Fiscal 12345"
Response Structure
transactionIdstringsempre presenteInternal transaction ID generated by Avista.
Example: "9876"
externalIdstringsempre presenteExternal ID provided in the request (same value as the input).
Example: "PAYMENT-987654-20240119"
statusstringsempre presenteCurrent transaction status.
Possible values:
PENDING: Payment being processedCONFIRMED: Payment confirmed and completedERROR: Processing error
Example: "PENDING"
Note: Most PIX payments are confirmed within a few seconds
generateTimestringsempre presentePayment 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.safirapay.com/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.safirapay.com/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.safirapay.com/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 totals3. 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
| Code | Description | Meaning |
|---|---|---|
201 | Payment Initiated | PIX transfer initiated successfully |
400 | Insufficient Balance | Insufficient balance to complete the transaction |
400 | Invalid Data | Check required fields and formats |
401 | Invalid Token | Token not provided, expired, or invalid |
See the API Reference for full response field details.
Best Practices
Important Notes
- Minimum amount: R$ 0.01