Skip to main content

An API for roman numerals

· 3 min read
Filip Tammergård
Software Engineer at Frilans Finans

Today is .

I just added a small API to roman.tammergard.se for converting between arabic and roman numerals.

All endpoints live under /api/v1/, return JSON, and have CORS enabled for all origins. Static endpoints (/to, /from, /validate, /convert, /all) are CDN-cached for a week, while /today is cached until the next midnight in Europe/Stockholm so the day rollover doesn't serve yesterday's date. The valid range is 1–3999, which is what classical roman numerals can express. Full docs at roman.tammergard.se/api.

Today​

const res = await fetch("https://roman.tammergard.se/api/v1/today")
const data = await res.json()

Live response right now:

Arabic to roman​

await fetch("https://roman.tammergard.se/api/v1/to/2026")
// {
// arabic: 2026,
// roman: "MMXXVI"
// }

Roman to arabic​

await fetch("https://roman.tammergard.se/api/v1/from/MMXXVI")
// {
// roman: "MMXXVI",
// arabic: 2026
// }

Convert in either direction​

await fetch("https://roman.tammergard.se/api/v1/convert/MMXXVI")
// {
// kind: "roman",
// input: "MMXXVI",
// roman: "MMXXVI",
// arabic: 2026
// }

The kind field shows which direction the input was converted from. Pass an arabic number to flip it around:

await fetch("https://roman.tammergard.se/api/v1/convert/2026")
// {
// kind: "arabic",
// input: "2026",
// arabic: 2026,
// roman: "MMXXVI"
// }

Validate​

await fetch("https://roman.tammergard.se/api/v1/validate/MMXXVI")
// {
// input: "MMXXVI",
// normalized: "MMXXVI",
// valid: true
// }

The input is normalized (trimmed and uppercased) before validation, so mmxxvi is just as valid as MMXXVI.

All numerals​

await fetch("https://roman.tammergard.se/api/v1/all")
// [
// { arabic: 1, roman: "I" },
// { arabic: 2, roman: "II" },
// ...
// { arabic: 3999, roman: "MMMCMXCIX" }
// ]

The response is around 100 KB. Useful if you'd rather ship the whole table to the client and look things up locally.

Errors​

Errors follow RFC 9110 and distinguish malformed input from semantically invalid input. Malformed values — for example a non-integer where an integer is expected — return 400:

await fetch("https://roman.tammergard.se/api/v1/to/abc")
// {
// error: {
// status: 400,
// message: 'Invalid number "abc". Must be a positive integer.'
// }
// }

Values that are well-formed but out of range — like an arabic number above 3999 or a malformed roman numeral like IIII — return 422:

await fetch("https://roman.tammergard.se/api/v1/to/5000")
// {
// error: {
// status: 422,
// message: 'Invalid number "5000". Must be between 1 and 3999.'
// }
// }

That's the whole API. It's built on the @tammergard/roman package on npm.

roman.tammergard.se​

Behind the API sits a small site at roman.tammergard.se. You can convert between arabic and roman numerals, see today's date in Latin, browse the ones, tens, hundreds, and thousands, and open a dedicated page for each numeral with its neighbors, doubles, and halves.