DEV Community

Cover image for Date.parse() breaks on 5 imperial eras — I built a Japan locale API for AI agents
Hiroki Sonoda
Hiroki Sonoda

Posted on

Date.parse() breaks on 5 imperial eras — I built a Japan locale API for AI agents

TL;DR

  • Japanese locale support is full of edge cases that mainstream i18n libraries don't cover: imperial era dates, invoice check digits, corporate numbers, variable-length area codes, and reversed address order.
  • Torify is a Japan locale API designed for AI agents — 39 REST endpoints + 10 MCP tools.
  • Try it three ways: free MCP server (100 req/day/IP), free Trial key (100 calls/month), or x402 pay-per-call ($0.02/call, agent-autonomous).

Why I wrote this

Japanese locale issues are scattered across the issue trackers of every major library. date-fns has issue #2156 about wareki formatting returning 1月 14 2021 when it should return 1月14日2020年. moment.js has issue #5698 — still open — about there being no token to parse the abbreviated 令2年1月1日. Even Electron had to backport Reiwa support into ICU after the 2019 era change.

Intl.DateTimeFormat('ja-JP-u-ca-japanese') does render eras, but the standard API can't handle era-change boundaries (Showa 64 = Heisei 1, January 7, 1989) or abbreviated forms like R6.5.1. Each of these is a small bug on its own. Pile them together and you get the AI agent persona that's been burning me out for months:

Foreign engineer rolling out a SaaS into the Japan market. A "trivial date validation" that takes 30 minutes back home has eaten three days because of Japan-specific rules.

That's the pain Torify is built to remove.

Every issue/PR linked above maps to a Torify endpoint:

  • moment #5698 (no abbreviation token)parseWarekiString handles all 5 forms: R2.5.1, 令2年5月1日, 令和元年5月1日, H31.4.30, 令6年5月1日.
  • electron PR #17833 (era boundaries) → strict eraStart enforcement plus automatic rejection of dates that fall in the next era (e.g. H31.5.1 is already Reiwa → 400).
  • date-fns #2156 (incomplete formatter) → ICU-independent implementation that returns a consistent schema across all 5 eras (era, eraRomaji, eraYear, eraYearLabel, formatted).

One REST or MCP call — no library bugs, no runtime version pinning.


Try it in 60 seconds — get a free Trial key

Every endpoint below requires an X-Trial-Key header (or X-Api-Key for Pro/Enterprise, or x402 wallet payment). The trial key is free, 100 calls/month, no credit card. One curl to claim it:

curl -X POST "https://torify.dev/v1/trial/signup" \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "consent": true}'
# { ok: true, data: { key: "trial_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", monthlyLimit: 100, message: "..." } }
Enter fullscreen mode Exit fullscreen mode

Save the key value. Every subsequent curl in this article uses it as X-Trial-Key. (Pro/Enterprise customers swap in X-Api-Key. Agents using x402 pay $0.02/call autonomously — no key needed; see Section C below.)

No-auth curls return 402 Payment Required, not the JSON shown. That's the x402 protocol challenge for agent-autonomous payment. Use the trial key or x402 wallet to get actual data.


The 5 ways AI agents silently fail on Japanese locale

1. Wareki dates — Date.parse() dies on 5 imperial eras

Japan uses an imperial era calendar. The current era is Reiwa (since May 1, 2019). Before that came Heisei, Showa, Taisho, and Meiji — each with its own start date.

Government APIs, invoices, and forms routinely return strings like 令和6年5月1日 or R6.5.1. Date.parse() won't touch them. And then there's the edge case from hell: January 7, 1989 is both Showa 64 and the first day of Heisei.

# All curls in this section need: -H "X-Trial-Key: trial_YOUR_KEY"

curl "https://torify.dev/v1/wareki/convert?direction=g2w&date=2024-05-01" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# { era: "令和", eraRomaji: "Reiwa", eraYear: 6, eraYearLabel: "6年", formatted: "令和6年5月1日" }

curl "https://torify.dev/v1/wareki/convert?direction=w2g&era=showa&eraYear=64&month=1&day=7" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# { year: 1989, month: 1, day: 7, formatted: "1989-01-07" }  <- Showa's final day, handled correctly

# Abbreviations: R6.5.1 / 令和6年5月1日 / 令6年5月1日 all accepted as free-form input
curl "https://torify.dev/v1/wareki/convert?direction=w2g&input=R6.5.1" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# { year: 2024, month: 5, day: 1, formatted: "2024-05-01" }

curl "https://torify.dev/v1/wareki/convert?direction=w2g&input=%E4%BB%A4%E5%92%8C6%E5%B9%B45%E6%9C%881%E6%97%A5" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# Same result — passing 令和6年5月1日 raw also works.

# Era boundaries enforced both ways: Heisei 31.5.1 is actually Reiwa 1.5.1, so it's rejected
curl "https://torify.dev/v1/wareki/convert?direction=w2g&input=H31.5.1" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# 400 INVALID_INPUT: H31.5.1 は次の元号が始まった日付のため無効です。正しい元号で指定してください。
# (Note: Heisei ended 2019-04-30 — use "R1.5.1" instead. Error messages are currently
# returned in Japanese only; see /openapi.json for the English schema.)
Enter fullscreen mode Exit fullscreen mode

