1. 무엇을 드리는지 — 오 분 요약
다른 사주 API는 대개 계산값까지 줍니다. 여덟 글자를 받아도 화면에 쓸 글이 없어서, 결국 그 글을 직접 써야 합니다. 기회는 사람이 읽는 완성 문장까지 줍니다.
| 드리는 것 | 내용 |
|---|---|
| 계산 | 만세력(년·월·일·시주)·24절기·일진·한 달 표·대운·음양력 변환. 1901~2099년 |
| 완성 풀이 | 제목·요약·본문 여러 칸·문단마다 근거·한계. 길이 셋, 말투 셋, 이름 부르기 |
| 열한 가지 풀이 | 나의 성향·연애·돈재물·직장사업·가족자녀·부모·친구인간관계·친구·배우자·오행·건강 |
| 궁합 | 두 사람을 함께 읽는 글 |
| 오늘의 기운 | 그날의 일진이 이 사주에 어떻게 닿는지. 매일 열어 볼 거리 |
| 붙이기 키트 | 연결 코드·응답 견본·검사. 이 문서의 예시가 전부 그 키트입니다 |
규칙으로 조립합니다. 생성 모델을 쓰지 않아서 같은 생년월일이면 언제나 같은 글이 나옵니다. 글에 숫자를 쓰지 않고, 길흉과 시기를 정해 말하지 않습니다. 판단마다 근거가 함께 옵니다.
2. 키 받기
요금 페이지에서 결제하시면 그 자리에서 키가 나옵니다. 사람이 확인하고 연락드리는 절차가 없습니다. 심사도 서류도 없습니다.
| 무엇 | 내용 |
|---|---|
| 단가 | 정가 29,900원/월(부가세 별도). 계약일부터 첫 한 달 50% — 기간 행사가 아니라 회사마다 첫 달입니다 |
| 전체 키 | 기능 전부가 들어 있는 키 |
| 맞춤 키 | 고른 묶음만 들어 있는 키. 예를 들어 연애 풀이와 궁합만. 값은 전체와 같습니다 |
| 연장 | 같은 이메일로 다시 결제하면 같은 키가 한 달 늘어납니다. 키를 바꿔 끼울 필요가 없습니다 |
| 무료 체험 키 | 없습니다 |
맞춤 키를 쓰실 때, 키에 없는 기능을 부르면 403 FEATURE_NOT_INCLUDED 가 오고 그 호출은 요금에서 세지 않습니다. 키에 무엇이 들어 있는지는 GET /v1/me 의 features 로 확인하시고, 없는 기능은 화면에서 숨기시면 됩니다.
3. 첫 호출 한 줄
키를 받으셨으면 이것부터 찍어 보십시오. 맛보기 풀이는 호출로 세지 않습니다.
curl -H "x-api-key: sk_live_당신의키" "https://api.gihoe.co.kr/v1/reading?date=1990-03-15&time=07:20"
돌아오는 봉투는 늘 같은 모양입니다.
{
"success": true,
"data": { ... },
"meta": { "version": "0.3.5", "responseMs": 12, "plan": "basic" }
}
실패도 같은 모양입니다. success:false 와 error{code,message} 가 옵니다. 코드는 10절에 전부 있습니다.
4. 붙이기 키트 설치 — 파일 두 개, 환경변수 두 개
키트는 결제 뒤 발급 페이지 문의로 드립니다. 폴더를 그대로 복사해 넣으시면 됩니다.
kits/gihoe-connect/ client.mjs 기회 API 부르기 (그대로 씁니다) to-app.mjs 응답을 앱 자료형으로 (회사마다 여기만 고칩니다) kit.test.mjs 검사 — 서버 없이 견본으로 돕니다 fixtures/ 실제 응답 견본 (가상 인물)
GIHOE_API_ORIGIN=https://api.gihoe.co.kr GIHOE_API_KEY=sk_live_...
import { createGihoeConnect } from './kits/gihoe-connect/client.mjs';
import { readingToCopy, readingSections, categoryToCopy, dailyToCopy,
eventPopups, readingHolds, readingCacheKey } from './kits/gihoe-connect/to-app.mjs';
const gihoe = createGihoeConnect({
baseUrl: process.env.GIHOE_API_ORIGIN,
apiKey: process.env.GIHOE_API_KEY,
});
// entry = 앱이 저장한 출생정보 그대로
// { birthDate:'1990-03-15', calendarType:'solar'|'lunar', birthTime:'07:20'|'unknown',
// isLeapMonth:false, gender:'female'|'male'|'unspecified' }
const { data, quota } = await gihoe.reading(entry, { length: 'normal', name: '지영' });
const copy = readingToCopy(data); // 화면 글감 여섯 칸
const sections = readingSections(data); // 제목 있는 단락들node --test kits/gihoe-connect/kit.test.mjs
5. 화면 흐름 만들기
실제로 파는 앱이 쓰는 순서입니다. 그대로 따라 하셔도 되고, 필요한 단계만 쓰셔도 됩니다.
| 단계 | 부르는 것 | 화면 | 셈 |
|---|---|---|---|
| 1. 들어오기 | /v1/reading 맛보기(length=short) | 짧은 풀이를 공짜로 보여 주고 관심을 끕니다 | 0회 |
| 2. 결제 | (앱의 결제) | 풀이 하나 또는 전체를 파는 자리 | — |
| 3. 본문 | /v1/reading?length=normal | 제목·요약·본문·근거·한계 | 1회 |
| 4. 풀이 | /v1/reading?category=money | 고른 분야 하나 | 풀이당 1회 |
| 5. 매일 | /v1/daily | 앱을 다시 열 이유 | 전체 키 0회 |
| 6. 둘이서 | /v1/match | 궁합 | 2회 |
맛보기가 0회인 이유는 분명합니다. 앱의 유입 화면이 앱의 비용이 되면 안 되기 때문입니다.
6. 부를 수 있는 것 전부
| 주소 | 무엇 | 주는 값 |
|---|---|---|
GET/POST /v1/reading | 완성 풀이 글 | length=short|normal|long, tone=talk|calm|plain, name, category |
GET/POST /v1/match | 궁합 글 | dateA timeA dateB timeB 또는 본문 {a,b} |
GET/POST /v1/daily | 오늘의 기운 글 | on=2026-09-20(비우면 오늘) |
GET/POST /v1/manse | 만세력 계산 | detail=full 이면 지장간·십이운성·신살까지 |
GET/POST /v1/daeun | 대운 | isFemale 필요(순행·역행이 갈립니다) |
GET /v1/calendar/convert | 음양력 변환 | date, isLunar, isLeapMonth |
GET /v1/solar-terms | 24절기 | year |
GET /v1/me | 내 키 상태 | 요금제·들어 있는 묶음·이번 달 사용량·사람 수·설정 |
POST /v1/support | 문의 | 키 없이도 됩니다. 이메일로 답합니다 |
GET /v1/openapi.json | 기계용 명세 | OpenAPI 3 |
입력은 쿼리로도 본문으로도 보낼 수 있습니다. 날짜는 YYYY-MM-DD, 시각은 HH:MM. 시각을 모르면 비워 두십시오 — 시주를 만들지 않고 여섯 글자로 계산하며(pillars.hour 가 null), 응답의 assumptions.assumedNoon 으로 밝힙니다. 음력이면 isLunar=true 와 함께 isLeapMonth 를 반드시 주셔야 합니다(윤달을 마음대로 추정하지 않습니다).
7. 응답 모양과 화면에 넣는 법
풀이 글은 어디서나 같은 모양입니다. 이 모양만 알면 화면을 한 번만 만들면 됩니다.
{
"prose": {
"title": "제목 한 줄",
"summary": ["요약 문장", "..."],
"blocks": [
{ "id": "foundation", "heading": "타고난 바탕",
"sentences": ["문장", "문장"],
"basis": ["일간 기(己)", "오행 토"] },
...
],
"limits": ["이 풀이가 말하지 않는 것", "..."]
},
"minor": { "under14": false, "under19": false },
"events": [ ... ],
"readingVersion": "1.0.0", "proseVersion": "1.2.0"
}
키트의 readingToCopy(data) 를 쓰면 화면 글감 여섯 칸으로 바뀝니다.
| 칸 | 무엇을 넣나 |
|---|---|
heading | 큰 제목 |
core | 먼저 보이는 요약 |
scenarios[] | 펼쳐 보는 본문들 |
reflection | 돌아볼 것 |
action | 살릴 것 |
limit | 이 풀이가 말하지 않는 것 — 화면에서 지우지 마십시오 |
basis 와 limits 는 "왜 그렇게 읽었는지"와 "무엇을 말하지 않는지"입니다. 이것을 지우면 단정하는 글로 보이고, 환불과 항의가 거기서 생깁니다. 접어 두셔도 좋으니 지우지는 마십시오.8. 열한 가지 풀이
/v1/reading 에 category 를 더하면 그 분야 풀이가 data.category 로 함께 옵니다. 모양은 전부 같습니다 — 요약 + 네 문단 + 문단마다 근거 + 금지선.
| id | 이름 | 무엇을 읽나 |
|---|---|---|
overview | 나의 성향 | 타고난 바탕·기운의 세기·균형·선택의 결 |
love | 연애 | 다가가는 방식·곁 자리·살릴 것·흔들리기 쉬운 때 |
money | 돈·재물 | 들어오는 길·버는 것과 남는 것·지킬 것·위험 |
career | 직장·사업 | 일하는 결·조직 안의 자리·살릴 것·어긋나는 자리 |
children | 가족·자녀 | 기운이 실린 자리·돌보는 방식·살릴 것·부딪히는 자리 |
parents | 부모 | 부모 자리(월주)·받은 결(인성)·살릴 것·어긋나기 쉬운 자리 |
friends | 친구 | 끌리는 친구·우정 속 내 자리·서운해지는 순간·오래 가는 사이 (말투 '~해요') |
relationships | 친구·인간관계 | 사람을 대하는 결·둘레·살릴 것·어긋나는 때 |
partner | 배우자 | 배우자 별·배우자 자리·함께 사는 방식·어긋나기 쉬운 때 |
elements | 오행 | 다섯 기운의 셈·몰린 기운·빈 기운과 살릴 기운·계절 |
health | 건강 | 힘을 쓰는 결·무리하기 쉬운 자리·채우는 방식·오래 가게 |
배우자는 isFemale 을 주시면 명리 관행대로 별을 잡고(여성은 관성, 남성은 재성), 안 주시면 추정하지 않고 두 별을 나란히 읽습니다. 결혼 시기·상대의 정체·이혼은 말하지 않습니다.
건강은 병명·증상·진단·치료·처방·몸의 부위·수명을 말하지 않습니다. 기운을 쓰고 채우는 결만 읽고, 의학 정보가 아니라는 문장을 글 안에 넣어 보냅니다. 그 문장도 화면에 그대로 두십시오.
부모는 태어난 달의 기둥을 부모 자리로, 받쳐 주는 기운을 받은 결로 읽습니다. 부모가 계신지 안 계신지·어떤 분인지·건강·수명은 말하지 않고, '부모덕이 있다 없다' 같은 판정도 하지 않습니다. 가족·자녀(children)와는 다른 글입니다 — 가족은 가족 전체를, 부모는 위에서 내려온 자리 하나만 읽습니다. 그래서 두 풀이를 서로 대신 쓸 수 없습니다.
친구(friends)는 나란한 자리(비겁) 하나만 읽습니다. relationships(친구·인간관계)가 사람 전반을 대하는 결을 읽는 것과 달라 서로 대신 쓸 수 없습니다 — 같은 사람에게서 두 글은 한 문장도 겹치지 않습니다. 말투가 '~해요'이고 분량이 1,100~1,300자로 고정입니다.
목록에 없는 아이디를 보내시면 400 VALIDATION_ERROR 로 옵니다.
이름은 바꿔 쓰셔도 됩니다. 화면에서 '부모'를 '부모운'이라 부르고 싶으시면 categoryToCopy(c, { labels: { parents: '부모운' } }) 처럼 주십시오. 글은 한 글자도 바뀌지 않고 이름표만 바뀝니다. 아이디(categoryId)는 그대로라 저장해 두신 결과와도 어긋나지 않습니다.
9. 궁합 · 오늘의 기운
궁합
const { data } = await gihoe.match(a, b, { length: 'normal' });
const copy = matchToCopy(data); // 같은 글감 모양
const table = matchTable(data); // 숫자 없이 막대만
두 사람의 기운이 어떻게 만나는지를 글로 드립니다. 점수로 등수를 매기지 않습니다. 호출은 2회로 셉니다(두 사람이니까요).
오늘의 기운
const { data } = await gihoe.daily(entry); // 오늘
const { data: d20 } = await gihoe.daily(entry, { on: '2026-09-20' });
const today = dailyToCopy(data); // { date, dayGanji, heading, core, sections[], action, limit }
그날의 일진이 이 사주에 어떻게 닿는지를 세 문단으로 드립니다. 하루를 좋다·나쁘다로 판정하지 않고, 무엇을 하라·피하라 지시하지 않고, 행운의 색·숫자·방향을 말하지 않습니다. 같은 사람 같은 날이면 언제나 같은 글이라 화면에 캐시하기 좋습니다. 전체 키에서는 호출로 세지 않습니다 — 매일 여는 거리이기 때문입니다.
10. 호출 셈과 한도
| 부르는 것 | 셈 |
|---|---|
맛보기 풀이(short, 풀이 없음) | 0회 |
| 보통·긴 풀이 | 1회 |
| 풀이 하나 | 1회 |
| 궁합 | 2회 |
| 오늘의 기운 | 0회(전체 키) |
| 계산(만세력·대운·음양력·절기) | 0회 |
| 키에 없는 묶음 · 연령 기준에 걸린 요청 | 세지 않음 |
남은 횟수는 응답 헤더 X-Monthly-Remaining 으로 옵니다. 기본 한도는 월 2,000건, 분당 30회입니다. 한도를 넘으면 추가 요금 대신 차단됩니다 — 예상 못 한 청구서가 나올 일이 없습니다. 분당 한도에 걸리면 429가 오고 다음 분에 저절로 풀립니다.
11. 오류가 왔을 때 앱이 할 일
| 상태 | 코드 | 뜻 | 앱이 할 일 |
|---|---|---|---|
| 400 | VALIDATION_ERROR | 입력이 잘못됨(없는 날짜, 모르는 풀이 아이디 등) | 입력 화면으로 돌려보냅니다. 음력이면 윤달 여부를 물으십시오 |
| 401 | API_KEY_REQUIRED / INVALID_API_KEY | 키가 없거나 틀림 | 서버 환경변수를 확인하십시오 |
| 401 | KEY_EXPIRED | 이용 기간이 지남 | 같은 이메일로 결제하면 같은 키가 연장됩니다 |
| 402 | QUOTA_EXHAUSTED | 이번 달 한도를 다 씀 | 다음 달까지 기다리거나 한도를 올리십시오. 화면에는 "잠시 뒤 다시"로 |
| 403 | FEATURE_NOT_INCLUDED | 이 키에 없는 묶음 | 그 기능은 화면에서 숨기십시오. 호출은 안 셉니다 |
| 403 | AGE_RESTRICTED | 회사가 정한 연령 기준 미만 | 보호자 확인 화면으로. 호출은 안 셉니다 |
| 429 | RATE_LIMITED | 분당 한도 | 잠깐 쉬었다 다시. 다음 분에 풀립니다 |
| 500 | INTERNAL_ERROR | 우리 잘못 | 한 번만 다시 불러 보시고, 계속되면 문의 주십시오 |
키트의 client.mjs 는 이 오류들을 앱이 화면에 그대로 쓸 수 있는 말로 바꿔 줍니다. 요금제·키 이야기 같은 운영자용 설명은 따로 operatorHint 에 담아 두어 고객에게 보이지 않습니다.
12. 회사별 설정 — 연령 기준, 이벤트 칸
연령 기준
모든 응답에 minor{under14,under19} 표시가 함께 옵니다(숫자 없이 참·거짓). 아예 막고 싶으시면 한 번만 정해 두십시오.
POST /v1/me/policy { "minAge": 19 } // 0(막지 않음·기본) · 14 · 19
기준 미만이면 403 AGE_RESTRICTED 가 오고 호출도 세지 않습니다. 기본이 0인 이유는 부모가 자녀 사주를 보는 경우 때문입니다.
이벤트 칸 — 매달 새 기능
응답의 events[] 에 그 달의 거리가 담겨 옵니다(예: 추석에 "올해 남은 넉 달"). 우리가 배포하면 붙어 있는 모든 앱이 같은 날 함께 받습니다. 앱은 eventPopups(data.events) 로 띄우기만 하면 되고, 원하지 않으시면 한 번에 끕니다.
POST /v1/me/events { "enabled": false }
13. 몇 사람이 썼는지 보기
GET /v1/me → data.usage = { month, count, byFeature, people }
계약 단위는 '호출'입니다. usage.count 하나만 보시면 됩니다 — 여러 풀이를 /v1/readings 로 묶어 부르면 풀이가 셋이든 다섯이든 호출 1회입니다. 묶음 하나가 곧 호출 하나입니다. 받으신 묶음을 나눠 쓰시든 쪼개 쓰시든 그건 쓰시는 쪽 몫이고, 저희 셈은 늘 1입니다. usage.byFeature 와 usage.people 은 늘 0입니다 — 계약이 호출 단위라 따로 세실 것이 없습니다. 아래는 그 전 설명입니다. 생년월일은 저장하지 않고, 비밀값과 그 키의 지문을 섞은 되돌릴 수 없는 표식으로만 세기 때문에 원래 값을 복원할 수 없고, 회사가 다르면 같은 사람도 다른 표식이라 회사끼리 맞춰 볼 수 없습니다. 달이 바뀌면 새로 셉니다.
14. 저장·캐시·재시도
같은 사람·같은 옵션이면 언제나 같은 글이 나오므로, 캐시하기 아주 쉽습니다. 키트의 readingCacheKey(entry, options) 가 저장 열쇠를 만들어 줍니다.
const key = readingCacheKey(entry, { length: 'normal', name });
const saved = await db.get(key);
const { data } = saved ?? await gihoe.reading(entry, { length: 'normal', name });
재시도는 500과 429에만 하십시오. 400·401·403은 다시 불러도 같은 답이 옵니다. 시간 제한은 키트가 기본 10초로 잡아 둡니다.
출생 시각을 모르는 손님에게는 readingHolds(data, entry) 가 HOLD_BIRTH_TIME_UNKNOWN 같은 표시를 돌려줍니다. 모르는 것은 모른다고 화면에 적는 편이 신뢰를 남깁니다.
15. 버전과 호환 약속
| 칸 | 무엇 |
|---|---|
meta.version | API 버전 |
data.readingVersion | 계산·판정 규칙 |
data.proseVersion | 기본 풀이 문장 |
data.category.version | 풀이 문장 |
data.matchVersion · data.dailyVersion | 궁합·오늘의 기운 문장 |
필드를 말없이 지우지 않습니다. 모양을 바꿔야 하면 60일 전에 알려 드립니다. 문장이 좋아지면 위 번호가 올라갑니다 — 번호가 같으면 글도 같습니다. 새 기능은 늘 더하기로 들어오므로, 붙여 두신 코드가 새 기능 때문에 깨지지 않습니다.
16. 붙이기 마무리 점검표
이 일곱 개가 되면 붙은 것입니다.
- 서버 환경변수 두 개가 들어갔고, 키가 브라우저·앱 안에는 없다
- 맛보기 풀이가 화면에 뜬다(호출이 안 세지는 것도
/v1/me로 확인) - 본문 풀이에 근거와 한계가 함께 보인다
- 풀이 하나가 열리고, 키에 없는 풀이는 화면에서 숨겨진다
- 없는 날짜·윤달 미지정 같은 잘못된 입력이 입력 화면으로 돌아간다
- 결과를 앱이 저장해 두었다가 다시 보여 준다
node --test kits/gihoe-connect/kit.test.mjs가 전부 통과한다