REST Countries

Docs Currencies API

The Currencies API is a read-only currency conversion and exchange rate API. Every endpoint returns JSON, authenticates via a single bearer token, and responds instantly. No SDK is required.

Overview

The Currencies API turns one currency amount into another and exposes the exchange-rate tables behind it. It's a small, process-style surface: three GET endpoints (convert, rates, and symbols) that respond instantly; a request never waits on an upstream provider. If you're after a currency converter API or a spot exchange-rate lookup, this is the whole surface. Both fiat and crypto codes are supported (e.g. USD, EUR, BTC, ETH).

  • Read-only. All endpoints are GET. There is no POST/PUT/DELETE surface.
  • JSON-only responses. Content-Type: application/json on every response. Successful payloads sit under a top-level data key with the result rows in data.objects; failures come back under errors as an array of objects carrying a message.
  • Explicit freshness. Rates refresh on your plan's cadence (see Freshness & as_of), and every object carries an as_of stamp of when its rate was sourced.
  • Case-insensitive codes. usd and USD are equivalent on input; codes are always echoed uppercase.

Try it now

Two ways to try the Currencies API right now. Neither requires an account.

1. Fire a live request from this page. Click the button below. The response slides open in the Explorer panel.

// Demo key: no account required.
const response = await fetch(
  'https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100',
  { headers: { 'Authorization': 'Bearer rc_live_demo' } }
);
const data = await response.json();

Run this request →

2. Run it from your terminal. Click the copy button below, then paste anywhere curl lives.

curl "https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100&pretty=1" \
  -H "Authorization: Bearer rc_live_demo"

The demo key returns a real, correctly-shaped sample plus a data._demo notice block; it never touches your account or quota.

Quick start

Sign up to get an API key, then paste this into your terminal (swap out the API key placeholder):

curl "https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100" \
  -H "Authorization: Bearer YOUR_API_KEY"

You'll get a single-object data.objects array back with the converted result. From there, browse the endpoint sections for the full surface, or the Response objects reference to see every field.

Base URL & versioning

All endpoints live under:

https://api.restcountries.com/currencies/v1

The v1 path segment is the API version. It's the current, stable version; backwards-compatible additions (new fields, new endpoints, new currencies) ship inside it without a bump. See Versioning.

Append ?pretty to any request to pretty-print the JSON response, which is handy while exploring from a browser or terminal.

Business-plan customers also get a dedicated EU-region endpoint at a separate hostname (eu-west-1) for in-region routing.

Convert

GET /currencies/v1/convert

Converts an amount from one currency into one or more targets. from and to are required; amount is optional and defaults to 1, so omitting it returns the unit rate as result. to accepts a comma-separated list of up to 5 currencies, and the response carries one object per target, in the order requested. The call is all-or-nothing: an unknown from or any unknown to, or more than 5 targets, fails the whole request with a 400. Both fiat and crypto codes are accepted.

Path examples

Unit rate (amount defaults to 1)
GET /currencies/v1/convert?from=USD&to=EUR Run →
Convert 100 USD to EUR
GET /currencies/v1/convert?from=USD&to=EUR&amount=100 Run →
Multiple targets (up to 5)
GET /currencies/v1/convert?from=USD&to=EUR,CAD,GBP&amount=100 Run →
Crypto to fiat
GET /currencies/v1/convert?from=BTC&to=USD Run →

Query parameters

from string required
Source currency code to convert from (e.g. USD). Accepted case-insensitively. Also accepted under the alias base.
to string required
Target currency code, or a comma-separated list of up to 5 (e.g. EUR or EUR,CAD). Returns one object per target. More than 5 targets fails the request.
amount number
Amount in the source currency to convert. Must be a number; defaults to 1 when omitted.

Example

Request
curl "https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100&pretty=1" \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  'https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await response.json();
fetch(
  'https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
)
  .then(function (response) { return response.json(); })
  .then(function (data) { console.log(data); });
