Multi currency pricing
Skill nusantara-ventures/exchangerate-skills/skills/multi-currency-pricing
Agent Skills for exchangerate.dev — FX rates API. Currency conversion, FX correctness, portfolio translation, multi-currency pricing. Works keyless.
npx -y skills add nusantara-ventures/exchangerate-skills --skill multi-currency-pricingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use whenever an app needs to show prices in a visitor's local currency — localizing a SaaS pricing page, e-commerce checkout estimates, "show prices in EUR/IDR/JPY", adding a currency switcher, or any request to "make prices local"/"support multiple currencies". Covers the display-price architecture (fetch, cache, convert, round, format) built on exchangerate.dev, including the critical distinction between showing a localized estimate and actually charging in local currency.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
7.5 KB, as published. Nobody here has run it
Multi-currency pricing
Two different things get called "multi-currency pricing," and conflating them causes real bugs (charging the wrong amount, promising a rate you can't honor):
- Display-only conversion — canonical price stays in one base currency (e.g. USD); the page shows a localized estimate in the visitor's currency; the charge still happens in base currency. This is what most SaaS pricing pages and many checkout flows do.
- True multi-currency pricing — the customer is actually charged in their local currency, at a rate set by your payment provider (Stripe, Adyen, etc.) at transaction time.
This skill covers #1 — the display layer. The charge rate always comes from the payment provider, never from an FX API you called for display. Never present the rate this skill computes as the transaction rate; label it an estimate and let the payment provider quote the real one at checkout. If the task is actually "charge in local currency," that's a payment-provider integration question, not an FX-API question — say so and stop building an FX-driven price engine for it.
1. Server-side rate fetch + cache — never call the FX API per page view
A pricing page rendered for every visitor must not trigger an API call per visitor. Fetch on a schedule, cache app-side, serve from cache.
For a short price list, batch-convert in one call instead of one call per currency:
curl -X POST "https://api.exchangerate.dev/v1/convert" \
-H "Content-Type: application/json" \
-d '{"from": "USD", "pairs": [["EUR", 29], ["GBP", 29], ["JPY", 29], ["IDR", 29]]}'
Body shape is {"from": string, "pairs": [[symbol, amount], ...]} — tuples, not objects. For a raw rate table instead (e.g. you have many SKUs and want to convert client-side), use GET /v1/latest?base=USD&symbols=EUR,GBP,JPY,IDR and cache the rates map.
TTL — key it off source, not a flat number:
source: live(actively-traded currencies — check the per-currencysourcesmap, don't hardcode the set) — minutes to hours is fine for display; these move fast but display doesn't need tick-level freshness.source: ecb_daily— refresh once, after the ~16:00 CET publish; refetching before that just returns yesterday's fix.market_session: weekend— nothing changes until Sydney open Monday morning local time; stop polling over the weekend, don't burn calls re-fetching an unchanged consensus.
At this cache cadence, keyless (12 req/min, IP-bucketed) is enough for local dev; in production get a free key (10,000 calls/month, Authorization: Bearer $EXCHANGERATE_API_KEY) — a daily scheduled fetch for a price list of a few dozen SKU/currency pairs uses a tiny fraction of that quota.
one scheduled job (cron / edge function)
│
▼
GET/POST exchangerate.dev → cache (KV, Redis, or in-memory)
│
▼
page render reads from cache — zero FX API calls per request
2. Price presentation — round to the target currency, then apply pricing psychology separately
Two rounding steps happen, and they are not the same step:
Step A — currency-correct rounding. Round to the target currency's minor_units from GET /v1/currencies. JPY has minor_units: 0 — there is no such thing as ¥2,900.00, only ¥2,900. Prefer the API's converted field (already minor-unit-rounded server-side) over multiplying rate yourself:
curl "https://api.exchangerate.dev/v1/convert/USD/JPY/29"
# converted: 4707 (whole yen — not 4707.16, minor_units is 0)
Step B — charm pricing, applied AFTER conversion, as a business rule. $29 → €26.54 at the raw rate. Presenting €26.54 looks like an unreviewed FX conversion, not a price. Deliberately re-price to your market's convention — €26.99 or €27, whichever your pricing strategy uses — the same way you'd choose $29 instead of $28.73 for the base price. Do NOT let the rounding-to-minor-units step (A) accidentally produce a psychologically-priced number and mistake that for intentional pricing; charm-price explicitly, on top of the correctly-rounded conversion.
Keep the canonical price in base currency in your source of truth (database, pricing config). Every localized price is a derived, presentation-only value — never store a converted price as if it were a base price; recompute it from the canonical value on each repricing pass.
3. Stability vs freshness — don't let prices flicker
Repricing on every rate tick makes the same plan show €27 on Monday and €27.10 on Tuesday for no reason a visitor can see. Common pattern:
- Reprice on a fixed schedule (daily or weekly), or
- Reprice on a threshold move (>1-2% since last reprice), with hysteresis (require the move to persist, not one noisy tick) so it doesn't reprice back and forth across a boundary.
Either way, label the number as an estimate, not a fixed price:
Estimated at ≈ €27, billed in USD — final amount may vary slightly at checkout.
4. Formatting
Format with the platform's locale formatter (Intl.NumberFormat in JS, babel.numbers.format_currency in Python) rather than hand-concatenating a symbol — symbol position, grouping, and decimal count vary per locale (29,00 € vs ¥2,900). A bonus worth knowing: passing the currency code makes the formatter render the right number of decimals on its own (JPY with 0), so you don't hard-code minor_units a second time in the display layer.
5. Compliance and honesty
- Rates are indicative (~5-15 bps band), aggregated market data — fine for display, not a settlement rate. See
fx-rates-correctnessfor why. - Show the conversion basis somewhere near the price: a timestamp ("rates as of Jul 6") or static copy ("prices updated daily; you'll be billed in USD"). Don't imply the localized number is live-ticking if it's a cached daily snapshot.
- Checkout must restate the charge currency explicitly before payment — "You'll be charged $29.00 USD" — even though the page led with a EUR estimate. Never let the last currency a user saw be silently different from the currency they're charged in.
6. The pipeline in one line
Putting §1-4 together, a localized price is: cached base→local rate → charm-price as a business rule → format with the locale formatter → label as an estimate. The one ordering that matters and is easy to get wrong: charm-pricing happens after the currency-correct conversion (§2 Step B after Step A), never folded into it — and the canonical price stays in base currency, every localized figure recomputed from it on each repricing pass.
Related skills in this repo
exchangerate-dev— API reference: endpoints, auth, MCP server, error handling.fx-rates-correctness— the correctness pitfalls this skill leans on: precision/minor-units, staleness,source/market_sessionsemantics, why indicative rates never touch settlement.fx-accounting-rates— once a sale is made, use this skill's rate-of-record conventions to invoice and book the converted amount correctly.