Islamic Finance Guide

Live gold and silver prices for fintech apps

Use the Zakat calculator's metal-price data for indicative gold-backed savings balances, Islamic wealth management dashboards, and custom Zakat estimators.

1. Request prices securely

Create a server-side API key with the finance:zakat scope and set KHALEEJI_API_KEY in your backend environment. Send it using the Authorization header with the Bearer scheme. Never embed it in browser bundles, mobile apps, URLs, logs, or source control.

Call GET /api/v1/zakat/calculate?amount=0&currency=USD, or POST /api/v1/zakat/calculate with a non-empty assets array containing {"type":"cash","value":0}. A zero-cash request retrieves prices without sending customer wealth; its zero Zakat result is not the customer's assessment. POST does not accept a standalone cashAmount field.

Supported currencies: AED (default), SAR, QAR, BHD, OMR, KWD, USD, EUR, GBP. Requests consume normal API quota. See the Zakat API reference.

2. Read the price and freshness fields

Although often described as calculation metadata, prices live under data.metalPrices, not meta. Read these fields together:

  • goldPerGram and silverPerGram: numeric prices per gram, in currency, rounded to two decimal places. Do not convert from troy ounces again.
  • source: market for upstream-derived prices (including cached prices), or fallback for fixed estimates.
  • stale: whether the market price is no longer fresh; fixed fallback prices also set this to true.
  • fetchedAt: server fetch time for market data, not a guaranteed exchange quote timestamp. A fallback carries its generation time, so a recent timestamp alone does not establish freshness.

The server fetches metalpriceapi.com data in USD and caches fresh prices for one hour. If refresh fails, cached market prices may be served up to 24 hours from their original fetch. Beyond that, or with no usable cache, fixed fallback estimates are returned. HTTP 200 therefore does not guarantee fresh market data.

Other currencies use exchange rates from open.er-api.com, cached for one hour, with fixed FX fallbacks. Metal freshness fields do not report FX freshness or fallback status. The examples request USD to avoid this ambiguity; independently verify FX for currency-sensitive decisions.

3. Node.js and Python examples

Both dependency-free backend examples reject stale, fallback, malformed, or over-one-hour-old prices before valuing holdings. Their output is an indicative valuation, not a buy/sell quote. Use decimal or fixed-point arithmetic and an explicit rounding policy for production ledgers; JavaScript floating-point and already-rounded API prices are not settlement-grade precision.

Node.js 24 (built-in fetch)

javascript
// Save as metal-prices.mjs; run with Node.js 24: node metal-prices.mjs
const apiKey = process.env.KHALEEJI_API_KEY
if (!apiKey) throw new Error("Set KHALEEJI_API_KEY on your server")
const response = await fetch(
"https://khaleejiapi.dev/api/v1/zakat/calculate?amount=0&currency=USD",
{
headers: { Authorization: ["Bearer", apiKey].join(" ") },
signal: AbortSignal.timeout(10000),
cache: "no-store",
},
)
if (!response.ok) throw new Error(`KhaleejiAPI HTTP ${response.status}`)
const result = await response.json()
const prices = result.data?.metalPrices
const ageMs = Date.now() - Date.parse(prices?.fetchedAt)
if (!prices || prices.currency !== "USD" || prices.source !== "market" ||
prices.stale !== false || !Number.isFinite(ageMs) ||
ageMs < 0 || ageMs > 60 * 60 * 1000 ||
!Number.isFinite(prices.goldPerGram) || prices.goldPerGram <= 0 ||
!Number.isFinite(prices.silverPerGram) || prices.silverPerGram <= 0) {
throw new Error("Fresh market metal prices unavailable")
}
// Indicative valuation only: 10 fine grams of gold and 100 fine grams of silver.
console.log({
goldValue: (10 * prices.goldPerGram).toFixed(2),
silverValue: (100 * prices.silverPerGram).toFixed(2),
currency: prices.currency,
fetchedAt: prices.fetchedAt,
})

Python 3 (standard library)

