# 기회(GIHOE) — 사주 계산·완성 풀이 API (Korean Four Pillars: manseryeok calculation + finished readings) > 만세력 계산과 완성 풀이(기본 풀이·열한 가지 풀이·궁합·오늘의 기운)를 JSON으로 제공한다. 이미지·캐릭터·이야기는 없다(각 앱의 몫). 요청의 생년월일은 기록하지 않는다. ## Auth - Header: x-api-key: sk_... (or Authorization: Bearer) - No free key. Keys are issued on payment at gihoe.co.kr/pricing — one price: 29,900 KRW/month (VAT excluded), first month 50% counted from each company's contract date. plan 'basic' key = every bundle (the all-features key, also used for promotion/feature checks); plan 'select' key = chosen bundles only (features). POST /v1/keys/signup is closed in production (403 NO_FREE_KEY). - Bundles (V2): calc (manse/daeun/calendar/terms) and the short taste reading are in every key. Twelve optional bundles: reading (normal/long length + name), the eleven topics (overview, love, money, career, children, parents, relationships, friends, partner, elements, health), match, daily. A key that lacks a bundle answers 403 FEATURE_NOT_INCLUDED and the call is not counted. GET /v1/me lists features and usage.byFeature. Paying again with the same email extends the same key by one month (paidUntil). - Key rotation: POST /v1/keys/rotate with current key -> new key shown once, old key revoked immediately, monthly usage carried over. ## Response envelope - Success: {success:true, data:{...}, meta:{responseMs,version,plan}}. Failure: {success:false, error:{code,message}} - /v1/manse data keys: input, lunarDate, pillars{year,month,day,hour}, dayMaster, elements, assumptions. Each pillar: {ganji, ganjiKo, stem, stemKo, branch, branchKo, stemElement, tenGod, branchTenGod}. elements is an object keyed by 오행 name: {"목":0,"화":1,"토":3,"금":1,"수":3} ## Endpoints (v1, fields are stable: additive only) - GET/POST /v1/manse?date=YYYY-MM-DD&time=HH:MM four pillars(hanja+korean), ten gods, element counts, day master, lunar date. Options: detail=full(hidden stems, 12 stages, sinsal, napeum, gongmang, daeun; isFemale required), trueSolarTime+longitude, isLunar input (isLeapMonth REQUIRED with it — unknown leap-month is a 400, never assumed false; lunar dates are validated by lunar month length, not Gregorian rules) - GET/POST /v1/reading?date=YYYY-MM-DD&time=HH:MM FINISHED READING, not raw material. Returns (a) judgments — body strength with per-slot evidence and a percentile against a 150,000-chart distribution, useful god, pattern, element imbalance in plain words, ten-god profile; (b) 'outline', seven ready-to-render sections; (c) 'prose', the finished Korean reading itself — title, screen summary, per-block paragraphs each carrying its basis, stated limits, and a full-text version. The consuming app does not have to write any reading text. Every reading/match response also carries an 'events' array — the monthly event slot. Empty most of the time; when we run a seasonal feature (e.g. year-end fortune) it appears there with badge/title/summary/blocks and disappears after its 'until' date. Build the slot once; nothing else changes on your side. Options: tone=talk|calm|plain, length=short|normal|long — wording changes, judgments and basis do not. normal/long length and name need the 'reading' bundle (all-features keys have it). Built by rules from the computed chart, not by a language model: the same birth data always yields the same text. No fortune claims, no timing predictions, nothing invented beyond what the chart shows. - GET/POST /v1/daeun?date=&time=&isFemale= ten 10-year luck pillars - GET /v1/calendar/convert?direction=solar-to-lunar|lunar-to-solar&year=&month=&day=&isLeapMonth= 1901..2099 - GET /v1/solar-terms?year= 24 solar terms (KST instants) - GET /v1/day-pillar?date= day ganji - GET /v1/calendar/month?year=&month= full month table - POST /v1/reading {date, time?, isLunar?, isLeapMonth?, isFemale?, length?: short|normal|long, tone?: talk|calm|plain, name?, category?} full reading. category = overview (나의 성향) | love (연애) | money (돈·재물) | career (직장·사업) | children (가족·자녀) | parents (부모) | relationships (친구·인간관계) | friends (친구) | partner (배우자) | elements (오행) | health (건강) — eleven topics, all ready, any plan. Adds data.category: {categoryId, label, version (topic wording version), summary {title, lines[]}, blocks[4]{id, heading, sentences[], basis[]}, forbidden[]}. partner reads the spouse star by gender when isFemale is given (female=관성, male=재성) and reads both stars when it is absent; it never states timing, the partner's identity, or divorce. elements splits the five-element balance into four blocks (tally, crowded element, empty element + useful god, season) with no lucky colors, numbers or directions. health never names an illness, a body part, a diagnosis, a treatment or a lifespan: it reads how this chart spends and refills energy (pace, where strain shows, how it recovers) and says in the text itself that it is not medical information. parents reads the month pillar as the parent seat and the supporting element (인성) as what was handed down; it never states whether the parents are alive, their character, their health or lifespan, and it never judges whether one 'had good parents'. parents and children are different readings and cannot substitute for each other: children reads the whole family, parents reads only the seat above. Every topic id is now ready; an unknown id → 400 VALIDATION_ERROR. One call per topic. focus? (2026-09-23): the customer's own answer to one situation question, accepted only by love (crush|some|dating|single), friends (hurt|newbond|drift|curious) and parents (unspoken|apart|friction|health); none or omitted = the regular reading. It never changes the chart calculation or the summary — it only turns the angle of the opening line and the strength line, and the reply echoes data.category.focus {key, label, question} (null when absent). Unknown value → 400. - GET/POST /v1/daily?date=YYYY-MM-DD&time=HH:MM&on=YYYY-MM-DD TODAY'S ENERGY (daily fortune, 0 calls on a full key). How that day's pillar meets this chart: the day stem's ten-god toward the day master, the day branch against the natal day branch (clash/combine/same), the twelve-stage of the day master on that branch, and whether that element group is the useful god. Returns summary {title, lines[]} + blocks[3]{id, heading, sentences[], basis[]} + forbidden[]. 'on' defaults to today in Korea (1901..2099). Same person + same day always yields the same text. No good/bad day verdicts, no lucky numbers or colors. Needs the 'daily' bundle. Counts 0 calls on a full key. - GET/POST /v1/match two-person compatibility. POST {a:{date, time?, isFemale?, name?}, b:{...}, length?} or GET ?dateA&timeA&dateB&timeB(&nameA&nameB&length). Finished prose (title, summary, blocks, table, limits) + judgments (dayStems, daySeats, support, fills, strengthPair, ties). By policy NO score and NO verdict (data.policy says so). Same tone/length/plan gates as reading. Counts 2 calls. Carries events; the per-key minAge rule applies to both people. - Call counting: /v1/reading short without category = 0 (taste, free forever; per-minute limit still applies) · normal/long = 1 · with category = 1 per topic · /v1/match = 2 · /v1/daily = 0 on a full key (included in the monthly contract; a per-bundle tally is still kept) — what it costs inside a selected key is agreed case by case. - Minors: every reading carries minor {under14, under19, asOf} (Korean age, no number) so the app can show consent screens (under 14: legal guardian consent for personal data; under 19: guardian consent for payment). Optional per-key rule POST /v1/me/policy {minAge: 0|14|19} — below it, 403 AGE_RESTRICTED and the call is not counted. Default 0 (family readings must stay possible). - GET /v1/me key: plan, features (bundles in this key), featuresKo, paidUntil, usage{month,count,byFeature,people} (billing unit is the CALL: one /v1/readings bundle = 1 call no matter how many readings are inside. byFeature and people are always 0 - per-reading counts stay inside the factory), events, policy. POST /v1/me/events {enabled:boolean} turn the events slot on/off per key (default on). GET /v1/health. GET /v1/openapi.json OpenAPI 3 spec - https://gihoe.co.kr/guide INTEGRATION GUIDE (Korean, human-readable): getting a key, first call, the connect kit, screen flow, every endpoint, response shape, the eleven topics, call counting, error handling, per-company settings, caching, versioning, and a seven-item done checklist. - POST /v1/chances internal-scope keys only (external keys get 403 FORBIDDEN by design). Option sections=true adds a supply-status contract block "reading" (contentStatus=basis_only, text always null — no finished prose is supplied, only rule-derived basis) ## Semantics AI should know - Timezone fixed UTC+9. date+time without trueSolarTime = wall clock. Missing time -> no hour pillar (pillars.hour = null, six-character element counts); noon is used only for solar-term resolution, assumptions.assumedNoon=true - Boundary honesty: assumptions.termBoundary/dayBoundary/hourBoundary flag births near solar-term/midnight/hour edges where schools differ. daeun.startAgeBoundary similar - lunarDate is null outside 1901..2099 with a note. Historic clock changes ARE applied and flagged via assumptions.historicClockApplied: UTC+8:30 eras 1908-04-01..1911-12-31 and 1954-03-21..1961-08-09 (+30min), DST 1948-60/1987-88 (-60min); wall-clock input is auto-converted to KST - Errors: codes: VALIDATION_ERROR(400), API_KEY_REQUIRED/INVALID_API_KEY/KEY_REVOKED/KEY_EXPIRED(401), QUOTA_EXHAUSTED(402), FORBIDDEN/PLAN_REQUIRED/AGE_RESTRICTED/FEATURE_NOT_INCLUDED/NO_FREE_KEY(403), NOT_FOUND/CATEGORY_NOT_READY(404), ALREADY_PENDING(409), PAYLOAD_TOO_LARGE(413), UNSUPPORTED_MEDIA_TYPE(415), RATE_LIMITED(429), INTERNAL_ERROR(500) - Rate headers: X-RateLimit-Remaining (per-minute window, resets each minute — no Retry-After header; wait to the next minute), X-Monthly-Remaining - Quiet degradations to check: manse detail=full WITHOUT isFemale returns daeun:null (no error). Missing time -> NO hour pillar is produced (pillars.hour = null) and element counts are 6-char, not 8. Noon is used only to resolve the solar term (year/month pillar); the day pillar comes straight from the calendar date and dayBoundary/hourBoundary stay false. assumptions.assumedNoon=true. - Verification: 764k synthetic profiles cross-checked vs two independent calculators (0 defects found on our side); ongoing full sweep vs KASI official lunar data. Reports in the repo docs. ## Integration - JS SDK (sdk/js), Python SDK (sdk/python/saju_api.py, stdlib only), MCP server (mcp/server.mjs, 5 tools) ship in the source repository; public download links will be posted at launch. The REST API above needs no SDK. - Contract: v1 fields never removed/renamed; breaking changes go to /v2 with 6-month overlap - Versions: meta.version (API), data.readingVersion, data.proseVersion (base prose wording), data.category.version (topic wording), data.matchVersion. Wording revisions bump these numbers; field shapes stay. Same input + same versions = same text (rule-built, no language model).