How to integrate Special Drawing Rights (XDR) - N/A Convert API into a Node.js backend
You need to quote or settle transactions in IMF Special Drawing Rights (XDR) and convert them to/from USD inside a Node.js backend. By the end of this walkthrough, you’ll have a small, production-ready service that (1) fetches real-time XDR reference rates, (2) converts arbitrary amounts between XDR and USD using Metals-API’s Convert endpoint, and (3) returns clean, cached JSON for your trading, treasury, or pricing workflows.
What you’re building: a Node.js microservice for XDR
The goal is a minimal service that your frontend or another backend can call to get:
- Latest XDR and USD cross rate (including the convenient inverted field USDXDR).
- On-demand conversion of amounts between USD and XDR using Metals-API’s Convert endpoint.
- Consistent timestamps (UTC), safe defaults, and simple caching to reduce API calls.
You’ll use two Metals-API endpoints that are relevant to XDR:
- GET /api/convert — convert between USD and XDR.
- GET /api/latest — fetch the latest reference rates for XDR (and USDXDR inversion).
If you’re new to the service, review the Metals-API Documentation and confirm the symbol you need on the Metals-API Supported Symbols list. You can register for an API key here: Register.
Why XDR in a metals and currencies API?
Many commodities and cross-border operations use Special Drawing Rights (XDR) as a neutral unit of account. Treasury teams, clearing operations, and research workflows often need a stable reference like XDR to normalize exposure across USD and multiple metals. With Metals-API, you can integrate XDR alongside metals in one consistent schema, making it easier to power analytics, risk dashboards, or automated settlement logic.
Note on units: currency rates such as USD and XDR are not tied to “per troy ounce.” That unit applies to metals symbols. For currency cross-rates, think in unitless ratios (e.g., XDR per USD) and, when needed, invert to USD per XDR.
Endpoint #1: Convert (USD ⇄ XDR)
Use the Convert endpoint to turn a notional amount in one currency into the other. This is ideal for settlement, quoting, order validation, or invoicing flows that must return a concrete converted value.
curl: convert 250,000 USD into XDR
curl "https://metals-api.com/api/convert?access_key=YOUR_API_KEY&from=USD&to=XDR&amount=250000"
Swap parameters to go the other way:
curl "https://metals-api.com/api/convert?access_key=YOUR_API_KEY&from=XDR&to=USD&amount=250000"
Response notes you will use:
- query.from, query.to, query.amount — echo your input; safe to log.
- info.rate — the applied rate for this conversion at the given timestamp.
- result — the converted amount you’ll return to the caller.
- timestamp — unify to UTC and propagate across systems.
The Convert endpoint returns a rate consistent with Metals-API’s base conventions. For currency conversions, treat the returned rate as a unitless cross-rate (e.g., XDR per USD or USD per XDR) depending on direction.
Endpoint #2: Latest (reference XDR and inversion)
For dashboards, daily mark-to-market, or to pre-fill a cache, pull the Latest endpoint with XDR. Metals-API provides both the direct rate (XDR per 1 USD) and an inverted helper (USDXDR: USD per 1 XDR) so you don’t need to compute it client-side.
curl: get the latest XDR reference
curl "https://metals-api.com/api/latest?access_key=YOUR_API_KEY&symbols=XDR,USD,USDXDR"
Official example JSON for XDR (copy and use these exact values to test your parser):
{"success":true,"timestamp":1791677340,"date":"2026-10-11","base":"USD","rates":{"XDR":0.707052,"USD":1,"USDXDR":1.4143231332348964}}
How to read this:
- base — USD. Rates are expressed per 1 USD unless explicitly inverted.
- rates.XDR — 0.707052 means 1 USD = 0.707052 XDR.
- rates.USDXDR — 1.4143231332348964 means 1 XDR = 1.4143231332348964 USD (already inverted for you).
- timestamp — epoch seconds. Convert to UTC for storage and auditing.
Node.js: minimal service for XDR conversion and caching
The following Node.js example exposes two routes:
- GET /fx/xdr/latest — returns the latest XDR, USD, and USDXDR fields from Metals-API Latest.
- GET /fx/xdr/convert?from=USD&to=XDR&amount=... — converts amounts via Metals-API Convert.
It also includes a tiny in-memory cache for /latest to reduce requests during a 60–600s window (tune to your plan and latency needs). For production, consider a shared cache like Redis.
import http from 'node:http';
import { setTimeout as delay } from 'node:timers/promises';
import fetch from 'node-fetch'; // npm i node-fetch@3
const API_KEY = process.env.METALS_API_KEY || 'YOUR_API_KEY';
const BASE_URL = 'https://metals-api.com/api';
let latestCache = null; // { data, ts }
const LATEST_TTL_MS = 120000; // 2 minutes; tune to your plan’s update frequency
async function getLatestXDR() {
const now = Date.now();
if (latestCache && now - latestCache.ts < LATEST_TTL_MS) return latestCache.data;
const url = `${BASE_URL}/latest?access_key=${API_KEY}&symbols=XDR,USD,USDXDR`;
const res = await fetch(url);
if (!res.ok) throw new Error(`Latest request failed: ${res.status}`);
const json = await res.json();
if (!json.success) throw new Error('Latest response not successful');
latestCache = { data: json, ts: Date.now() };
return json;
}
async function convertXDR({ from, to, amount }) {
const url = `${BASE_URL}/convert?access_key=${API_KEY}&from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&amount=${encodeURIComponent(amount)}`;
const res = await fetch(url);
if (!res.ok) throw new Error(`Convert request failed: ${res.status}`);
const json = await res.json();
if (!json.success) throw new Error('Convert response not successful');
return json;
}
function json(res, status, body) {
const payload = JSON.stringify(body);
res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });
res.end(payload);
}
const server = http.createServer(async (req, res) => {
try {
if (req.method === 'GET' && req.url.startsWith('/fx/xdr/latest')) {
const data = await getLatestXDR();
// Normalize output: keep only fields you need downstream
const { timestamp, date, base, rates } = data;
return json(res, 200, {
timestamp,
iso_date: date,
base,
xdr_per_usd: rates?.XDR,
usd_per_xdr: rates?.USDXDR
});
}
if (req.method === 'GET' && req.url.startsWith('/fx/xdr/convert')) {
const u = new URL(req.url, 'http://localhost');
const from = u.searchParams.get('from') || 'USD';
const to = u.searchParams.get('to') || 'XDR';
const amountStr = u.searchParams.get('amount') || '1';
const amount = Number(amountStr);
if (!Number.isFinite(amount) || amount <= 0) {
return json(res, 400, { error: 'amount must be a positive number' });
}
// Basic validation for supported direction (extend as needed)
if (!['USD','XDR'].includes(from) || !['USD','XDR'].includes(to)) {
return json(res, 400, { error: 'only USD <-> XDR conversions are exposed by this route' });
}
const data = await convertXDR({ from, to, amount });
// Normalize response
return json(res, 200, {
timestamp: data?.info?.timestamp,
from: data?.query?.from,
to: data?.query?.to,
amount: data?.query?.amount,
rate_applied: data?.info?.rate,
result: data?.result
});
}
// Health check and fallback
if (req.method === 'GET' && req.url === '/healthz') return json(res, 200, { ok: true });
json(res, 404, { error: 'not found' });
} catch (err) {
// Backoff on provider/network errors to avoid thundering herd
await delay(50);
json(res, 502, { error: 'upstream_error', detail: String(err?.message || err) });
}
});
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`XDR service listening on :${PORT}`);
});
Request/response semantics you should not skip
- Base currency: in the Latest response above, base is USD. rates.XDR is XDR per 1 USD. rates.USDXDR is already inverted for convenience.
- Units: metals are quoted per troy ounce in many endpoints. XDR and USD are currencies and thus unitless cross-rates. Don’t label currency conversions as “troy ounces.”
- Timestamps and timezone: timestamps are epoch seconds; treat them as UTC. Store both the raw epoch and an ISO string for audits.
- Weekends/market closures: metals may have limited updates on weekends or holidays; currencies can also experience slower updates. If you fetch on a closed day, your timestamp/date may reflect the latest available fix. Cache sensibly and surface the date in your responses.
- Caching and rate savings: cache /latest for a short TTL aligned with your plan’s update interval. For repeated conversions at the same rate, consider using /latest to compute conversions server-side (multiply by amount) if that meets your accuracy and reconciliation needs; otherwise continue using /convert for auditable results.
- Precision: JavaScript numbers are floating point. For money, use decimal libraries (e.g., decimal.js, big.js) and fix your rounding strategy (e.g., bankers’ rounding or standard half-up) to match your finance system.
Designing your integration for production
Beyond the bare minimum, consider a few patterns that pay dividends:
- Normalization layer: convert Metals-API fields into a stable internal schema (e.g., xdr_per_usd, usd_per_xdr, iso_date). Keeps downstream systems consistent.
- Feature flags: toggle between using /convert vs. local math based on your audit requirements and update cadence.
- Backoff and retries: implement exponential backoff for transient failures and observe provider guidance from the Documentation.
- Service-level telemetry: log timestamp, base, and resulting rates alongside request IDs. If treasury disputes a rate, you can trace exactly which fix was applied.
- Access control: keep your API key server-side. Never expose it in the browser.
Extending beyond XDR without breaking the contract
Once the XDR microservice is stable, it’s straightforward to add other symbols by wiring more routes with the same pattern. Verify exact codes on the Metals-API Supported Symbols page. If you later decide to blend in metals pricing (e.g., to express a basket of industrial inputs in XDR), remember that:
- Metals are typically per troy ounce; you may need to convert to grams or kilograms for supply chain or manufacturing contexts.
- When quoting metals in XDR, you can combine /latest for XDR and metals, or use /convert in steps (USD → XDR, then apply metals in USD). Ensure you handle unit transformations consistently.
If you’re exploring digital transformation in commodity workflows—say, building smart ERP cost models or advanced analytics for materials like neodymium (ND)—a stable reference like XDR can help unify disparate price sources. Use the same Node.js patterns here to add routes for metals, and normalize everything into a coherent, auditable data model.
Operational tips that save time
- Round-trip testing: after implementing USD→XDR and XDR→USD, test a random amount through both directions and validate the delta is only due to rounding (if any).
- Input validation: reject non-numeric and negative amounts early to avoid unnecessary provider calls.
- Time boundaries: store both server receipt time and provider timestamp for each conversion. If a ledger posts “as of” a specific date, use the historical endpoints documented in the Documentation to fetch the correct fix for that day.
- Change monitoring: watch for significant moves in USDXDR. If you have SLAs or margin rules tied to XDR exposures, alert on changes beyond a threshold within your TTL window.
- External reference: for domain context on XDR composition and fixes, see the IMF’s page on Special Drawing Rights Special Drawing Right (IMF factsheet).
Testing: verify parsing against the official example
Before hitting production, assert your parser with the known-good payload below to ensure you handle base, rate inversion, and timestamps correctly:
{"success":true,"timestamp":1791677340,"date":"2026-10-11","base":"USD","rates":{"XDR":0.707052,"USD":1,"USDXDR":1.4143231332348964}}
Unit tests should assert:
- usd_per_xdr = rates.USDXDR exactly as provided.
- xdr_per_usd = rates.XDR exactly as provided.
- timestamp converts to an ISO string in UTC without timezone drift.
Common pitfalls when working with XDR
- Confusing units: don’t apply “per troy ounce” to XDR/USD. Keep them unitless.
- Forgetting inversion: if you only fetch rates.XDR and need USD per XDR, either use rates.USDXDR (preferred) or invert safely using high-precision math.
- Over-calling the API: cache /latest within the provider’s update cadence. For synchronous conversions, fall back to /convert, but don’t refetch /latest multiple times per request.
- Rounding policies: document your rounding policy and apply it uniformly across both directions of conversion.
Observability and auditing
For regulated or high-stakes environments, store:
- Provider timestamp and ISO date.
- Exact inputs and returned rate from /convert.
- Derived fields (e.g., usd_per_xdr, xdr_per_usd) and final result with precision/scale.
- Versioned code or configuration hash handling the conversion.
When paired with Metals-API’s consistent schema, this data model makes reconciliations and audits straightforward.
Where to go next
- Read the endpoint details and parameters in the Metals-API Documentation.
- Confirm the symbols you plan to support via the Metals-API Supported Symbols list.
- Explore programmatic controls or integrations for managed connectivity in MCP.
If you need an API key to follow along, you can Register at Metals-API and start wiring your Node.js backend in minutes.
FAQ
Q: Do I use /latest or /convert for real-time quoting?
A: For reference display or to pre-fill a cache, use /latest. For settlement-grade conversions tied to a specific timestamp and auditable rate, use /convert.
Q: What does base: "USD" mean in the Latest response?
A: Rates are expressed per 1 USD. For XDR, rates.XDR is “XDR per USD.” The response also provides rates.USDXDR (“USD per XDR”) to avoid manual inversion.
Q: How should I store timestamps?
A: Persist the epoch seconds from the API and an ISO 8601 UTC string. Use the provider date for reporting and the epoch for precise ordering and deduplication.
Q: How do I avoid rate-limit or quota hotspots?
A: Cache /latest for a short TTL aligned to your plan’s refresh cadence, batch downstream consumers behind your Node.js service, and implement jittered retries with exponential backoff.
Q: Can I convert metals quotes into XDR?
A: Yes. Fetch the metals rate (often per troy ounce) and the XDR cross-rate. Convert units (e.g., ounces to grams) as needed and apply either the inverted USDXDR or the Convert endpoint to express values in XDR.
Ready to put this into production? Get your API key now and ship your XDR Node.js backend: Register. For advanced controls and integration options, check out MCP, and keep the Documentation and Symbols pages handy as you extend coverage.