import requests
response = requests.get(
  'https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100',
  headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
data = response.json()
$ch = curl_init('https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer YOUR_API_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
require 'net/http'
require 'json'
uri = URI('https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end
data = JSON.parse(response.body)
package main
import (
  "encoding/json"
  "io"
  "net/http"
)
req, _ := http.NewRequest("GET", "https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]interface{}
json.Unmarshal(body, &data)
use reqwest::blocking::Client;
use serde_json::Value;
let client = Client::new();
let response = client
  .get("https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100")
  .header("Authorization", "Bearer YOUR_API_KEY")
  .send()?;
let data: Value = response.json()?;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100"))
  .header("Authorization", "Bearer YOUR_API_KEY")
  .GET()
  .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body();
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_KEY");
var data = await client.GetFromJsonAsync<JsonElement>(
  "https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100");
import Foundation
var request = URLRequest(url: URL(string: "https://api.restcountries.com/currencies/v1/convert?from=USD&to=EUR&amount=100")!)
request.addValue("Bearer YOUR_API_KEY", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data)
Response
{
  "data": {
    "objects": [
      {
        "from": { "code": "USD", "name": "United States dollar", "symbol": "$" },
        "to": { "code": "EUR", "name": "Euro", "symbol": "€" },
        "amount": 100,
        "rate": 0.8772645,
        "result": 87.72645,
        "as_of": 1782691200
      }
    ]
  }
}

With multiple targets, data.objects holds one object per to currency, each with its own rate and result.

Rates

GET /currencies/v1/rates/{base}

Returns the full exchange-rate table for a base currency: every supported currency expressed as units per one unit of the base. Supply the base as the path slug (/rates/USD) or as the base query parameter (/rates?base=USD); when both are given, the path wins. A bare /rates with no base resolves to a 400. Every key in the returned rates map is itself a queryable base, so any code you see here you can also request a table for.

Path examples

Base as path slug
GET /currencies/v1/rates/USD Run →
Base as query parameter
GET /currencies/v1/rates?base=EUR Run →

Query parameters

base string required
Base currency the table is expressed against. Optional when the base is supplied as the path slug (/rates/USD); the path slug takes precedence when both are present. Also accepted under the alias from.

Example

Request
curl "https://api.restcountries.com/currencies/v1/rates/USD?pretty=1" \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  'https://api.restcountries.com/currencies/v1/rates/USD',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await response.json();
fetch(
  'https://api.restcountries.com/currencies/v1/rates/USD',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
)
  .then(function (response) { return response.json(); })
  .then(function (data) { console.log(data); });
import requests
response = requests.get(
  'https://api.restcountries.com/currencies/v1/rates/USD',
  headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
data = response.json()
$ch = curl_init('https://api.restcountries.com/currencies/v1/rates/USD');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer YOUR_API_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
require 'net/http'
require 'json'
uri = URI('https://api.restcountries.com/currencies/v1/rates/USD')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end
data = JSON.parse(response.body)
package main
import (
  "encoding/json"
  "io"
  "net/http"
)
req, _ := http.NewRequest("GET", "https://api.restcountries.com/currencies/v1/rates/USD", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]interface{}
json.Unmarshal(body, &data)
use reqwest::blocking::Client;
use serde_json::Value;
let client = Client::new();
let response = client
  .get("https://api.restcountries.com/currencies/v1/rates/USD")
  .header("Authorization", "Bearer YOUR_API_KEY")
  .send()?;
let data: Value = response.json()?;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("https://api.restcountries.com/currencies/v1/rates/USD"))
  .header("Authorization", "Bearer YOUR_API_KEY")
  .GET()
  .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body();
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_KEY");
var data = await client.GetFromJsonAsync<JsonElement>(
  "https://api.restcountries.com/currencies/v1/rates/USD");
import Foundation
var request = URLRequest(url: URL(string: "https://api.restcountries.com/currencies/v1/rates/USD")!)
request.addValue("Bearer YOUR_API_KEY", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data)
Response
{
  "data": {
    "objects": [
      {
        "base": "USD",
        "as_of": 1782691200,
        "rates": {
          "EUR": 0.8772645,
          "GBP": 0.75560016,
          "CAD": 1.42319257,
          "JPY": 162.17946627
        }
      }
    ]
  }
}

Abbreviated above; the live rates map carries one entry per supported currency, and every code in it is itself a queryable base.

Symbols

GET /currencies/v1/symbols

Returns the supported-currency catalog: one object per currency (code, name, symbol), sorted by code. symbol is null where the currency has no conventional sign (e.g. XAU, gold). Takes no parameters beyond the key. Every code listed here is usable as a from, to, or base elsewhere in the API, so this is the canonical list of what the Currencies API supports.

Path examples

Full supported-currency catalog
GET /currencies/v1/symbols Run →

Example

Request
curl "https://api.restcountries.com/currencies/v1/symbols?pretty=1" \
  -H "Authorization: Bearer YOUR_API_KEY"
const response = await fetch(
  'https://api.restcountries.com/currencies/v1/symbols',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const data = await response.json();
fetch(
  'https://api.restcountries.com/currencies/v1/symbols',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
)
  .then(function (response) { return response.json(); })
  .then(function (data) { console.log(data); });
import requests
response = requests.get(
  'https://api.restcountries.com/currencies/v1/symbols',
  headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
data = response.json()
$ch = curl_init('https://api.restcountries.com/currencies/v1/symbols');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer YOUR_API_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
require 'net/http'
require 'json'
uri = URI('https://api.restcountries.com/currencies/v1/symbols')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer YOUR_API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end
data = JSON.parse(response.body)
package main
import (
  "encoding/json"
  "io"
  "net/http"
)
req, _ := http.NewRequest("GET", "https://api.restcountries.com/currencies/v1/symbols", nil)
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]interface{}
json.Unmarshal(body, &data)
use reqwest::blocking::Client;
use serde_json::Value;
let client = Client::new();
let response = client
  .get("https://api.restcountries.com/currencies/v1/symbols")
  .header("Authorization", "Bearer YOUR_API_KEY")
  .send()?;
let data: Value = response.json()?;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
  .uri(URI.create("https://api.restcountries.com/currencies/v1/symbols"))
  .header("Authorization", "Bearer YOUR_API_KEY")
  .GET()
  .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body();
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer YOUR_API_KEY");
var data = await client.GetFromJsonAsync<JsonElement>(
  "https://api.restcountries.com/currencies/v1/symbols");
import Foundation
var request = URLRequest(url: URL(string: "https://api.restcountries.com/currencies/v1/symbols")!)
request.addValue("Bearer YOUR_API_KEY", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data)
Response
{
  "data": {
    "objects": [
      { "code": "AED", "name": "United Arab Emirates dirham", "symbol": "د.إ" },
      { "code": "EUR", "name": "Euro", "symbol": "€" },
      { "code": "GBP", "name": "Pound sterling", "symbol": "£" },
      { "code": "USD", "name": "United States dollar", "symbol": "$" },
      { "code": "XAU", "name": "Gold (troy ounce)", "symbol": null }
    ]
  }
}

Abbreviated above; the live catalog returns one object per supported currency, sorted by code.

Response objects

Every endpoint answers with a data.objects array. The three endpoints each return a distinct object shape, described below. Currency codes are always uppercase on output regardless of request casing.

Convert object

from object
The source currency as a { code, name, symbol } object, matching the symbols shape.
to object
The target currency as a { code, name, symbol } object.
amount number
The input amount in the source currency (the amount you sent, or 1 by default).
rate number
Units of the target currency per one unit of the source, current as of the as_of timestamp.
result number
The converted amount, i.e. amount × rate.
as_of integer
Unix timestamp (seconds) the rate was sourced. See Freshness & as_of.

Rates object

base string
The base currency code (uppercase) the table is expressed against.
as_of integer
Unix timestamp (seconds) the table was sourced.
rates object
Map of uppercase currency code to its rate (units per one unit of base). Every key is itself a queryable base.

Symbol object

code string
Uppercase currency code (e.g. USD, BTC).
name string
Human-readable currency name (e.g. United States dollar).
symbol string
Conventional sign (e.g. $, ), or null where the currency has no conventional sign.

Freshness & as_of

How often rates update depends on your plan: free plans get 24-hour updates, and paid plans get 1-hour updates. Every convert and rates object carries an as_of timestamp naming when its rate was sourced, so you always know how fresh a number is.

as_of is a unix timestamp in seconds (e.g. 1782691200) marking which refresh a value came from, so it reflects your plan's update cadence.

Errors

Error responses use standard HTTP status codes plus a JSON body that names what went wrong. Each entry in the errors array includes a message describing the failure.

{
  "errors": [
    {
      "message": "A \"to\" currency is required (for example ?to=EUR)."
    }
  ]
}

Most currency-specific failures are 400s raised while validating the request, each with a plain-language message:

  • convert is missing from or to.
  • from, or any to target, names a currency that isn't supported.
  • The to list has more than 5 targets.
  • amount isn't a valid number.
  • rates resolved no base (a bare /rates), or the base currency isn't supported.
StatusMeaning
200OK.
400Malformed request: missing or unsupported from/to/base, too many to targets, or a non-numeric amount (see the list above).
401Missing, unrecognized, revoked, or expired API key, across both Authorization header and ?api-key= query-param forms.
403Forbidden. Returned when the account is frozen (e.g. monthly request limit exceeded). Frozen accounts clear on the monthly billing anniversary or when you upgrade.
404Path doesn't match a known endpoint.
405HTTP method not allowed. Currencies endpoints are GET-only.
410API version is no longer active.
429Too Many Requests. Returned by the edge rate limiter when sustained traffic exceeds 20 requests per 10 seconds.
500Unhandled server error.

Webhooks

Subscribe one or more URLs to receive a signed POST when Currencies API data changes: new rates publishing (rates.updated), a currency joining or leaving the supported set (currency.added, currency.removed), or a currency's metadata changing (currency.updated). Webhooks are a Business plan feature.

Full reference (event types, payload schemas, signature verification, delivery and retry semantics, idempotency, and local testing) lives on its own page: /docs/webhooks.

Data sources

Exchange rates refresh on your plan's cadence (every 24 hours on free plans, every hour on paid plans), and every object carries an as_of stamp naming the refresh its rates came from. Coverage spans both fiat and crypto currencies.

Clean to use. The numbers you receive are yours to use directly in your own product; there's no upstream attribution chain to maintain. (Use of the REST Countries API itself is governed by the Terms.)