Turn a birthdate into a Korean Four Pillars chart with one API call.

Korean Saju (사주 / Four Pillars of Destiny — Bazi in Chinese) reads a person’s birth moment as four columns of Heavenly Stems and Earthly Branches. This tutorial converts a plain birthdate into that full chart — pillars, Five Elements balance, Day Master, and zodiac animal — in a single REST call. Clean JSON, free tier, 10 languages.

Why convert a birthday into a chart

Many apps already collect a date of birth — at signup, on a profile, in a horoscope feature, or for a cultural personalization touch. The calculate endpoint turns that single field into a structured Saju chart you can store, display, or branch logic on. Because it is a deterministic calendar computation, the same birthdate always returns the same chart, so you compute it once and cache it forever.

The response is small, typed, and language-aware, which makes it easy to render a birth-chart card, seed a personality blurb, or feed downstream features such as compatibility scoring or a daily fortune loop.

Saju is a traditional cultural framework offered for entertainment and self-reflection. The chart is derived from the calendar — it is not a prediction and should not drive medical, financial, legal, or other consequential decisions.

1Get a free API key

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. Authenticate every call with the header X-API-Key: <your key>.

2Send a birthdate, get a chart

Post the solar birthdate as integers: year, month, day, an hour in 24-hour form, and gender as "M" or "F". Pass lang to localize the labels. The response is the four pillars plus a Five Elements count, the Day Master, and the zodiac animal.

curl -X POST https://saju-api.pages.dev/api/v1/calculate \
  -H "X-API-Key: sajuapi_free_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "year": 1990, "month": 5, "day": 15,
    "hour": 13, "gender": "M", "lang": "en"
  }'
async function chartFromBirthdate(birth) {
  const res = await fetch("https://saju-api.pages.dev/api/v1/calculate", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.SAJU_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ ...birth, lang: "en" }),
  });
  if (!res.ok) throw new Error("saju_api_" + res.status);
  return res.json();
}

const chart = await chartFromBirthdate({
  year: 1990, month: 5, day: 15, hour: 13, gender: "M",
});
console.log(chart.day_master.element); // "metal"
console.log(chart.zodiac);             // "horse"
import os, requests

def chart_from_birthdate(birth):
    r = requests.post(
        "https://saju-api.pages.dev/api/v1/calculate",
        headers={"X-API-Key": os.environ["SAJU_API_KEY"]},
        json={**birth, "lang": "en"},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()

chart = chart_from_birthdate(
    {"year": 1990, "month": 5, "day": 15, "hour": 13, "gender": "M"}
)
print(chart["day_master"]["element"])  # "metal"
print(chart["zodiac"])                 # "horse"

Example response

{
  "input": { "year": 1990, "month": 5, "day": 15, "hour": 13, "gender": "M", "lang": "en" },
  "pillars": {
    "year":  { "stem": "경", "branch": "오", "stem_hanja": "庚", "branch_hanja": "午" },
    "month": { "stem": "신", "branch": "사", "stem_hanja": "辛", "branch_hanja": "巳" },
    "day":   { "stem": "경", "branch": "진", "stem_hanja": "庚", "branch_hanja": "辰" },
    "hour":  { "stem": "계", "branch": "미", "stem_hanja": "癸", "branch_hanja": "未" }
  },
  "elements": { "wood": 0, "fire": 2, "earth": 2, "metal": 3, "water": 1 },
  "day_master": { "stem": "경", "element": "metal", "polarity": "yang" },
  "zodiac": "horse",
  "tier": "free",
  "remaining": 99
}

What each field means

FieldShapeMeaning
pillars4 objectsThe Year, Month, Day, and Hour pillars. Each has a Heavenly Stem (stem) and Earthly Branch (branch) in Korean, plus stem_hanja and branch_hanja for the Chinese characters.
elements5 integersHow many of the chart’s eight characters fall into each of the Five Elements: wood, fire, earth, metal, water. Useful for a balance bar or radar chart.
day_masterobjectThe Day Pillar’s stem — the chart’s anchor. Includes its element and polarity (yang or yin).
zodiacstringThe Earthly-Branch animal of the Year Pillar — the familiar twelve-animal sign (here horse), localized by lang.
remainingintegerRequests left in today’s quota for your key.

The day_master is the single most useful field for lightweight personalization: it is one of ten stems and pairs cleanly with the daily fortune endpoint, which keys off the Day Master.

When the birth time is unknown

Birth hour is often missing. Send hour: -1 and the endpoint computes the Year, Month, and Day pillars and returns "hour": null. The Five Elements counts and zodiac are still returned, so you get a usable chart even without a time.

curl -X POST https://saju-api.pages.dev/api/v1/calculate \
  -H "X-API-Key: sajuapi_free_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "year": 1985, "month": 11, "day": 3, "hour": -1, "gender": "F" }'

// → "pillars": { ..., "hour": null }, "zodiac": "ox"

Inputs are validated: year is 1920–2050, month 1–12, day 1–31, hour −1–23, and gender must be "M" or "F". Out-of-range values return a 400 with an invalid_input reason.

Compute once at signup, then cache

Because the chart is a pure function of the birthdate, the right pattern is to call the API once — when a user first provides their date of birth — and persist the result. You never need to recompute it for the same person.

// pseudo: enrich a user record on first save of birthdate
async function enrichUser(user) {
  if (user.sajuChart) return user;           // already cached
  const chart = await chartFromBirthdate({
    year: user.birthYear, month: user.birthMonth, day: user.birthDay,
    hour: user.birthHour ?? -1, gender: user.gender,
  });
  user.sajuChart = chart;                     // store; it never changes
  return await db.save(user);
}

A date of birth is personal data. Keep the API key on your server, call the API server-side, and store only what your privacy policy covers. The API is stateless — it does not retain the birthdates you send.

Go deeper, or localize

Need Ten Gods, hidden stems, the Yongshin (useful element), and the Daeun luck cycles instead of just the bare chart? Switch to the interpret endpoint, which accepts the same birthdate input and returns the full reading.

Pass lang on either endpoint to localize element and zodiac labels. Supported values include en, ko, ja, zh, es, pt, id, vi, th, and hi — 10 languages total.

Keep building

Building a relationship feature? See the dating app compatibility use case. Want a recurring engagement loop? See the daily fortune & horoscope API.