Accounting integrations

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.

typescript
const apiKey = process.env.KHALEEJI_API_KEY
const 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 }
}
python
import os
import 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, source

See 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.

typescript
const quote = await getRate("AED", "SAR") // SAR per AED
const 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.

typescript
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.

python
import os
from uuid import uuid4
import 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()

Official references