Skip to main content

Execute Currency Conversion

Perform actual currency exchange between different currencies in your Paysera account.

Real Transaction

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

Learn how to get this scope →


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

ParameterTypeRequiredDescription
from_currencystring✅ YesSource currency code
to_currencystring✅ YesTarget currency code
from_amountinteger⚠️ ConditionalSource amount in cents
from_amount_decimalstring⚠️ ConditionalSource amount (e.g., "100.00")
to_amountinteger⚠️ ConditionalTarget amount in cents
to_amount_decimalstring⚠️ ConditionalTarget amount (e.g., "200.00")
account_numberstring✅ YesAccount to perform conversion in
max_from_amountinteger⬜ OptionalMaximum allowed source amount (cents)
max_from_amount_decimalstring⬜ OptionalMaximum allowed source amount
min_to_amountinteger⬜ OptionalMinimum expected target amount (cents)
min_to_amount_decimalstring⬜ OptionalMinimum expected target amount
Safety Limits

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
FieldTypeDescription
idintegerUnique conversion ID
dateintegerUnix timestamp of conversion
from_currencystringSource currency
to_currencystringTarget currency
from_amountintegerConverted amount in cents
from_amount_decimalstringConverted amount
to_amountintegerReceived amount in cents
to_amount_decimalstringReceived amount
account_numberstringAccount number

Error Codes

Error CodeStatusDescription
not_found404Account not found
no_rights403No conversion permissions for this account
not_enough_funds400Insufficient balance
limits_exceeded400Daily/monthly conversion limit exceeded
expectation_failed412Rate 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