Official Central Bank Exchange Rates in Odoo
Load central-bank and tax-authority exchange rates into Odoo res.currency.rate over XML-RPC, with the inversion Odoo expects and a scheduled action that runs after the bank publishes.
GET /api/v1/central-bank/{bank}/latest (free key, one call per fetch) or the
keyless evaluation copy at /api/public/central-bank/{bank}?format=json.
Add ?source=USD&target=EUR for one resolved pair, format=xml or
format=csv for importers that do not read JSON, and api_key= on the URL
when the importer cannot send headers. Bank codes are on the
central bank coverage map.
What Odoo does out of the box
Odoo's Automatic Currency Rates setting (module currency_rate_live) can pull from a fixed list of
providers: the ECB, the Swiss Federal Tax Administration, the Bank of Canada, Banxico and a handful of others. If your
publisher is on that list, use it. If it is not, or you need one feed that covers every subsidiary's central bank, write
the rows yourself. Rates live in res.currency.rate and the API for that model is stable across versions.
The rate Odoo expects
The stored rate field is units of the foreign currency per 1 unit of the company currency. For a EUR
company and USD that is USD per EUR, which is the ECB's own direction. For an INR company on the Reserve Bank of India,
which publishes INR per USD, you need the inverse. Ask the API for the pair in Odoo's direction and it will invert or
cross for you, flagging the result with derived:
GET /api/v1/central-bank/rbi/latest?source=INR&target=USD
{ "bank": "rbi", "rate_date": "2026-09-16", "source": "INR", "target": "USD",
"rate": 0.011325, "derived": true, "method": "inverse" }
Odoo 14 and later also expose inverse_company_rate (company currency per foreign unit) on the same model; writing
either field is fine, but be consistent so a rerun updates rather than fights.
Load the rates over XML-RPC
import xmlrpc.client, requests
URL, DB, USER, PWD = "https://erp.example.com", "prod", "bot@example.com", "api-key"
BANK = "ecb"
common = xmlrpc.client.ServerProxy(f"{URL}/xmlrpc/2/common")
uid = common.authenticate(DB, USER, PWD, {})
models = xmlrpc.client.ServerProxy(f"{URL}/xmlrpc/2/object")
call = lambda model, method, *args, **kw: models.execute_kw(DB, uid, PWD, model, method, list(args), kw)
company = call("res.company", "read", [1], fields=["currency_id"])[0]
home = call("res.currency", "read", [company["currency_id"][0]], fields=["name"])[0]["name"]
active = {c["name"]: c["id"] for c in call("res.currency", "search_read", [["active", "=", True]], fields=["name"])}
table = requests.get(f"https://allratestoday.com/api/v1/central-bank/{BANK}/latest",
headers={"Authorization": "Bearer art_live_your_key"}, timeout=10).json()
for code, currency_id in active.items():
if code == home:
continue
r = requests.get(f"https://allratestoday.com/api/v1/central-bank/{BANK}/latest",
params={"source": home, "target": code},
headers={"Authorization": "Bearer art_live_your_key"}, timeout=10)
if r.status_code == 404:
continue # bank does not publish this currency
body = r.json()
vals = {"currency_id": currency_id, "name": body["rate_date"], "rate": body["rate"], "company_id": 1}
existing = call("res.currency.rate", "search",
[["currency_id", "=", currency_id], ["name", "=", body["rate_date"]], ["company_id", "=", 1]])
if existing:
call("res.currency.rate", "write", existing, vals)
else:
call("res.currency.rate", "create", vals)
print(code, body["rate_date"], body["rate"], body["method"]) The first request fetches the whole table so you can see what the bank covers; each pair request afterwards is one call and handles direction. Cache per date if you run it more than once a day.
Run it as a scheduled action
Put the loop in a small custom module and register an ir.cron that runs daily after the bank's publication
time, or run the script from the server's scheduler and let it talk to Odoo over XML-RPC as above. Inside Odoo the same
logic uses self.env['res.currency.rate'] and requests, with the API key in a system parameter rather
than the code.
Notes
nameonres.currency.rateis the date the rate takes effect. Use the bank'srate_date, never the run date.- Multi-company: run the loop per company with that company's home currency and, if the subsidiaries sit under different central banks, per bank code.
- Month-end tables from HMRC or a monthly-average source such as the SNB load the same way on a monthly cron.
Other systems: Business Central · Xero · QuickBooks Online · SAP · Odoo · all integrations. Endpoint reference: Central Bank Rates.