Transfers API
The Transfers API allows you to transfer funds between two customers on the Sznd platform. Transfers can be made in the same currency or across different currencies, with automatic exchange rate conversion when needed.
Overview
Transfers enable peer-to-peer money transfers between users on the platform. You can transfer funds:
- Same Currency: Direct transfer between wallets of the same currency
- Cross-Currency: Transfer with automatic currency conversion
Two Methods for Transfers
Method 1: Transfer via Quotes (Recommended)
Use the Quotes API with quote_type: TRANSFER to create transfers. This method supports both same-currency and cross-currency transfers, includes fee calculations, and provides better control over the transfer process.
Method 2: Direct Transfer
Use the direct transfer endpoint for same-currency transfers between wallets. This is simpler but only works for transfers within the same currency.
Method 1: Transfer via Quotes
Create a transfer quote and accept it to complete the transfer. This method supports cross-currency transfers and provides detailed fee information.
Step 1: Look Up Recipient by User Tag
Before creating a transfer, you may want to verify the recipient's user tag.
Endpoint: GET /api/v1/users/by-tag
Authentication: Required (JWT Bearer token)
Query Parameters:
tag(string, required) - User tag to look up (4-20 alphanumeric characters)
Success Response (200 OK):
{
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"user_tag": "janedoe123",
"type_id": 1,
"type_name": "Individual",
"role_id": 1,
"role_name": "CUSTOMER",
"status": "ACTIVE"
}Step 2: Create Transfer Quote
Endpoint: POST /api/v1/quotes/v2
Authentication: Required (JWT Bearer token)
Request Body:
{
"source_currency": "USD",
"target_currency": "NGN",
"source_amount": "100.00",
"quote_type": "TRANSFER",
"user_tag": "janedoe123",
"fee_config_id": "2fbdb973-39fd-4217-aa9b-e7b8ccc9e457",
"payment_method": "FIAT",
"tz": "UTC",
"narration": "Payment for services"
}Required Parameters:
source_currency(string) - Currency to transfer fromtarget_currency(string) - Currency to transfer to (can be same as source)source_amount(string) - Amount to transfer in source currencyquote_type(string) - Must beTRANSFERuser_tag(string) - User tag of the recipientfee_config_id(string, UUID) - Fee configuration IDpayment_method(string) -FIATorCRYPTOtz(string) - Timezone (IANA format)
Optional Parameters:
narration(string) - Description for the transfer
Success Response (201 Created):
{
"quote_id": "123e4567-e89b-12d3-a456-426614174000",
"source": {
"currency": "USD",
"amount": "100.00"
},
"target": {
"currency": "NGN",
"amount": "150000.00"
},
"exchange_rate": "1500.00",
"fees": {
"processing_fee": "2.00",
"deposit_fee": "0.00",
"payout_fee": "0.00",
"total_fees": "2.00",
"fee_currency": "USD"
},
"total_payable": "102.00",
"valid_until": "2025-03-09T01:40:53Z",
"quote_status": "CREATED",
"type": "TRANSFER"
}Step 3: Accept Transfer Quote
Endpoint: POST /api/v1/quotes/{id}/accept
Authentication: Required (JWT Bearer token)
Path Parameters:
id(UUID) - Quote ID from Step 2
Success Response (200 OK):
{
"quote_id": "123e4567-e89b-12d3-a456-426614174000",
"transaction_id": "223e4567-e89b-12d3-a456-426614174001",
"transaction_reference": "TRX-20240501-XYZ123",
"total_payable": "102.00",
"timeline": {
"created_at": "2024-03-09T01:10:53Z",
"valid_until": "2024-03-09T01:40:53Z"
}
}Example: Complete Transfer Flow
Step 1: Verify recipient exists
curl -X GET "/api/v1/users/by-tag?tag=janedoe123" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"Step 2: Create transfer quote
curl -X POST /api/v1/quotes/v2 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"source_currency": "USD",
"target_currency": "NGN",
"source_amount": "100.00",
"quote_type": "TRANSFER",
"user_tag": "janedoe123",
"fee_config_id": "2fbdb973-39fd-4217-aa9b-e7b8ccc9e457",
"payment_method": "FIAT",
"tz": "UTC",
"narration": "Payment for services"
}'Step 3: Accept the quote
curl -X POST /api/v1/quotes/{quote_id}/accept \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"Example: HTTP Requests
Step 1: Look up user by tag
GET /api/v1/users/by-tag?tag=janedoe123 HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/jsonStep 2: Create transfer quote
POST /api/v1/quotes/v2 HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"source_currency": "USD",
"target_currency": "NGN",
"source_amount": "100.00",
"quote_type": "TRANSFER",
"user_tag": "janedoe123",
"fee_config_id": "2fbdb973-39fd-4217-aa9b-e7b8ccc9e457",
"payment_method": "FIAT",
"tz": "UTC",
"narration": "Payment for services"
}Step 3: Accept quote
POST /api/v1/quotes/{quote_id}/accept HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/jsonMethod 2: Direct Transfer (Same Currency Only)
For same-currency transfers, you can use the direct transfer endpoint. This is simpler but only works when both wallets use the same currency.
Endpoint
POST /api/v1/transactions/transfer
Authentication
This endpoint requires JWT authentication. Include your access token in the Authorization header:
Authorization: Bearer <your-access-token>Request Body
{
"source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
"amount": "100.00",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}Parameters:
source_wallet_id(UUID, required) - Your wallet ID (must belong to authenticated user)target_wallet_id(UUID, required) - Recipient's wallet IDamount(string, required) - Amount to transfer (decimal string)idempotency_key(UUID, required) - Unique key to prevent duplicate transfers
Important Notes
- Same Currency Required: Both wallets must use the same currency
- Wallet Ownership: Source wallet must belong to the authenticated user
- Idempotency: Using the same
idempotency_keywill return the existing transaction instead of creating a duplicate
Success Response (201 Created)
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"user_id": "111e4567-e89b-12d3-a456-426614174000",
"transaction_type": "TRANSFER",
"status": "PENDING",
"source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
"source_amount": "100.00",
"source_currency": "USD",
"target_amount": "100.00",
"target_currency": "USD",
"created_at": "2024-05-01T14:00:00Z",
"updated_at": "2024-05-01T14:00:00Z"
}Error Responses
| Status Code | Description |
|---|---|
400 | Invalid request, insufficient funds, or currency mismatch |
401 | Unauthorized - Invalid or missing JWT token |
403 | Forbidden - Source wallet does not belong to authenticated user |
404 | Wallet not found |
500 | Internal server error |
Example: Direct Transfer
curl -X POST /api/v1/transactions/transfer \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
"amount": "100.00",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}'Example: HTTP Request
POST /api/v1/transactions/transfer HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
{
"source_wallet_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"target_wallet_id": "b2c3d4e5-f6a7-8901-2345-678901bcdefg",
"amount": "100.00",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}User Tags
User tags are unique identifiers that allow users to receive transfers without exposing their wallet IDs or email addresses. Each user can set a custom user tag.
Set User Tag
Endpoint: PUT /api/v1/users/tag
Authentication: Required (JWT Bearer token)
Request Body:
{
"user_tag": "myusertag123"
}Requirements:
- 4-20 alphanumeric characters
- Must be unique across the platform
- Pattern:
^[a-zA-Z0-9]+$
Success Response (200 OK):
{
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"user_tag": "myusertag123",
"message": "User tag updated successfully"
}Look Up User by Tag
Endpoint: GET /api/v1/users/by-tag?tag={user_tag}
Authentication: Required (JWT Bearer token)
Query Parameters:
tag(string, required) - User tag to look up
Success Response (200 OK):
{
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane.doe@example.com",
"user_tag": "janedoe123",
"type_name": "Individual",
"status": "ACTIVE"
}Transfer Requirements
Prerequisites
-
Source User must have:
- Active account
- Wallet in source currency
- Sufficient available balance
-
Recipient User must have:
- Active account
- Wallet in target currency (will be created automatically if needed)
- Valid user tag (for quote-based transfers)
-
Both users must be:
- KYC verified (if required for the currency/amount)
- In good standing
Transfer Limits
Transfer limits may apply based on:
- User verification status
- Currency-specific limits
- Daily/monthly transaction limits
Check the Exchange Rates API to see applicable limits for a transfer.
Currency Conversion
When transferring between different currencies:
- Exchange rate is locked when the quote is created
- Fees are calculated and included in the quote
- Conversion happens automatically upon quote acceptance
Transfer Status
Transfers go through the following statuses:
- PENDING - Transfer is pending processing
- PROCESSING - Transfer is being processed
- COMPLETED - Transfer completed successfully
- FAILED - Transfer failed
- CANCELLED - Transfer was cancelled
Check Transfer Status
curl -X GET "/api/v1/transactions/v2/{transaction_id}" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"Example: HTTP Request
GET /api/v1/transactions/v2/{transaction_id} HTTP/1.1
Host: api.sznd.app
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/jsonError Handling
Common Errors
| Status Code | Error | Description |
|---|---|---|
400 | Invalid request | Missing required fields, invalid amounts, or validation errors |
401 | Unauthorized | Invalid or missing JWT token |
403 | Forbidden | Source wallet doesn't belong to user, or transfer to self |
404 | Not found | Recipient user tag not found, or wallet not found |
422 | Limit violation | Transfer amount exceeds limits |
Error Response Format
{
"error": "Target user not found",
"message": "No user found with the provided user tag"
}Best Practices
- Verify Recipient First - Always look up the user tag before creating a transfer to confirm the recipient
- Use Idempotency Keys - Always include unique idempotency keys to prevent duplicate transfers
- Check Balance - Verify sufficient balance before creating transfers
- Handle Errors - Implement proper error handling for failed transfers
- Monitor Status - Track transfer status and notify users of completion
- Use Quotes for Cross-Currency - Always use the quote method for transfers between different currencies
- Store Transaction References - Save transaction references for reconciliation and support
Use Cases
- P2P Payments - Enable users to send money to each other
- Marketplace Payments - Process payments between buyers and sellers
- Gig Economy - Pay freelancers and contractors
- Remittances - Send money to family and friends
- Business Payments - Transfer funds between business accounts
Webhook Integration
Set up webhooks to receive real-time notifications when transfers complete or fail. This allows you to:
- Update your system automatically when transfers complete
- Handle failed transfers and notify users
- Reconcile transfers in your database
See the Webhooks documentation for more information.
Next Steps
- Quotes API - Learn more about creating and accepting quotes
- Transactions API - Track transfer status and history
- Wallets API - Manage user wallets and check balances
- Webhooks - Set up webhooks for transfer notifications