If you have ever tried to build a Korean fortune-telling or astrology app, you already know the dirty secret: the fun part is the interpretation, and the brutal part is the date math. Saju (사주), the Korean "Four Pillars of Destiny," is computed from a person's birth year, month, day, and hour. Sounds like four lookups. It is not.
I built and shipped a small HTTP API to do exactly this part for me, because I got tired of every Western reimplementation quietly returning the wrong pillars. This is a writeup of why the math is hard and how the API works.
Why Korean Saju date math is hard
The Four Pillars are four pairs of characters (Heavenly Stem + Earthly Branch), one pair for the year, month, day, and hour. To compute them correctly you have to handle three things that trip up almost every naive implementation:
The lunisolar calendar. Saju is rooted in a calendar that tracks both the moon and the sun. You cannot just do modular arithmetic on a Gregorian date. Solar to lunar conversion is table-driven and irregular, and getting a single month's leap-month placement wrong cascades into every downstream pillar.
The 24 solar terms (절기). The year and month pillars do not flip at midnight on January 1, or even on the lunar new year. They flip at solar-term boundaries. For example, the year pillar changes at 입춘 (around February 4), not at the calendar new year. If your birth date sits within a day or two of a solar term, an off-by-one in the boundary time gives you the wrong pillar entirely. These boundaries shift year to year and require real astronomical data, not a fixed calendar.
True solar time (진태양시). The hour pillar depends on the actual position of the sun, not the clock on the wall. Standard time zones and the equation of time both pull the wall clock away from solar noon, and the hour-branch boundaries land in the wrong two-hour slot if you skip the correction.
Get any one of these wrong and the output looks plausible but is simply incorrect, which is the worst kind of bug for a destiny app. Users who know their own Saju will notice immediately.
The API
The service is live at https://saju-api.pages.dev with five endpoints. It is also listed on RapidAPI as "Korean Saju Fortune Telling API" if you prefer to manage keys and billing there.
| Method | Endpoint | What it returns |
|---|---|---|
| POST | /v1/saju |
Four Pillars (8 characters), Five Elements counts, Day Master |
| POST | /v1/compatibility |
궁합 compatibility score, 0-100 |
| GET | /v1/daily |
Daily fortune (일진) |
| GET | /v1/usage |
Your current usage / quota |
| POST | /admin/issue-key |
Issue a free API key (100 calls/month) |
Auth is a single header: X-API-Key. If you call it through RapidAPI, use X-RapidAPI-Key instead.
There is no language model anywhere in the request path. The engine is fully deterministic, so the same birth input always returns the same pillars, latency is in the millisecond range, and your cost stays predictable instead of scaling with token counts.
Quick start
Issue yourself a free key, then call the core endpoint. Here is the raw curl:
# 1. Get a free key (100 calls/month)
curl -X POST https://saju-api.pages.dev/admin/issue-key
# 2. Compute the Four Pillars
curl -X POST https://saju-api.pages.dev/v1/saju \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"year":1990,"month":5,"day":15,"hour":10}'
The same call from JavaScript with fetch:
const res = await fetch("https://saju-api.pages.dev/v1/saju", {
method: "POST",
headers: {
"X-API-Key": process.env.SAJU_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ year: 1990, month: 5, day: 15, hour: 10 }),
});
const data = await res.json();
console.log(data.eight_characters); // "庚午 辛巳 庚辰 辛巳"
console.log(data.day_master); // "庚" (Metal / yang)
console.log(data.zodiac); // "말" (Horse)
For a birth of 1990-05-15 at hour 10, the verified response gives you:
{
"eight_characters": "庚午 辛巳 庚辰 辛巳",
"day_master": "庚",
"zodiac": "말",
"five_elements": {
"wood": 0,
"fire": 3,
"earth": 1,
"metal": 4,
"water": 0
}
}
That is everything most apps need to drive a reading: the eight characters for display, the Day Master (庚, yang Metal) as the anchor of the chart, the zodiac animal, and the Five Elements distribution. Here the chart is Metal-heavy (4) with no Wood or Water, which is exactly the kind of imbalance an interpretation layer keys off.
Free tier
Every key starts on the free tier: 100 calls per month, no card required. Hit POST /admin/issue-key, grab the key, and you are making real calls in under a minute. Check GET /v1/usage any time to see how much of the quota you have left. When you outgrow 100 calls, the RapidAPI listing handles the paid tiers.
Why this one is accurate
This is the whole reason the API exists. The manse-ryeok (만세력) engine, the lookup core that maps a calendar date to its pillars, was validated against the dataset from the Korea Astronomy and Space Science Institute (KASI), the national reference for Korean calendar conversions. The sweep covered 47,455 days with 0 failures: every solar to lunar conversion, every one of the 24 solar-term boundaries, and the true-solar-time adjustment matched the reference across that entire span.
That is the moat. Plenty of English and Western Saju or BaZi implementations are casual about solar terms and true solar time, so they drift exactly at the boundaries where it matters most. This engine was built against the authoritative dataset specifically so it does not.
To be precise about the claim: this is validated against the KASI dataset with 0 failures across 47,455 days. It is not a promise about your personal future. It is a promise that the date arithmetic underneath the reading is correct.
Try it
If you are building anything that touches Korean Saju, BaZi, or Four Pillars, stop reimplementing the calendar and let this handle it.
- Direct API and free key:
https://saju-api.pages.dev(callPOST /admin/issue-key) - RapidAPI listing: search for "Korean Saju Fortune Telling API"
Issue a key, send one birth date, and check the eight characters against a chart you trust. If the solar-term edges line up, you have found your backend.
Top comments (0)