2. Invoice numbers — the NTA's own check digit (Luhn-like, but mod 9) + registry lookup

Since October 2023, Japan's qualified invoice system (適格請求書等保存方式) requires every B2B invoice to reference a 13-digit T-prefixed number.

The check digit looks like Luhn at first glance — but it isn't. The 12-digit base is multiplied by [2,1,2,1,...] right-to-left, summed, and the check digit is 9 - (sum % 9). Luhn is mod 10 with digit-splitting; this is mod 9 without digit-splitting. Drop in a Luhn implementation from a foreign payments library and you will get a 100% failure rate. Reverse the multiplier direction, and real invoice numbers fail validation.

This isn't a UX issue. It's a tax compliance failure.

# Format check + check digit (offline, no network call)
curl "https://torify.dev/v1/invoice/validate?number=T7000012050002" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# { valid: true, format: "valid", checkDigit: { expected: 7, actual: 7, match: true }, type: "corporate" }
Enter fullscreen mode Exit fullscreen mode

A live registry lookup is also available via /v1/invoice/verify (same auth header, NTA Web-API backed, returns registered, registrantName, registrationDate, cancellation date). Subject to NTA Web-API availability and registrant state.

3. Corporate numbers — 13 digits + the NTA API

Every Japanese corporation has a 13-digit corporate number (法人番号) assigned by the National Tax Agency. Unlike US EINs (9 digits) or EU VAT numbers, the only official way to verify one is the dedicated NTA API at houjin-bangou.nta.go.jp. The endpoint returns XML, and the counter-intuitive part: type=11 is invalid (use type=12).

curl "https://torify.dev/v1/houjin/lookup?number=7000012050002" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# {
#   name: "国税庁",
#   address: {
#     full: "東京都千代田区霞が関3丁目1番1号",
#     prefecture: "東京都", city: "千代田区", rest: "霞が関3丁目1番1号"
#   },
#   kind: { code: "101", ja: "国の機関", en: "Government Agency" },
#   status: "active",
#   confidence: 0.99
# }
Enter fullscreen mode Exit fullscreen mode

4. Phone numbers — area codes are 2-5 digits, variable length

Japanese area codes aren't fixed length:

Region Area code Subscriber part
Tokyo 03 (2 digits) 8 digits
Osaka 06 (2 digits) 8 digits
Nagano / Suwa 0266 (4 digits) 6 digits
Toll-free 0120 (4 digits) 6 digits

Generic E.164 parsers either fail outright or produce wrong results. We've seen libraries silently split 0266-22-2611 as 02-6622-2611 because they assumed a 2-digit area code.

curl "https://torify.dev/v1/phone/validate?phone=0266-22-2611" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# { valid: true, type: "fixed", typeEn: "Landline (fixed)", normalized: "0266222611", e164: "+81266222611" }
Enter fullscreen mode Exit fullscreen mode

5. Addresses — largest-first ordering + full-width/half-width digit mix

Japanese addresses go prefecture → city → town → block — the exact reverse of street → city → state in the West. Worse, half-width and full-width digits are mixed freely in real data, and postal codes don't map to a single address — they map to a town area, and the building/unit number comes after.

curl "https://torify.dev/v1/address/normalize?address=東京都千代田区霞が関3丁目1番1号" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# { prefecture: "東京都", prefectureEn: "Tokyo", city: "千代田区", town: "霞が関3丁目1番1号", normalized: "東京都千代田区霞が関3丁目1番1号", streetNumberHyphen: "3-1-1" }

curl "https://torify.dev/v1/postal/lookup?zipcode=1000013" \
  -H "X-Trial-Key: trial_YOUR_KEY"
# { zipcode: "100-0013", prefecture: "東京都", prefectureEn: "Tokyo", city: "千代田区", town: "霞が関", full: "東京都千代田区霞が関" }
Enter fullscreen mode Exit fullscreen mode

What Torify is

Torify packages all of the above into a single Japan locale API designed for AI agents:

  • 39 REST endpoints — wareki, invoice, corporate lookup, addresses, phone numbers, kanji↔kana, consumption tax, holidays, bank codes, coordinate conversion, and more.
  • 10 MCP tools — drop straight into Claude Desktop / Cursor / Cline / Continue.dev / Zed / Windsurf / Goose / 5ire.
  • 5 auth tiers — MCP (free) / Trial key (free) / Pro / Enterprise / x402 pay-per-call.

Every response uses the same envelope:

{ "ok": true, "data": { ... } }
// or
{ "ok": false, "error": { "code": "...", "message": "..." } }
Enter fullscreen mode Exit fullscreen mode

Three ways to try it right now

A) MCP server — 1 line of config, free, 100 req/day/IP

Works in every MCP-capable client: Claude Desktop, Cursor, Cline (VS Code), Continue.dev, Zed, Windsurf, Goose, 5ire. Just add one entry to your config:

