Odoo's Automatic Currency Rates setting (module currency_rate_live) pulls from a fixed list of providers: the ECB, the Swiss FTA, the Bank of Canada, Banxico and a few more. If the central bank your statutory accounts must follow is on that list, use it and stop reading.
If it is not, or you run subsidiaries under several central banks and want one feed, you write the rows yourself. The good news is that res.currency.rate is one of the most stable models in Odoo and the loader is short. The full version with notes is in the Odoo guide.
The rate Odoo stores
res.currency.rate.rate 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, so nothing to invert.
For an INR company following the Reserve Bank of India, which publishes INR per USD, you need the inverse. Do not divide in your own code. Ask the API for the pair in Odoo's direction and let it invert, flagging what it did:
GET https://allratestoday.com/api/v1/central-bank/rbi/latest?source=INR&target=USD
{ "bank": "rbi", "rate_date": "2026-10-09", "source": "INR", "target": "USD",
"rate": 0.01035, "derived": true, "method": "inverse" }
derived: true with method: "inverse" is the line you want in your audit file. When the bank publishes the pair directly you get derived: false, method: "published".
The loader
import xmlrpc.client, requests
URL, DB, USER, PWD = "https://erp.example.com", "prod", "bot@example.com", "api-key"
BANK = "ecb"
ART = {"Authorization": "Bearer art_live_your_key"}
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"])}
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=ART, 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"])
Three details that bite people:
-
nameis the effective date, not the run date. Always use the bank'srate_date. If you run the loader on a Monday for a bank that publishes on business days, you get Friday's table dated Friday, which is correct. - Multi-company: run the loop per company with that company's home currency, and per bank code if the subsidiaries sit under different central banks.
- Month-end sources such as HMRC's monthly table or a monthly-average publisher load the same way on a monthly cron.
Run it inside Odoo
Put the loop in a small custom module and register an ir.cron that runs daily after the bank's publication time. Inside Odoo the same logic uses self.env['res.currency.rate'] and requests, with the API key in an ir.config_parameter rather than in the code. An inverse_company_rate field also exists from Odoo 14; writing either field is fine, but be consistent so reruns update rather than fight.
Try it without a key
https://allratestoday.com/api/open/central-bank/ecb returns the latest ECB table keyless, so you can prototype the loader before signing up.
Links
- Odoo guide: https://allratestoday.com/docs/integrations/odoo/
- 121 central banks covered, with each bank's publication cadence: https://allratestoday.com/central-bank-rates-api/
- Other ERPs: https://allratestoday.com/docs/integrations/
- Free API key: https://allratestoday.com/register/
Rates are redistributed from each institution's public publication. They are reference rates for accounting, tax and compliance, not dealing quotes.
Top comments (0)