GCC currency rates with Wafeq and Zoho Books
Retrieve a dated GCC exchange-rate quote, then apply it using each accounting platform’s supported workflow. Keep provider credentials on your server and record the rate date with every accounting operation.
1. Fetch a dated rate
The endpoint requires the finance:exchange scope. It returns a rate for one source and target currency pair, plus the date and upstream source. For an AED base currency, request AED → SAR for a quote expressed as SAR per AED.
const apiKey = process.env.KHALEEJI_API_KEYconst baseUrl = "https://khaleejiapi.dev" async function getRate(from, to) { const url = new URL("/api/v1/finance/currency/exchange", baseUrl) url.searchParams.set("from", from) url.searchParams.set("to", to) const response = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` }, }) if (!response.ok) throw new Error(`KhaleejiAPI returned ${response.status}`) const { data } = await response.json() return data // { from, to, rate, date, source }}import osimport requests def get_rate(source_currency: str, target_currency: str) -> dict: response = requests.get( "https://khaleejiapi.dev/api/v1/finance/currency/exchange", params={"from": source_currency, "to": target_currency}, headers={"Authorization": f"Bearer {os.environ['KHALEEJI_API_KEY']}"}, timeout=10, ) response.raise_for_status() return response.json()["data"] # from, to, rate, date, sourceSee the exchange-rates API reference for response fields and rate limits.
2. Add a Zoho Books exchange rate
Look up the Zoho currency ID and organization ID first. The Books API creates a dated rate with POST /books/v3/settings/currencies/{currency_id}/exchangerates and updates an existing rate with PUT plus its exchange-rate ID. Use an OAuth token with the required Settings read/create/update scopes and the API domain for your Zoho data center.
const quote = await getRate("AED", "SAR") // SAR per AEDconst url = new URL( `/books/v3/settings/currencies/${currencyId}/exchangerates`, process.env.ZOHO_API_BASE_URL, // use your Zoho data-center domain)url.searchParams.set("organization_id", process.env.ZOHO_ORGANIZATION_ID) const response = await fetch(url, { method: "POST", headers: { Authorization: `Zoho-oauthtoken ${process.env.ZOHO_ACCESS_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ effective_date: quote.date, rate: quote.rate }),})if (!response.ok) throw new Error(`Zoho Books returned ${response.status}`)For each currency, confirm the rate direction in a test organization against Zoho’s currency screen and a sample transaction before scheduling production updates.
3. Set the rate on a Wafeq invoice
Wafeq documents exchange_rate on invoice creation as the rate to the organization’s base currency at the time of the document. There is no separate currency-rate write endpoint in the current public reference. Fetch invoice currency → base currency, then supply the result with the normal invoice fields.
const baseCurrency = "AED"const invoiceCurrency = "SAR"const quote = await getRate(invoiceCurrency, baseCurrency) // AED per SAR const response = await fetch("https://api.wafeq.com/v1/invoices/", { method: "POST", headers: { Authorization: `Api-Key ${process.env.WAFEQ_API_KEY}`, "Content-Type": "application/json", "X-Wafeq-Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify({ ...invoiceFields, // supply the required contact, dates, and line items currency: invoiceCurrency, exchange_rate: quote.rate, }),})if (!response.ok) throw new Error(`Wafeq returned ${response.status}`)Wafeq private-key authentication uses the Api-Key scheme; OAuth integrations use a Bearer access token. Use an idempotency key for invoice creation and never log API keys or tokens.
4. Schedule and reconcile safely
- Run a server-side worker on your accounting schedule; never call these provider APIs from browser code.
- Store the returned
date, source currency, target currency, and rate with the invoice or rate record. - Upsert Zoho rates by currency and effective date; use Wafeq’s invoice idempotency header to avoid duplicate invoices.
- Reconcile provider responses and accounting records, and route failures for review instead of silently retrying writes.
- Confirm exchange-rate direction, precision, and statutory source with your finance team before production use.
Python worker example
The worker below shows both provider write patterns. Supply the required invoice fields for Wafeq and use the Zoho API base URL for your data center.
import osfrom uuid import uuid4import requests BASE_URL = "https://khaleejiapi.dev" def get_rate(source: str, target: str) -> dict: response = requests.get( f"{BASE_URL}/api/v1/finance/currency/exchange", params={"from": source, "to": target}, headers={"Authorization": f"Bearer {os.environ['KHALEEJI_API_KEY']}"}, timeout=10, ) response.raise_for_status() return response.json()["data"] def add_zoho_rate(currency_id: str, organization_id: str, base: str, quote: str) -> None: rate = get_rate(base, quote) # quote currency per one base currency url = f"{os.environ['ZOHO_API_BASE_URL']}/books/v3/settings/currencies/{currency_id}/exchangerates" response = requests.post( url, params={"organization_id": organization_id}, headers={"Authorization": f"Zoho-oauthtoken {os.environ['ZOHO_ACCESS_TOKEN']}"}, json={"effective_date": rate["date"], "rate": rate["rate"]}, timeout=10, ) response.raise_for_status() def create_wafeq_invoice(invoice: dict, invoice_currency: str, base_currency: str) -> None: rate = get_rate(invoice_currency, base_currency) # base currency per invoice currency payload = {**invoice, "currency": invoice_currency, "exchange_rate": rate["rate"]} response = requests.post( "https://api.wafeq.com/v1/invoices/", headers={ "Authorization": f"Api-Key {os.environ['WAFEQ_API_KEY']}", "X-Wafeq-Idempotency-Key": str(uuid4()), }, json=payload, # invoice must include Wafeq's required contact/date/line-item fields timeout=10, ) response.raise_for_status()