Best Currency API for Flutter and Dart (2026)
Flutter is the fastest-growing cross-platform framework for mobile development, and currency conversion is one of the most common features in finance, travel, and e-commerce apps. If you are building a Flutter app that needs exchange rate data — whether for a currency converter widget, a travel budget planner, or a multi-currency shopping cart — you need a currency API that works cleanly with Dart and the Flutter ecosystem.
The challenge: most currency APIs were built for web backends, not mobile apps. They lack Dart packages, provide no guidance on offline caching, and offer free tiers so restrictive that you cannot even test your app properly on a real device. Some return bloated JSON payloads that waste bandwidth on metered mobile connections.
This article compares the 5 most popular currency exchange rate APIs for Flutter and Dart in 2026 and shows you exactly how to integrate each one. Spoiler: AllRatesToday is the clear winner for Flutter developers, with real-time mid-market rates, a clean REST API that works perfectly with the Dart http package, and a free tier that does not require a credit card.
Side-by-Side Comparison
Here is an honest look at how the top 5 currency APIs stack up for Flutter and Dart developers:
| API | Dart Package | Free Tier | Real-Time | Historical Data | Offline Support |
|---|---|---|---|---|---|
| AllRatesToday | REST + http | Free tier, no CC | Yes (60s) | Yes | Easy (cache-friendly) |
| Open Exchange Rates | None | 1,000 req/mo | Hourly | Paid only | Manual |
| Fixer.io | None | 100 req/mo | No (daily) | Paid only | Manual |
| ExchangeRate-API | None | 1,500 req/mo | No (daily) | Paid only | Manual |
| Frankfurter | None | Unlimited | No (daily ECB) | Yes (ECB only) | Manual |
Key takeaway: AllRatesToday is the only API on this list that offers real-time rates updated every 60 seconds, historical data on the free tier, a cache-friendly JSON response ideal for offline storage, and no credit card requirement to get started.
1. AllRatesToday — Best Overall for Flutter
AllRatesToday was built API-first with clean REST endpoints that work perfectly with Dart's http package. The JSON responses are lightweight and predictable, which is exactly what you need in a mobile app where bandwidth and battery life matter.
Add the http package
Add the dependency to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
http: ^1.2.0 Create a currency service
import 'dart:convert';
import 'package:http/http.dart' as http;
class CurrencyService {
static const String _baseUrl = 'https://allratestoday.com/api/v1';
final String _apiKey;
CurrencyService(this._apiKey);
Future<double> getRate(String source, String target) async {
final response = await http.get(
Uri.parse('$_baseUrl/rates?source=$source&target=$target'),
headers: {'Authorization': 'Bearer $_apiKey'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['rate'].toDouble();
} else {
throw Exception('Failed to fetch rate: ${response.statusCode}');
}
}
Future<Map<String, double>> getAllRates(String source) async {
final response = await http.get(
Uri.parse('$_baseUrl/rates?source=$source'),
headers: {'Authorization': 'Bearer $_apiKey'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final rates = <String, double>{};
data['rates'].forEach((key, value) {
rates[key] = value.toDouble();
});
return rates;
} else {
throw Exception('Failed to fetch rates: ${response.statusCode}');
}
}
Future<double> convert(
String source,
String target,
double amount,
) async {
final rate = await getRate(source, target);
return amount * rate;
}
} Fetch a single exchange rate
final service = CurrencyService('YOUR_API_KEY');
// Get real-time USD to EUR rate
final rate = await service.getRate('USD', 'EUR');
print('1 USD = $rate EUR'); Convert an amount
final result = await service.convert('USD', 'EUR', 1000);
print('1,000 USD = ${result.toStringAsFixed(2)} EUR'); Fetch historical rates
Future<List<Map<String, dynamic>>> getHistoricalRates(
String source,
String target,
String period,
) async {
final response = await http.get(
Uri.parse(
'$_baseUrl/historical-rates?source=$source&target=$target&period=$period',
),
headers: {'Authorization': 'Bearer $_apiKey'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return List<Map<String, dynamic>>.from(data['rates']);
} else {
throw Exception('Failed to fetch historical rates');
}
}
// Usage
final history = await getHistoricalRates('USD', 'EUR', '30d');
for (final point in history) {
print('${point['time']}: ${point['rate']}');
} REST API docs: allratestoday.com/docs — View on GitHub
- Data source: Reuters/Refinitiv and interbank feeds
- Update frequency: Every 60 seconds (real-time)
- Currencies: 160+ including majors, minors, and exotics
- Rate type: Mid-market (no bank markup)
- Free tier: Available — no credit card required
- Authentication: Bearer token
2. Open Exchange Rates
Open Exchange Rates is one of the older currency APIs, established in 2012. It has no Dart package and no official mobile guidance. You need to call the REST API manually with the http package.
import 'dart:convert';
import 'package:http/http.dart' as http;
const appId = 'YOUR_APP_ID';
final response = await http.get(
Uri.parse('https://openexchangerates.org/api/latest.json?app_id=$appId&base=USD'),
);
final data = jsonDecode(response.body);
final eurRate = data['rates']['EUR'];
print('1 USD = $eurRate EUR'); - Free tier: 1,000 requests/month, USD base only
- Update frequency: Hourly on free, more frequent on paid
- Limitation: Free plan locked to USD as base currency
- Dart package: None
3. Fixer.io
Fixer was once popular but now sits behind a restrictive paywall after being acquired by APILayer. There is no Dart package. The free tier is limited to 100 requests per month and uses HTTP instead of HTTPS.
import 'dart:convert';
import 'package:http/http.dart' as http;
const apiKey = 'YOUR_API_KEY';
// Note: free tier is HTTP only, not HTTPS
final response = await http.get(
Uri.parse(
'http://data.fixer.io/api/latest?access_key=$apiKey&base=EUR&symbols=USD,GBP,JPY',
),
);
final data = jsonDecode(response.body);
print(data['rates']); - Free tier: 100 requests/month, EUR base only
- Update frequency: Daily
- Limitation: Free plan uses HTTP (not HTTPS) — a security risk for mobile apps
- Dart package: None
4. ExchangeRate-API
ExchangeRate-API offers a simple REST interface with no Dart package. The free tier gives 1,500 requests per month but only provides daily rates. No real-time data, no historical data on the free plan.
import 'dart:convert';
import 'package:http/http.dart' as http;
const apiKey = 'YOUR_API_KEY';
final response = await http.get(
Uri.parse('https://v6.exchangerate-api.com/v6/$apiKey/latest/USD'),
);
final data = jsonDecode(response.body);
final eurRate = data['conversion_rates']['EUR'];
print('1 USD = $eurRate EUR');
print('Last updated: ${data['time_last_update_utc']}'); - Free tier: 1,500 requests/month
- Update frequency: Daily
- Dart package: None
- Limitation: No real-time rates, historical data requires paid plan
5. Frankfurter
Frankfurter is a free, open-source API that wraps European Central Bank (ECB) data. No API key is required, which makes prototyping easy. However, it only covers about 30 currencies and updates once per business day.
import 'dart:convert';
import 'package:http/http.dart' as http;
// No API key needed
final response = await http.get(
Uri.parse('https://api.frankfurter.app/latest?from=USD&to=EUR,GBP,JPY'),
);
final data = jsonDecode(response.body);
print('Date: ${data['date']}');
data['rates'].forEach((currency, rate) {
print('USD/$currency: $rate');
}); - Free tier: Unlimited (no API key)
- Update frequency: Daily (ECB publishes at 16:00 CET)
- Currencies: ~30 (ECB reference rates only)
- Limitation: No exotic currencies, no real-time data, no weekend updates
- Dart package: None
Building a Flutter Currency Converter Widget
Here is a complete, production-ready currency converter widget using AllRatesToday. This is the kind of widget you can drop into any Flutter app — a travel app, a finance dashboard, or an e-commerce checkout screen.
import 'package:flutter/material.dart';
import 'currency_service.dart'; // The service class from above
class CurrencyConverterWidget extends StatefulWidget {
final String apiKey;
const CurrencyConverterWidget({super.key, required this.apiKey});
@override
State<CurrencyConverterWidget> createState() =>
_CurrencyConverterWidgetState();
}
class _CurrencyConverterWidgetState extends State<CurrencyConverterWidget> {
late final CurrencyService _service;
final _amountController = TextEditingController(text: '1000');
String _sourceCurrency = 'USD';
String _targetCurrency = 'EUR';
double? _result;
double? _rate;
bool _loading = false;
String? _error;
final List<String> _currencies = [
'USD', 'EUR', 'GBP', 'JPY', 'CAD',
'AUD', 'CHF', 'CNY', 'INR', 'SGD',
];
@override
void initState() {
super.initState();
_service = CurrencyService(widget.apiKey);
_convert();
}
Future<void> _convert() async {
setState(() {
_loading = true;
_error = null;
});
try {
final amount = double.tryParse(_amountController.text) ?? 0;
final rate = await _service.getRate(
_sourceCurrency,
_targetCurrency,
);
setState(() {
_rate = rate;
_result = amount * rate;
_loading = false;
});
} catch (e) {
setState(() {
_error = 'Failed to fetch rate. Check your connection.';
_loading = false;
});
}
}
void _swapCurrencies() {
setState(() {
final temp = _sourceCurrency;
_sourceCurrency = _targetCurrency;
_targetCurrency = temp;
});
_convert();
}
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Currency Converter',
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
TextField(
controller: _amountController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Amount',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.attach_money),
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: DropdownButtonFormField<String>(
value: _sourceCurrency,
decoration: const InputDecoration(
labelText: 'From',
border: OutlineInputBorder(),
),
items: _currencies
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
.toList(),
onChanged: (v) {
setState(() => _sourceCurrency = v!);
_convert();
},
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: IconButton(
onPressed: _swapCurrencies,
icon: const Icon(Icons.swap_horiz),
),
),
Expanded(
child: DropdownButtonFormField<String>(
value: _targetCurrency,
decoration: const InputDecoration(
labelText: 'To',
border: OutlineInputBorder(),
),
items: _currencies
.map((c) => DropdownMenuItem(value: c, child: Text(c)))
.toList(),
onChanged: (v) {
setState(() => _targetCurrency = v!);
_convert();
},
),
),
],
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loading ? null : _convert,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF2ED06E),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: _loading
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
)
: const Text('Convert', style: TextStyle(fontSize: 16)),
),
const SizedBox(height: 20),
if (_error != null)
Text(_error!, style: const TextStyle(color: Colors.red)),
if (_result != null && _rate != null) ...[
Text(
'${_amountController.text} $_sourceCurrency = '
'${_result!.toStringAsFixed(2)} $_targetCurrency',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: const Color(0xFF2ED06E),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(
'1 $_sourceCurrency = ${_rate!.toStringAsFixed(6)} $_targetCurrency',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
],
],
),
),
);
}
@override
void dispose() {
_amountController.dispose();
super.dispose();
}
} Use the widget anywhere in your app:
// In any screen or page
CurrencyConverterWidget(apiKey: 'YOUR_API_KEY') Offline Caching with SharedPreferences
Mobile apps need to work when the user has no connectivity. Here is how to add offline caching to your currency service so your Flutter app always shows the last known rates:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
class CachedCurrencyService {
static const String _baseUrl = 'https://allratestoday.com/api/v1';
final String _apiKey;
CachedCurrencyService(this._apiKey);
Future<Map<String, double>> getRates(String source) async {
try {
// Try fetching fresh rates
final response = await http.get(
Uri.parse('$_baseUrl/rates?source=$source'),
headers: {'Authorization': 'Bearer $_apiKey'},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
final rates = <String, double>{};
data['rates'].forEach((key, value) {
rates[key] = value.toDouble();
});
// Cache locally
final prefs = await SharedPreferences.getInstance();
await prefs.setString('cached_rates_$source', response.body);
await prefs.setString(
'cached_rates_time_$source',
DateTime.now().toIso8601String(),
);
return rates;
}
} catch (_) {
// Network error - fall through to cache
}
// Return cached rates if available
return _getCachedRates(source);
}
Future<Map<String, double>> _getCachedRates(String source) async {
final prefs = await SharedPreferences.getInstance();
final cached = prefs.getString('cached_rates_$source');
if (cached == null) {
throw Exception('No cached rates available');
}
final data = jsonDecode(cached);
final rates = <String, double>{};
data['rates'].forEach((key, value) {
rates[key] = value.toDouble();
});
return rates;
}
Future<String?> getLastUpdated(String source) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('cached_rates_time_$source');
}
} Tip: For more advanced offline storage, use the hive package instead of shared_preferences. Hive is a fast, lightweight key-value database built for Flutter that handles complex data structures better.
State Management with Provider
For production Flutter apps, you will want to manage exchange rate state properly. Here is a clean Provider-based approach:
import 'package:flutter/material.dart';
import 'cached_currency_service.dart';
class ExchangeRateProvider extends ChangeNotifier {
final CachedCurrencyService _service;
Map<String, double> _rates = {};
bool _loading = false;
String? _error;
String? _lastUpdated;
ExchangeRateProvider(String apiKey)
: _service = CachedCurrencyService(apiKey);
Map<String, double> get rates => _rates;
bool get loading => _loading;
String? get error => _error;
String? get lastUpdated => _lastUpdated;
Future<void> loadRates(String baseCurrency) async {
_loading = true;
_error = null;
notifyListeners();
try {
_rates = await _service.getRates(baseCurrency);
_lastUpdated = await _service.getLastUpdated(baseCurrency);
} catch (e) {
_error = e.toString();
}
_loading = false;
notifyListeners();
}
double convert(String target, double amount) {
final rate = _rates[target];
if (rate == null) return 0;
return amount * rate;
}
}
// In main.dart
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => ExchangeRateProvider('YOUR_API_KEY'),
child: const MyApp(),
),
);
} Why AllRatesToday Wins for Flutter
After comparing all five APIs, here is why AllRatesToday is the best choice for Flutter and Dart developers:
- Real-time rates: Updated every 60 seconds from Reuters/Refinitiv and interbank feeds. Every other free API on this list provides daily rates at best. For a currency converter app, stale data is a dealbreaker.
- Mid-market rates: The true interbank rate with no markup. This is the rate users expect to see — the same rate shown on Google Finance and XE. Bank-markup rates will make your app look inaccurate.
- 160+ currencies: Covers majors, minors, and exotic currency pairs. Frankfurter only offers ~30 ECB currencies, which means your app cannot support users in many countries.
- Cache-friendly JSON: Lightweight, predictable response format that is easy to serialize and store offline with
shared_preferencesorhive. Mobile apps need offline support, and AllRatesToday makes it simple. - Historical data on free tier: Build rate trend charts and analytics directly in your Flutter app. Most competitors lock historical data behind paid plans.
- No credit card required: Sign up and start building immediately. Fixer requires a credit card even for the free tier.
- HTTPS on free tier: Secure connections are non-negotiable for mobile apps. Fixer's free tier only supports HTTP, which is a security risk and will trigger warnings on both iOS and Android.
- Low latency: Fast response times that keep your Flutter UI responsive. No one wants a currency converter that takes 3 seconds to load.
Quick Reference
- Dart package:
http(from pub.dev) - Get rates:
GET /api/v1/rates?source=USD&target=EUR - Historical:
GET /api/historical-rates?source=USD&target=EUR&from=2026-01-01&to=2026-05-24 - Auth:
Authorization: Bearer YOUR_API_KEY - API docs: allratestoday.com/docs
- GitHub: allratestoday/exchange-rates-api
Build Your Flutter Currency App with Real-Time Rates
Get your free API key in 30 seconds. Add the http package to your Flutter project and fetch real-time mid-market rates for 160+ currencies. Compare all options on our Exchange Rate API page.