Reconcile Cross-Border Payments With FX Rate Data
Pull dated exchange rates with named providers to reconcile what cross-border payments should have converted at versus what actually settled.

TL;DR — The Currency Rates Scraper gives you a dated, provider-named market rate for any day. Join it to payment settlements and the spread between expected and actual conversion becomes a measurable line item instead of a shrug.
Why cross-border reconciliation needs a benchmark rate
Every international payment has two rates: the one you assumed and the one that settled. The difference is spread plus timing, and it adds up — BIS counts $7.5 trillion in daily FX volume, and none of it moves at the rate your invoice guessed.
Without an independent benchmark, "the bank gave us a bad rate" is a feeling. With one — dated, provider-named — it is a number you can trend, dispute, or budget for.
How does this compare to the alternatives?
| Trust the statement | Paid market-data feed | Thirdwatch actor | |
|---|---|---|---|
| Cost | Free but blind | Expensive | Pay per result |
| Reliability | No benchmark | Excellent | Named free sources |
| Setup time | None | Weeks | Minutes |
| Maintenance | None | Vendor-managed | Scheduled pulls |
A paid feed is overkill for reconciliation — you need a daily fix, not tick data. The actor gives exactly that, with provenance.
How to reconcile payments in 4 steps
How do I pull rates for a settlement period?
One range pull covers the whole month:
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("thirdwatch/currency-rates-scraper").call(
run_input={
"baseCurrencies": ["USD"],
"targetCurrencies": ["EUR", "GBP", "INR"],
"startDate": "2026-08-01",
"endDate": "2026-08-31",
"maxResults": 250,
}
)
rates = list(client.dataset(run["defaultDatasetId"]).iterate_items())How do I join payments to the right rate?
Match each settlement to the rate on its date:
import pandas as pd
rates_df = pd.DataFrame(rates)
rates_df["date"] = pd.to_datetime(rates_df["date"]).dt.date
payments = pd.read_csv("settlements.csv") # id, pair, date, expected, settled
merged = payments.merge(
rates_df[["pair", "date", "rate", "provider_name"]],
on=["pair", "date"], how="left")Missing joins mark days where the provider had no fix — forward-fill or flag them deliberately.
How do I compute the variance?
merged["benchmark_amount"] = merged["amount"] * merged["rate"]
merged["variance_pct"] = (merged["settled"] / merged["benchmark_amount"] - 1) * 100
print(merged.groupby("pair")["variance_pct"].mean())A consistent 2-4% gap is your processor's real spread. A sudden spike on one date is worth a support ticket — and the provider-named rate is your evidence.
How do I make it repeatable?
Schedule the pull monthly, append rates to a JSONL store, and the reconciliation becomes a join you rerun, not a project:
import json
with open("fx_benchmark.jsonl", "a") as f:
for r in rates:
f.write(json.dumps(r) + "\n")Sample output
{"base_currency": "USD", "quote_currency": "GBP", "pair": "USD/GBP",
"date": "2026-08-14", "rate": 0.7861, "inverse_rate": 1.2721,
"amount": 1, "converted_amount": 0.7861,
"provider": "erapi", "provider_name": "ExchangeRate-API open access"}date keys the join; provider_name is the citation when you dispute a settlement.
Common pitfalls
Settlement date and quote date differ — decide which one your policy benchmarks and be consistent. Weekend settlements map to the nearest published fix; document the convention. A benchmark shows market rate, not your processor's rate — the gap is the finding, not a bug. The actor names the provider per row so variance reports survive a source switch.
Related use cases
Frequently asked questions
What is FX reconciliation in one sentence?
Comparing the exchange rate a payment was expected to convert at — a dated, sourced market rate — against the rate that actually settled, and explaining the difference.
Why does the settled amount differ from the quoted amount?
Processors add spread and fees on top of mid-market rates, and settlement can happen days after the quote. A dated benchmark rate separates real cost from noise.
Can I pull the rate for a past settlement date?
Yes. Set the date field to the settlement or quote day and the actor returns that day's published fix with the provider named.
How do I batch a month of payments?
Use startDate/endDate to pull the whole period's daily rates once, then join payments to the rate for each settlement date locally.