Korean Saju (사주 / Four Pillars of Destiny — Bazi in Chinese) has been used for marriage and relationship matching for centuries. This tutorial wires a 0–100 compatibility score into your app with a single REST call. Send two birthdates, get back a match number and a breakdown. Free tier, clean JSON, 10 languages.
Match percentages drive engagement. A profile that reads “87% compatible” gets opened, screenshotted, and shared far more than a plain list. In Korea, Saju-based gunghap (궁합) compatibility is a familiar, trusted framing for exactly this. The endpoint below turns two birthdates into a deterministic number you can surface on a profile card, a swipe screen, or a daily “best match” push.
The score is computed from real Four Pillars structure — not random — so the same two people always return the same result, which keeps your UI stable and cacheable.
Keys are self-serve. Post an email and you get a free-tier key instantly. The key is shown once — store it as a server-side secret and never ship it in client code.
curl -X POST https://saju-api.pages.dev/api/v1/keys/create \
-H "Content-Type: application/json" \
-d '{ "email": "dev@yourcompany.io" }'
const res = await fetch("https://saju-api.pages.dev/api/v1/keys/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "dev@yourcompany.io" })
});
const { api_key } = await res.json();
// store api_key server-side as a secret
import requests
r = requests.post(
"https://saju-api.pages.dev/api/v1/keys/create",
json={"email": "dev@yourcompany.io"},
)
api_key = r.json()["api_key"] # keep this server-side
Free tier returns 100 requests/day at 1 request/second — enough to build, test,
and run a small launch. Authenticate every call with the header X-API-Key: <your key>.
Send each person’s solar birthdate (and birth hour if you have it). Use
gender of "M" or "F", and hour as a 24-hour integer
(use -1 when the birth time is unknown). The response is a 0–100 score
plus a breakdown.
curl -X POST https://saju-api.pages.dev/api/v1/compatibility \
-H "X-API-Key: sajuapi_free_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"person_a": { "year": 1990, "month": 5, "day": 15, "hour": 13, "gender": "M" },
"person_b": { "year": 1992, "month": 9, "day": 20, "hour": 8, "gender": "F" },
"lang": "en"
}'
async function compatibility(a, b) {
const res = await fetch("https://saju-api.pages.dev/api/v1/compatibility", {
method: "POST",
headers: {
"X-API-Key": process.env.SAJU_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ person_a: a, person_b: b, lang: "en" }),
});
if (!res.ok) throw new Error("saju_api_" + res.status);
return res.json();
}
const match = await compatibility(
{ year: 1990, month: 5, day: 15, hour: 13, gender: "M" },
{ year: 1992, month: 9, day: 20, hour: 8, gender: "F" }
);
console.log(match.score); // e.g. 75
import os, requests
def compatibility(a, b):
r = requests.post(
"https://saju-api.pages.dev/api/v1/compatibility",
headers={"X-API-Key": os.environ["SAJU_API_KEY"]},
json={"person_a": a, "person_b": b, "lang": "en"},
timeout=10,
)
r.raise_for_status()
return r.json()
match = compatibility(
{"year": 1990, "month": 5, "day": 15, "hour": 13, "gender": "M"},
{"year": 1992, "month": 9, "day": 20, "hour": 8, "gender": "F"},
)
print(match["score"]) # e.g. 75
{
"score": 75,
"breakdown": {
"element_balance": 25,
"day_master_relation": 30,
"branch_harmony": 0,
"branch_clash": 0
},
"person_a": { "day_master": "경", "day_master_element": "metal", "zodiac": "말" },
"person_b": { "day_master": "기", "day_master_element": "earth", "zodiac": "원숭이" },
"tier": "free",
"remaining": 98
}
The total score is the sum of four signals. You can show only the headline number, or
expose the breakdown as “why you matched” copy on a profile.
| Field | Range | Meaning |
|---|---|---|
element_balance | 0–25 | How evenly the two charts’ Five Elements combine. Higher = a more balanced pair. |
day_master_relation | 5–30 | The relationship between each person’s Day Master element — generating cycles score highest. |
branch_harmony | 0–25 | Earthly Branch harmonies (six-harmony, three-harmony) between the charts. |
branch_clash | negative | Penalty when branches clash. A flat tension signal you can surface as “needs work.” |
A common pattern: for a viewer, score them against each candidate, then sort the feed by score.
Cache results — the score for a fixed pair never changes — so you only call the API once per
unique pairing.
// pseudo-feed: rank candidates for one viewer
const ranked = [];
for (const candidate of candidates) {
const cacheKey = pairKey(viewer.birth, candidate.birth);
let m = cache.get(cacheKey);
if (!m) {
m = await compatibility(viewer.birth, candidate.birth);
cache.set(cacheKey, m); // result is deterministic — cache forever
}
ranked.push({ candidate, score: m.score });
}
ranked.sort((x, y) => y.score - x.score);
Birthdates are personal data. Keep the API key on your server, call the API server-side, and store only what your privacy policy covers. The API itself is stateless — it does not retain the birthdates you send.
Pass lang to get localized element and zodiac labels for international users. Supported values
include en, ko, ja, zh, es, pt,
id, vi, th, and hi — 10 languages total, so a single
integration serves a global user base.
Need the raw Four Pillars instead of a score? See the
interpret endpoint for Ten Gods, Yongshin, and luck cycles,
or the quickstart for the calculate endpoint.
Want a daily engagement loop? See the
daily fortune & horoscope API use case.