{
  "mcpServers": {
    "torify": {
      "type": "http",
      "url": "https://torify-mcp.torify.workers.dev"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Rate limit (MCP): no authentication required, but limited to 100 req/day/IP. Ask Claude Desktop "what year is Reiwa 6 in the Gregorian calendar?" — that's a working smoke test.

Also listed on Smithery: smithery.ai/servers/endenibrk/torify.

B) Trial key — email signup, free, 100 calls/month

Same POST /v1/trial/signup as shown at the top (or use the LP form at torify.dev). Pass the returned key value as the X-Trial-Key header.

Rate limit (Trial): 100 calls/month, independent of the MCP day-limit. No credit card, no wallet.

curl "https://torify.dev/v1/wareki/convert?direction=g2w&date=2024-05-01" \
  -H "X-Trial-Key: trial_YOUR_KEY"
Enter fullscreen mode Exit fullscreen mode

C) x402 pay-per-call — $0.02/call, wallet only

x402 is an HTTP-native payment protocol on Base L2 (USDC). AI agents can pay autonomously, no API key signup needed:

import { wrapFetchWithPayment } from "x402-fetch";
import { createWalletClient, http } from "viem";
import { base } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const wallet = createWalletClient({
  account: privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`),
  chain: base,
  transport: http()
});
const fetch402 = wrapFetchWithPayment(fetch, wallet);

// The agent pays $0.02 USDC on every call, on its own
const res = await fetch402("https://torify.dev/v1/invoice/verify?number=T7000012050002");
const { ok, data } = await res.json();
Enter fullscreen mode Exit fullscreen mode

No subscription. No API key rotation. The agent handles payment end-to-end.


All 39 endpoints at a glance

Category Examples
Date & calendar Wareki conversion, public holidays, legal holidays, age & school year
Tax & corporate Invoice validate/verify (incl. bulk), corporate number lookup, consumption tax, eLTAX
Address & phone Postal → address, address normalization, phone validation
Text Kanji → kana, text normalization, name romanization (Hepburn) / split / validate
Payments & finance Bank codes, Zengin transfer validation, Japan Post (Yucho) conversion, 3D Secure
IDs & numbers Corporate number, My Number, passport, driver's license, insurance card
Geo & misc. Coordinates (WGS84 ↔ JGD2011, GSI), JSIC industry codes, license plates, JAN barcodes

Full spec: torify.dev/docs.


Design choices (the short version)

Why Cloudflare Workers? AI agents are distributed globally. Cloudflare's edge minimizes round-trips to Japan's NTA API while keeping latency low for agents anywhere in the world.

Why three protocols (MCP / A2A / x402)? MCP for today's Claude/Cursor users. A2A (Agent-to-Agent) for next-gen inter-agent communication. x402 for autonomous agent payment. Running all three in parallel means we can ride the protocol transition without breaking anyone.

The A2A Agent Card is already live: torify.dev/.well-known/agent.json.


Production-grade: verified against real inputs

Every endpoint was exercised against real production data before launch:

  • 1,535 automated tests passing (unit + integration + consistency)
  • 117 live curl checks on testnet (41 happy-path + 76 edge cases), 100% pass rate
  • 39 mainnet checks via Trial key, after URL-encoding — all passed
  • 16 bugs fixed from real-traffic detection plus code/security review

Two resilience features that came directly from real failure modes:

  • Mock fallback for external APIs. If the NTA corporate registry or Japan's GSI geocoding service returns 5xx, the endpoint still returns 200 with a cached/estimated result and confidence: 0.5. Your agent won't crash because a Japanese government API had a bad afternoon. Kanji-to-kana now runs on Cloudflare Workers AI (Llama 3.3 70B inference) — no external API dependency, zero ToS risk.
  • Forgiving input aliases. Coordinate conversion accepts wgs84-to-jgd2011, wgs84_jgd2011, and the canonical form interchangeably. Bank transfer type accepts ordinary, futsu, or 普通. When an LLM generates a semantically correct value with surface-level variance, we accept it instead of throwing.

What's next

  • Endpoint-specific pricing once traffic justifies it (external API endpoints like NTA cost more to run; kanji-to-kana stays flat because Workers AI absorbs the cost).
  • As the A2A ecosystem grows, positioning Torify as the Japan locale primitive that other agents can call.

Feedback welcome — which endpoint would you use first? Drop a comment.


Get started

Tier Limit How
MCP (free) 100 req/day/IP Add one entry to your MCP config
Trial (free) 100 calls/month Email signup at torify.dev
Pro 10,000 calls/month $49/mo
Enterprise 1,000,000 calls/month (Fair Use Policy) from $499/mo
x402 pay-per-call none $0.02/call, wallet only

torify.dev/pricing — no credit card required to start.


Torify: torify.dev | MCP: Smithery (100/100), MCP.so | x402: x402scan | A2A: a2aregistry.org, Agent Card

Author: Hiroki Sonoda · License: MIT

Top comments (0)