python
# Save as metal_prices.py; run with Python 3: python3 metal_prices.py
import json
import os
from datetime import datetime, timezone
from decimal import Decimal
from urllib.request import Request, urlopen
api_key = os.environ["KHALEEJI_API_KEY"]
if not api_key:
raise ValueError("Set KHALEEJI_API_KEY on your server")
request = Request(
"https://khaleejiapi.dev/api/v1/zakat/calculate",
data=json.dumps({
"currency": "USD",
"assets": [{"type": "cash", "value": 0}],
}).encode("utf-8"),
headers={
"Authorization": " ".join(["Bearer", api_key]),
"Content-Type": "application/json",
},
method="POST",
)
# urlopen raises HTTPError for non-success responses; never use their bodies as prices.
with urlopen(request, timeout=10) as response:
result = json.load(response, parse_float=Decimal)
prices = result["data"]["metalPrices"]
fetched_at = datetime.fromisoformat(prices["fetchedAt"].replace("Z", "+00:00"))
age = (datetime.now(timezone.utc) - fetched_at).total_seconds()
gold = Decimal(str(prices["goldPerGram"]))
silver = Decimal(str(prices["silverPerGram"]))
if (prices["currency"] != "USD" or prices["source"] != "market"
or prices["stale"] is not False or not 0 <= age <= 3600
or not gold.is_finite() or not silver.is_finite()
or gold <= 0 or silver <= 0):
raise ValueError("Fresh market metal prices unavailable")
print({
"goldValue": str((Decimal("10") * gold).quantize(Decimal("0.01"))),
"silverValue": str((Decimal("100") * silver).quantize(Decimal("0.01"))),
"currency": prices["currency"],
"fetchedAt": prices["fetchedAt"],
})

4. Apply prices to your product

  • Gold-backed savings: indicative value = fine-gold grams × goldPerGram. For an alloy, fine grams = gross grams × fineness (for example, 18/24 for 18-karat gold). Prices do not account for dealer spreads, fees, custody, allocation, or redemption.
  • Islamic wealth management: value gold and silver holdings separately in the same currency, then aggregate with other assets. Display currency, source, fetchedAt, and stale status alongside balances, and preserve the snapshot for audit.
  • Custom Zakat estimators: use data.calculation.nisabGold, nisabSilver, and nisabThreshold from the same response. The endpoint uses 87.48 grams of gold and 612.36 grams of silver, selecting the lower threshold. Confirm the chosen fiqh methodology, hawl, liabilities, and asset eligibility with a qualified advisor; the API does not track hawl or deduct liabilities.

For an API-calculated estimate, POST this body with the same authorization and Content-Type: application/json. Metal weight is in grams; the required non-negative value is replaced by the weight-based valuation. Normalize purity before submitting weight. Read data.calculation.zakatDueand data.breakdown, and check data.metalPrices again. Recomputing thresholds from displayed prices can differ due to rounding.

json
{
"currency": "AED",
"assets": [
{ "type": "cash", "value": 50000 },
{ "type": "gold", "value": 0, "weight": 10 },
{ "type": "silver", "value": 0, "weight": 100 }
]
}

For gold-backed products, AAOIFI Shariah Standard No. 57 is a relevant review starting point: qualified reviewers must assess ownership, possession, allocation, and spot settlement. This reference-price API neither executes trades nor certifies compliance.

5. Production checklist

  • Cache snapshots server-side by currency, retaining source, stale, and the original fetchedAt. Never reset freshness on a cache hit; expire at your maximum permitted price age.
  • Refresh shared snapshots rather than polling per user. Re-fetching repeatedly does not bypass the upstream one-hour cache.
  • Fail closed for price-sensitive actions when freshness checks fail. A read-only dashboard may show a clearly labelled last-known estimate under a documented maximum-age policy, never as a live quote.
  • Handle 400 input errors, 401 authentication failures, and 403 scope failures without blind retries. On 429, respect rate-limit reset information; use bounded backoff for transient 5xx or network errors.
  • Protect any browser-facing backend with customer authentication and rate limits. Do not expose a credential-bearing unrestricted proxy.