Exchange Rate API for Multi-Currency Invoicing

Create an invoice in one currency and report it in another without losing the original amount or the rate used. Fetch a dated FX observation, round the money once, and save the evidence.

Lock the rate on the invoice date

An invoice needs to keep its numbers after it is sent. If you show a reference value in a second currency, fetch the rate for the invoice date and save the result. Don't recalculate an old document with today's rate. The customer, support team, and finance team should all see the same number.

The rate date follows your business rule. Some systems use the issue date. Others use the service date or posting date. The API gives you the observation for the date you request; your contract and company policy still decide which date applies. A weekend or holiday response uses the latest published fix and marks it with is_forward_filled.

Keep both the original amount and the conversion

Store the original currency and amount as the main facts. Add the reporting currency, converted amount, rate, requested date, observation time in data_updated_at, source, and is_forward_filled. With that shape, you can recreate the PDF, answer a customer question, or export the invoice to accounting software without guessing which rate was used.

Round the converted amount for the target currency's minor units. JPY normally has no decimal places; many currencies have two. Keep the rate at full precision and avoid rounding every line more than once. If your invoice policy rounds at the document level, total the line items first and convert that subtotal.

Give credit notes a clear FX rule

A credit note should point back to the original invoice and state which FX rule it uses. For a full reversal, reusing the invoice's rate will usually reverse the reporting value cleanly. If your policy requires the credit-note date, fetch a new observation and keep it as a separate record.

Don't edit the original invoice record to fit the correction. Store the correction document, original amount, converted amount, and relationship. That history is much easier to reconcile than one row that changes each time somebody updates a document. For the ledger side, use the accounting software guide.

Keep invoice value separate from payment outcome

The invoice rate is an indicative value for display or reporting. The bank or payment processor decides the settled amount, fees, and actual exchange rate. When payment arrives, save their settlement record and compare it with the booked invoice value. Your accounting policy decides how to record the difference.

This line matters for any cross-border invoice. exchangerate.dev helps your software make a stable display and a repeatable report, but it can't promise what the seller receives or what the customer pays. Use the payment provider's record as the source for settlement.

Create a JPY invoice display with a dated rate

Keep the API key on your server. This example records the rate and its freshness fields alongside the value your application calculated.

TypeScript · server-side multi-currency invoice
import Decimal from 'decimal.js';

type HistoricalRates = {
  date: string;
  rates: Record<string, number>;
  source: 'ecb_daily' | 'fred_daily' | 'nbp_daily';
  data_updated_at: string;
  is_forward_filled: boolean;
};

const invoice = {
  id: 'invoice_2207',
  issuedOn: '2026-07-15',
  currency: 'USD',
  total: '2400.00',
  displayCurrency: 'JPY',
} as const;

const response = await fetch(
  `https://api.exchangerate.dev/v1/${invoice.issuedOn}/${invoice.currency}?symbols=${invoice.displayCurrency}`,
  { headers: { Authorization: `Bearer ${process.env.EXR_API_KEY}` } },
);
if (!response.ok) throw new Error('Could not load the invoice-date FX rate');

const fx = (await response.json()) as HistoricalRates;
const rawRate = fx.rates[invoice.displayCurrency];
if (!Number.isFinite(rawRate)) throw new Error('Missing USD/JPY rate');

// JPY is a zero-decimal currency. Keep rate at full precision.
const rate = new Decimal(String(rawRate));
const displayTotal = new Decimal(invoice.total)
  .mul(rate)
  .toDecimalPlaces(0, Decimal.ROUND_HALF_UP)
  .toFixed(0);

await saveInvoiceFx({
  invoice_id: invoice.id,
  original_currency: invoice.currency,
  original_total: invoice.total,
  display_currency: invoice.displayCurrency,
  display_total: displayTotal,
  fx_rate: rate.toString(),
  fx_requested_date: fx.date,
  fx_source: fx.source,
  fx_observed_at: fx.data_updated_at,
  fx_is_forward_filled: fx.is_forward_filled,
});

Ready to test the API?

Start with the quickstart, check the response fields, and see pricing when you need account quota.

Free is for evaluation and internal, non-commercial use. Basic and Pro include commercial use.

Create a free evaluation key →