Monetary Amounts
Why amounts are integers, how currencies are written, and how to format them for display.
What this is and who needs it
A small but constant source of bugs in payment integrations is rounding. EPD avoids the problem entirely by sending and receiving every amount as an integer in the smallest unit of the given currency. There are no decimals over the wire.
Anyone who shows a price to a user, computes a discount, or reconciles totals needs to read this page once.
Examples
| Display | API value | Currency | Smallest unit |
|---|---|---|---|
$1.00 | 100 | usd | cent |
$29.99 | 2999 | usd | cent |
$297.00 | 29700 | usd | cent |
€50.00 | 5000 | eur | cent |
£12.50 | 1250 | gbp | penny |
¥1,000 | 1000 | jpy | yen (no decimals) |
Currency codes are lowercase. Send usd, eur, gbp — not USD. EPD validates this strictly. Sending "USD" returns validation_error.
Zero-decimal currencies (jpy, krw, vnd, etc.) use the whole-unit value directly — 1000 jpy means ¥1,000, not ¥10. Always check the currency before applying any / 100 conversion in your UI.
Formatting in your UI
Use the platform’s locale-aware formatter rather than manual string concatenation:
function format(amount, currency) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency.toUpperCase(),
}).format(amount / (currency.toLowerCase() === "jpy" ? 1 : 100));
}
format(2999, "usd"); // "$29.99"
format(1000, "jpy"); // "¥1,000"
from babel.numbers import format_currency
def format(amount: int, currency: str) -> str:
divisor = 1 if currency.lower() == "jpy" else 100
return format_currency(amount / divisor, currency.upper(), locale="en_US")
format(2999, "usd") # "$29.99"