Home Documentation Playground Pricing API Status Blog About FAQ Support

Best Currency API for Flutter and Dart (2026)

Reviewed by Madhushan, Fintech Developer — May 2026
Mobile app development on screen

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/docsView on GitHub

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');

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']);

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']}');

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');
});

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:

Quick Reference

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.

Get Your Free API Key