Execute Currency Conversion
Perform actual currency exchange between different currencies in your Paysera account.
This endpoint performs a real currency conversion and cannot be undone. Always calculate first to preview the result.
Endpoint
POST /rest/v1/currency-conversion
Content-Type: application/json
Required Scope
convert_currency - Extended scope requiring SMS verification
Request Body
Required Fields
You must provide either source amount or target amount (but not both):
View Request Options
Option A: Specify Source Amount
{
"from_currency": "EUR",
"to_currency": "USD",
"from_amount_decimal": "100.00",
"account_number": "EVP1234567890"
}
Option B: Specify Target Amount
{
"from_currency": "EUR",
"to_currency": "USD",
"to_amount_decimal": "200.00",
"account_number": "EVP1234567890"
}
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
from_currency | string | ✅ Yes | Source currency code |
to_currency | string | ✅ Yes | Target currency code |
from_amount | integer | ⚠️ Conditional | Source amount in cents |
from_amount_decimal | string | ⚠️ Conditional | Source amount (e.g., "100.00") |
to_amount | integer | ⚠️ Conditional | Target amount in cents |
to_amount_decimal | string | ⚠️ Conditional | Target amount (e.g., "200.00") |
account_number | string | ✅ Yes | Account to perform conversion in |
max_from_amount | integer | ⬜ Optional | Maximum allowed source amount (cents) |
max_from_amount_decimal | string | ⬜ Optional | Maximum allowed source amount |
min_to_amount | integer | ⬜ Optional | Minimum expected target amount (cents) |
min_to_amount_decimal | string | ⬜ Optional | Minimum expected target amount |
Use max_from_amount or min_to_amount to protect against rate changes between calculation and execution.
Response
Success Response
{
"id": 11221,
"date": 1391686980,
"from_currency": "EUR",
"to_currency": "USD",
"from_amount": 10000,
"from_amount_decimal": "100.00",
"to_amount": 10850,
"to_amount_decimal": "108.50",
"account_number": "EVP1234567890"
}
View Response Fields
| Field | Type | Description |
|---|---|---|
id | integer | Unique conversion ID |
date | integer | Unix timestamp of conversion |
from_currency | string | Source currency |
to_currency | string | Target currency |
from_amount | integer | Converted amount in cents |
from_amount_decimal | string | Converted amount |
to_amount | integer | Received amount in cents |
to_amount_decimal | string | Received amount |
account_number | string | Account number |
Error Codes
| Error Code | Status | Description |
|---|---|---|
not_found | 404 | Account not found |
no_rights | 403 | No conversion permissions for this account |
not_enough_funds | 400 | Insufficient balance |
limits_exceeded | 400 | Daily/monthly conversion limit exceeded |
expectation_failed | 412 | Rate changed, safety limits not met |
Examples
Example 1: Basic Conversion
Convert 100 EUR to USD
Request
POST /rest/v1/currency-conversion HTTP/1.1
Host: wallet.paysera.com
Content-Type: application/json
Authorization: MAC id="wkVd93h2uS", ts="1343811600", nonce="nQnNaSNyubfPErjRO55yaaEYo9YZfKHN", mac="+ZVwDWDVqqhjTeA5t6cW4ZtgcXI9ikDH3oKzzWEHDfI=", ext="body_hash=%2FnvaEOVnCNABstYnlN4pVfw3f6FElVlYw5CNvgYzBls%3D"
{
"from_currency": "EUR",
"to_currency": "USD",
"from_amount_decimal": "100.00",
"account_number": "EVP0010001000101"
}
Response
{
"id": 11221,
"date": 1391686980,
"from_currency": "EUR",
"to_currency": "USD",
"from_amount": 10000,
"from_amount_decimal": "100.00",
"to_amount": 10850,
"to_amount_decimal": "108.50",
"account_number": "EVP0010001000101"
}
Example 2: With Safety Limit
Convert to get exactly 200 USD, but don't pay more than 150 EUR
Request
{
"from_currency": "EUR",
"to_currency": "USD",
"to_amount_decimal": "200.00",
"account_number": "EVP0010001000101",
"max_from_amount_decimal": "150.00"
}
Response
{
"id": 11222,
"date": 1391687100,
"from_currency": "EUR",
"to_currency": "USD",
"from_amount": 14433,
"from_amount_decimal": "144.33",
"to_amount": 20000,
"to_amount_decimal": "200.00",
"account_number": "EVP0010001000101"
}
Example 3: Safety Limit Exceeded
Rate changed too much
Request
{
"from_currency": "EUR",
"to_currency": "USD",
"to_amount_decimal": "200.00",
"max_from_amount_decimal": "140.00"
}
Error Response
{
"error": "expectation_failed",
"error_description": "Conversion price (144.33 EUR) exceeds maximum allowed (140.00 EUR)"
}
Code Examples
JavaScript
const crypto = require('crypto');
const CLIENT_ID = 'wkVd93h2uS';
const MAC_KEY = 'your_mac_key';
function generateMacAuth(method, uri, host, body = '') {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = crypto.randomBytes(16).toString('hex');
let ext = '';
if (body) {
const bodyHash = crypto.createHash('sha256').update(body).digest('base64');
ext = `body_hash=${encodeURIComponent(bodyHash)}`;
}
const normalizedString = [timestamp, nonce, method, uri, host, '443', ext].join('\n') + '\n';
const mac = crypto.createHmac('sha256', MAC_KEY).update(normalizedString).digest('base64');
let authHeader = `MAC id="${CLIENT_ID}", ts="${timestamp}", nonce="${nonce}", mac="${mac}"`;
if (ext) {
authHeader += `, ext="${ext}"`;
}
return authHeader;
}
async function convertCurrency(fromCurrency, toCurrency, amount, accountNumber) {
try {
const body = JSON.stringify({
from_currency: fromCurrency,
to_currency: toCurrency,
from_amount_decimal: amount,
account_number: accountNumber
});
const uri = '/rest/v1/currency-conversion';
const response = await fetch(
`https://wallet.paysera.com${uri}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': generateMacAuth('POST', uri, 'wallet.paysera.com', body)
},
body: body
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error_description || error.error);
}
const conversion = await response.json();
console.log('✅ Conversion successful!');
console.log(`ID: ${conversion.id}`);
console.log(`Converted: ${conversion.from_amount_decimal} ${conversion.from_currency}`);
console.log(`Received: ${conversion.to_amount_decimal} ${conversion.to_currency}`);
return conversion;
} catch (error) {
console.error('❌ Conversion failed:', error.message);
throw error;
}
}
// Usage
await convertCurrency('EUR', 'USD', '100.00', 'EVP1234567890');
With Safety Limits
async function safeConvert(params) {
// First calculate
const calculation = await api.calculateConversion({
from_currency: params.from_currency,
to_currency: params.to_currency,
from_amount_decimal: params.from_amount_decimal
});
console.log(`Preview: ${params.from_amount_decimal} ${params.from_currency} → ${calculation.to_amount_decimal} ${params.to_currency}`);
// Get user confirmation
const confirmed = await confirm(
`Convert ${params.from_amount_decimal} ${params.from_currency} to ${calculation.to_amount_decimal} ${params.to_currency}?`
);
if (!confirmed) {
console.log('Conversion cancelled');
return null;
}
// Execute with small buffer for rate changes (0.50 EUR)
const maxAmount = parseFloat(calculation.from_amount_decimal) + 0.50;
try {
return await api.convertCurrency({
...params,
max_from_amount_decimal: maxAmount.toFixed(2)
});
} catch (error) {
if (error.code === 'expectation_failed') {
console.log('Rate changed significantly, please try again');
}
throw error;
}
}
Python
import requests
import json
import hmac
import hashlib
import time
import secrets
from urllib.parse import quote
CLIENT_ID = 'wkVd93h2uS'
MAC_KEY = 'your_mac_key'
def generate_mac_auth(method, uri, host, body=''):
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
ext = ''
if body:
body_hash = hashlib.sha256(body.encode()).digest()
body_hash_b64 = hashlib.b64encode(body_hash).decode()
ext = f'body_hash={quote(body_hash_b64)}'
normalized = f"{timestamp}\n{nonce}\n{method}\n{uri}\n{host}\n443\n{ext}\n"
mac = hmac.new(MAC_KEY.encode(), normalized.encode(), hashlib.sha256).digest()
mac_base64 = hashlib.b64encode(mac).decode()
auth_header = f'MAC id="{CLIENT_ID}", ts="{timestamp}", nonce="{nonce}", mac="{mac_base64}"'
if ext:
auth_header += f', ext="{ext}"'
return auth_header
def convert_currency(from_currency, to_currency, amount, account_number):
url = 'https://wallet.paysera.com/rest/v1/currency-conversion'
uri = '/rest/v1/currency-conversion'
data = {
'from_currency': from_currency,
'to_currency': to_currency,
'from_amount_decimal': amount,
'account_number': account_number
}
body = json.dumps(data)
headers = {
'Content-Type': 'application/json',
'Authorization': generate_mac_auth('POST', uri, 'wallet.paysera.com', body)
}
response = requests.post(url, data=body, headers=headers)
if response.status_code != 200:
error = response.json()
raise Exception(f"Conversion failed: {error.get('error_description', error.get('error'))}")
conversion = response.json()
print(f"✅ Conversion successful!")
print(f"ID: {conversion['id']}")
print(f"Converted: {conversion['from_amount_decimal']} {conversion['from_currency']}")
print(f"Received: {conversion['to_amount_decimal']} {conversion['to_currency']}")
return conversion
# Usage
result = convert_currency('EUR', 'USD', '100.00', 'EVP1234567890')
PHP
<?php
define('CLIENT_ID', 'wkVd93h2uS');
define('MAC_KEY', 'your_mac_key');
function generateMacAuth($method, $uri, $host, $body = '') {
$timestamp = time();
$nonce = bin2hex(random_bytes(16));
$ext = '';
if ($body) {
$bodyHash = base64_encode(hash('sha256', $body, true));
$ext = 'body_hash=' . urlencode($bodyHash);
}
$normalized = implode("\n", [$timestamp, $nonce, $method, $uri, $host, '443', $ext]) . "\n";
$mac = base64_encode(hash_hmac('sha256', $normalized, MAC_KEY, true));
$authHeader = sprintf('MAC id="%s", ts="%s", nonce="%s", mac="%s"', CLIENT_ID, $timestamp, $nonce, $mac);
if ($ext) {
$authHeader .= sprintf(', ext="%s"', $ext);
}
return $authHeader;
}
function convertCurrency($fromCurrency, $toCurrency, $amount, $accountNumber) {
$url = 'https://wallet.paysera.com/rest/v1/currency-conversion';
$uri = '/rest/v1/currency-conversion';
$data = [
'from_currency' => $fromCurrency,
'to_currency' => $toCurrency,
'from_amount_decimal' => $amount,
'account_number' => $accountNumber
];
$body = json_encode($data);
$authorization = generateMacAuth('POST', $uri, 'wallet.paysera.com', $body);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
"Authorization: $authorization"
]);
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode !== 200) {
$error = json_decode($response, true);
throw new Exception($error['error_description'] ?? $error['error']);
}
$conversion = json_decode($response, true);
echo "✅ Conversion successful!\n";
echo "ID: {$conversion['id']}\n";
echo "Converted: {$conversion['from_amount_decimal']} {$conversion['from_currency']}\n";
echo "Received: {$conversion['to_amount_decimal']} {$conversion['to_currency']}\n";
return $conversion;
}
// Usage
$result = convertCurrency('EUR', 'USD', '100.00', 'EVP1234567890');
?>
Advanced Topics
Complete Conversion Flow
Full Implementation
class CurrencyConverter {
constructor(api) {
this.api = api;
}
async convert(params) {
try {
// Step 1: Calculate
console.log('Step 1: Calculating conversion...');
const calculation = await this.api.calculateConversion({
from_currency: params.from_currency,
to_currency: params.to_currency,
from_amount_decimal: params.from_amount_decimal
});
console.log(`You will receive: ${calculation.to_amount_decimal} ${params.to_currency}`);
// Step 2: Get user confirmation
console.log('Step 2: Awaiting user confirmation...');
const confirmed = await this.getUserConfirmation(calculation);
if (!confirmed) {
console.log('Conversion cancelled by user');
return null;
}
// Step 3: Execute with safety limit
console.log('Step 3: Executing conversion...');
const maxAmount = parseFloat(calculation.from_amount_decimal) * 1.01; // 1% buffer
const conversion = await this.api.convertCurrency({
...params,
max_from_amount_decimal: maxAmount.toFixed(2)
});
// Step 4: Success
console.log('✅ Conversion completed successfully!');
this.showSuccess(conversion);
return conversion;
} catch (error) {
this.handleError(error);
throw error;
}
}
async getUserConfirmation(calculation) {
return confirm(
`Convert ${calculation.from_amount_decimal} ${calculation.from_currency} ` +
`to ${calculation.to_amount_decimal} ${calculation.to_currency}?`
);
}
showSuccess(conversion) {
console.log('Conversion Details:');
console.log(`ID: ${conversion.id}`);
console.log(`Date: ${new Date(conversion.date * 1000).toLocaleString()}`);
console.log(`From: ${conversion.from_amount_decimal} ${conversion.from_currency}`);
console.log(`To: ${conversion.to_amount_decimal} ${conversion.to_currency}`);
}
handleError(error) {
switch (error.code) {
case 'not_enough_funds':
console.error('❌ Insufficient balance');
break;
case 'expectation_failed':
console.error('❌ Exchange rate changed significantly');
break;
case 'limits_exceeded':
console.error('❌ Conversion limit exceeded');
break;
case 'no_rights':
console.error('❌ No permission to convert currency');
break;
default:
console.error(`❌ Conversion failed: ${error.message}`);
}
}
}
// Usage
const converter = new CurrencyConverter(api);
await converter.convert({
from_currency: 'EUR',
to_currency: 'USD',
from_amount_decimal: '100.00',
account_number: 'EVP1234567890'
});
Best Practices
1. Always Calculate First
// ✅ Good
const calc = await api.calculateConversion({...});
const confirmed = await showRateToUser(calc);
if (confirmed) {
await api.convertCurrency({...});
}
// ❌ Bad
await api.convertCurrency({...}); // User doesn't see rate first
2. Use Safety Limits
// ✅ Good - protect against rate changes
await api.convertCurrency({
from_currency: 'EUR',
to_currency: 'USD',
to_amount_decimal: '100.00',
max_from_amount_decimal: '92.00', // Won't pay more than this
account_number: accountNumber
});
3. Handle Retries on Rate Changes
async function convertWithRetry(params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const calc = await api.calculateConversion(params);
const conversion = await api.convertCurrency({
...params,
max_from_amount: calc.from_amount + 50 // 0.50 buffer
});
return conversion;
} catch (error) {
if (error.code === 'expectation_failed' && i < maxRetries - 1) {
console.log(`Rate changed, retrying (${i + 1}/${maxRetries})...`);
await sleep(1000);
continue;
}
throw error;
}
}
}
4. Store Conversion History
async function convertAndStore(params) {
const conversion = await api.convertCurrency(params);
// Store in database/localStorage
await db.conversions.add({
id: conversion.id,
date: conversion.date,
from: `${conversion.from_amount_decimal} ${conversion.from_currency}`,
to: `${conversion.to_amount_decimal} ${conversion.to_currency}`,
rate: conversion.to_amount / conversion.from_amount
});
return conversion;
}
Common Issues
Issue: "expectation_failed" Error
Cause: Rate changed between calculation and execution
Solution: Use appropriate safety limits:
const calc = await api.calculateConversion({...});
await api.convertCurrency({
...,
max_from_amount_decimal: (parseFloat(calc.from_amount_decimal) * 1.02).toFixed(2)
});
Issue: "not_enough_funds"
Cause: Insufficient balance in source currency
Solution: Check balance before converting:
const balance = await api.getBalance(accountNumber);
const eurBalance = balance.balances.find(b => b.currency === 'EUR');
if (parseFloat(eurBalance.available) < parseFloat(amount)) {
throw new Error(`Insufficient EUR balance. Available: ${eurBalance.available}`);
}
Issue: "limits_exceeded"
Cause: Daily or monthly limit reached
Solution: Check limits and wait:
try {
await api.convertCurrency({...});
} catch (error) {
if (error.code === 'limits_exceeded') {
console.log('Daily limit reached. Try again tomorrow.');
}
}
Next Steps
- Calculate Conversion - Preview rates first
- Overview - Learn about currency conversion
- Authentication - Get required scopes
- Payments - Make payments