{
  "openapi": "3.1.1",
  "info": {
    "title": "four-pillars-for-agents — 사주 만세력 계산 API",
    "version": "1.3.0",
    "description": "출생 기록(그레고리력 민간 시각, 시간대, 동경 경도)과 계산 정책을 받아 **사주팔자**(연·월·일·시주), 선택적 **대운**, 선택적 **파생 정보**(십신·지장간), **계산 근거**(provenance)와 **경계 근접도·불확실성 후보**를 반환한다.\n\n이 문서(`contracts/openapi.json`)가 계약의 source of truth다. 서버는 요청 본문을 이 문서의 JSON Schema(draft 2020-12)로 검증하고, 계약 테스트는 실제 응답을 같은 스키마로 검증한다.\n\n## 엔드포인트\n\n| 메서드·경로 | 용도 | 인증 |\n|---|---|---|\n| `POST /v1/charts` | 사주·대운·파생 정보 계산 | `X-API-Key` |\n| `GET /v1/solar-terms?year=` | 연도별 24절기 조회 | `X-API-Key` |\n| `GET /v1/lunar-months?lunar_year=` | 음력 연도의 월 목록 | `X-API-Key` |\n| `GET /v1/convert?calendar=&date=` | 양력↔음력 날짜 변환과 일진 | `X-API-Key` |\n| `GET /v1/ganzhi?date=` | 날짜의 일진·연주·월주와 절 전환 | `X-API-Key` |\n| `GET /v1/policies` | 승인된 정책·기본값·가용 데이터 조회 | `X-API-Key` |\n| `GET /health/live` | 생존 확인 | 없음 |\n| `GET /health/ready` | 준비 상태 확인 | 없음 |\n\n## 인증과 권한\n\n- 모든 `/v1/*` 호출은 `X-API-Key` 헤더가 필요하다. `/health/*`는 인증하지 않는다.\n- 키는 운영 CLI(`keyctl issue`)로 발급하며 `sk_` 접두의 무작위 문자열이다. 서버는 키의 SHA-256 다이제스트만 보관한다.\n- 키가 없거나, 길이가 35~128자 범위를 벗어나거나, 등록되지 않은 키면 `401 INVALID_API_KEY`를 반환한다. 폐기(revoked)된 키는 `403 QUOTA_FORBIDDEN`이다. 키 파일 변경(발급·폐기)은 폴링(기본 30초)으로 반영된다.\n- **권한 모델**: 별도의 scope나 role은 없다. 활성(active) 키면 모든 `/v1/*` 엔드포인트를 호출할 수 있다. 키마다 tenant와 요청률 한도가 지정된다.\n\n## 레이트 리밋과 부하 제어\n\n- 키별 토큰 버킷을 쓴다. `keyctl` 기본 발급값은 초당 20회, 버스트 40이며 키마다 다를 수 있다. 인스턴스가 여러 개면 한도를 인스턴스 수로 나눠 인스턴스별로 적용한다.\n- 한도를 넘으면 `429 RATE_LIMITED`와 `Retry-After`(정수 초, 1 이상) 헤더를 반환한다. 레이트 리밋은 인증에 성공한 요청에만 적용된다.\n- `POST /v1/charts`, `GET /v1/convert`, `GET /v1/ganzhi`는 동시 계산 수 한도를 함께 쓴다. 한도가 차면 약 50ms 기다린 뒤 `503 SERVICE_OVERLOADED`를 반환한다. 계산 시한(기본 2초)을 넘기면 `503 CALCULATION_TIMEOUT`이다.\n\n## 멱등성\n\n모든 엔드포인트는 서버 상태를 바꾸지 않는 순수 조회·계산이다. 동일한 정규화 요청, `dataset_id`, `policy_id`, 엔진 버전이면 결정적 필드가 모두 같다(`request_id`만 매 요청 다르다). 따라서 `POST /v1/charts`도 네트워크 오류·429·503 후에 그대로 재시도해도 안전하며 `Idempotency-Key` 헤더는 필요 없다.\n\n## 캐시\n\n모든 응답(오류·헬스 포함)에 `Cache-Control: no-store`와 `X-Content-Type-Options: nosniff`가 붙는다. 계산 응답에는 출생 정보가 담기므로 중간 프록시·브라우저·클라이언트 캐시에 저장하지 않는다.\n\n## 재현성\n\n- 서버의 활성 데이터 번들은 교체될 수 있지만, 한 번 발행된 `dataset_id`·`policy_id`의 내용은 바뀌지 않는다.\n- 같은 결과를 다시 얻으려면 응답의 `provenance.dataset_id`와 `provenance.policy_id`를 다음 요청에 명시해 고정한다. 해당 ID가 서버에서 제거되면 `404 DATASET_NOT_FOUND` / `POLICY_NOT_FOUND`를 반환하며 다른 데이터로 조용히 대체하지 않는다.\n- 정책은 데이터 번들에 포함되어 번들별로 불변이다. `dataset_id`를 고정하면 그 번들이 가진 정책으로 계산한다.\n- 생략한 계산 옵션은 모두 `provenance.resolved_policy`에 펼쳐 반환된다. `provenance.calculation_fingerprint`(HMAC-SHA256)로 두 계산의 조건이 같은지 비교할 수 있다.\n\n## 개인정보\n\n- 출생 정보가 URL과 액세스 로그에 남지 않도록 명식 계산은 POST 본문으로만 받는다. `GET /v1/convert`·`GET /v1/ganzhi`는 날짜만 쿼리로 받는 조회이며, 그 날짜는 게이트웨이 로그·마켓플레이스 분석·브라우저 기록에 남을 수 있다.\n- 서버는 요청 본문, 쿼리 값, 헤더를 로그·트레이스·메트릭에 기록하지 않고 저장하지도 않는다. 요청 로그에는 `request_id`, 메서드, 라우트, 상태 코드, 소요 시간, trace ID만 남고, 오류 로그에도 요청 내용 없이 `request_id`와 오류 유형만 남는다.\n- 오류 `message`는 코드별 고정 문구이고 `details`에는 필드 경로와 원인 키워드만 담는다. 요청 값을 되돌려 보내지 않는다.\n\n## 공통 요청·응답 헤더\n\n- 요청 `X-API-Key`: `/v1/*` 필수.\n- 요청 `Content-Type: application/json`: `POST /v1/charts` 필수(`; charset=utf-8` 같은 파라미터 허용).\n- 요청 `traceparent`(선택): W3C Trace Context. 서버 트레이스를 호출자 트레이스에 잇는다.\n- 응답 `X-Request-Id`: 서버가 요청마다 새로 발급하는 32자리 소문자 16진수. 오류 본문의 `request_id`, 계산 응답의 `request_id`와 같다. 운영 문의 시 이 값을 전달한다. 클라이언트가 보낸 값은 쓰지 않는다.\n- 응답 `Content-Type: application/json`, `Cache-Control: no-store`, `X-Content-Type-Options: nosniff`.\n- `405 METHOD_NOT_ALLOWED` 응답에는 허용 메서드를 알리는 `Allow` 헤더가 붙는다.\n\n## 오류 형식\n\n모든 오류는 `Error` 스키마 `{code, message, request_id, details[]}`로 반환한다(RFC 7807 형식이 아니다). 클라이언트는 `code`로 분기하고 `message`는 표시용으로만 쓴다. 한 요청에는 먼저 걸린 오류 하나만 반환한다.\n\n| HTTP | code | 의미 | 조치 |\n|---|---|---|---|\n| 400 | `MALFORMED_JSON` | JSON 구문 오류, 중복 키, 후행 데이터 | 본문 수정 |\n| 401 | `INVALID_API_KEY` | 키 누락·형식 오류·미등록 | 키 확인 |\n| 403 | `QUOTA_FORBIDDEN` | 폐기된 키 | 허용 범위·계약 확인 |\n| 404 | `DATASET_NOT_FOUND` / `POLICY_NOT_FOUND` | 서버에 없는 dataset_id / policy_id | `/v1/policies`의 가용 ID 지정 |\n| 404 | `NOT_FOUND` | 정의되지 않은 경로 | 경로 확인 |\n| 405 | `METHOD_NOT_ALLOWED` | 경로는 있으나 메서드가 다름 | `Allow` 헤더의 메서드 사용 |\n| 413 | `PAYLOAD_TOO_LARGE` | 본문 16 KiB 초과 | 본문 축소 |\n| 415 | `UNSUPPORTED_MEDIA_TYPE` | `Content-Type`이 application/json이 아님 | 헤더 수정 |\n| 422 | `INVALID_INPUT` | 스키마·필드 검증 실패, 정의되지 않은 필드·파라미터 | `details`의 필드 수정 |\n| 422 | `UNSUPPORTED_RANGE` | 지원 연도 범위(1900~2100) 밖 | 범위 수정 |\n| 422 | `UNSUPPORTED_CALENDAR` | gregorian 외 달력 | gregorian 사용 |\n| 422 | `NONEXISTENT_LOCAL_TIME` | DST·오프셋 전환으로 존재하지 않는 시각 | 기록 확인(서버가 임의로 1시간 옮기지 않음) |\n| 422 | `AMBIGUOUS_LOCAL_TIME` | 두 번 존재하는 시각인데 fold 미지정 | `details`의 두 후보 중 fold 선택 |\n| 422 | `INVALID_FOLD` | 중복이 아닌 시각에 fold=1 | fold 제거 |\n| 422 | `CONFLICTING_OPTIONS` | 명시 방향(forward/reverse)과 sex_for_rule 동시 지정 | 둘 중 하나 제거 |\n| 422 | `DAEWOON_SEX_REQUIRED` | by_year_stem_and_sex인데 sex_for_rule 누락 | 성별 규칙 입력 또는 명시 방향 선택 |\n| 422 | `UNCERTAINTY_TOO_WIDE` | 불확실성 범위의 후보가 32개 초과 | 범위 축소 |\n| 429 | `RATE_LIMITED` | 키별 요청률 초과 | `Retry-After` 이후 재시도 |\n| 500 | `INTERNAL_ERROR` | 내부 오류 | `request_id`로 운영 문의 |\n| 503 | `DATASET_UNAVAILABLE` / `RULESET_UNAVAILABLE` | 필요한 데이터·승인 규칙표 없음 | 복구 후 재시도(근사값 대체 없음) |\n| 503 | `SERVICE_OVERLOADED` / `CALCULATION_TIMEOUT` | 부하 차단 / 계산 시한 초과 | 잠시 후 재시도 |\n\n## 지원 범위와 제한\n\n- 출생 민간 연도 1900~2100, 시간대 `Asia/Seoul`, 경도 124~132°E, 위도 33~39°N, 입력 시각 초 00~59.\n- 요청 본문 최대 16 KiB, 계산 시한 기본 2초.\n\n## 버전\n\n`/v1`은 이 계약의 범위다. 계산 방법이 바뀌면 새로운 불변 `policy_id`로 식별하고, 계약 문서 버전은 `info.version`으로 관리한다.",
    "summary": "한국 사주(四柱)·대운을 결정적으로 계산하고 계산 근거를 함께 돌려주는 만세력 API"
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/v1/charts": {
      "post": {
        "operationId": "calculateChart",
        "summary": "사주·대운 계산",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChartRequest"
              },
              "examples": {
                "nominal_with_daewoon": {
                  "summary": "진태양시·순행 대운(±60초 불확실성)",
                  "description": "`contracts/example-request.json`에서 `include_derived`만 false로 바꾼 요청이다. 200 응답 예시 `nominal_with_daewoon`이 이 요청의 실제 결과다.",
                  "value": {
                    "birth": {
                      "calendar": "gregorian",
                      "local_datetime": "2000-01-07T12:00:00",
                      "timezone": "Asia/Seoul",
                      "longitude": 126.978,
                      "latitude": 37.5665,
                      "uncertainty_seconds": 60,
                      "longitude_uncertainty_degrees": 0.01
                    },
                    "policy_id": "kr-solar-v1",
                    "options": {
                      "clock_basis": "local_apparent_solar",
                      "day_boundary": "midnight"
                    },
                    "daewoon": {
                      "direction": "forward",
                      "count": 10,
                      "display_rounding": "half_up"
                    },
                    "include_derived": false
                  }
                },
                "minimal": {
                  "summary": "필수 필드만",
                  "description": "birth의 필수 필드 4개만 보낸다. 정책·옵션은 `kr-solar-v1` 기본값을 쓰고, 대운과 파생 정보는 계산하지 않는다. 200 응답 예시 `minimal`의 요청이다.",
                  "value": {
                    "birth": {
                      "calendar": "gregorian",
                      "local_datetime": "1990-05-15T08:30:00",
                      "timezone": "Asia/Seoul",
                      "longitude": 127.0
                    }
                  }
                },
                "boundary_sensitive": {
                  "summary": "입춘 경계 근처(±600초, 민간시)",
                  "description": "2000년 입춘(2000-02-04 21:40:22 KST) 직전이다. 불확실성 범위가 입춘에 걸려 후보가 2개 나온다. 200 응답 예시 `boundary_sensitive`의 요청이다.",
                  "value": {
                    "birth": {
                      "calendar": "gregorian",
                      "local_datetime": "2000-02-04T21:40:00",
                      "timezone": "Asia/Seoul",
                      "longitude": 126.978,
                      "uncertainty_seconds": 600
                    },
                    "options": {
                      "clock_basis": "civil"
                    }
                  }
                },
                "daewoon_by_rule": {
                  "summary": "성별 규칙 대운·야자시 분리",
                  "description": "`direction=by_year_stem_and_sex`이므로 `sex_for_rule`이 필수다. 연간 음양과 성별로 순행·역행을 정한다. `split_zi`는 23시 이후 출생의 일주는 그대로 두고 시간 천간만 다음 날 기준으로 계산한다.",
                  "value": {
                    "birth": {
                      "calendar": "gregorian",
                      "local_datetime": "1985-11-03T23:40:00",
                      "timezone": "Asia/Seoul",
                      "longitude": 129.0756,
                      "latitude": 35.1796
                    },
                    "options": {
                      "day_boundary": "split_zi"
                    },
                    "daewoon": {
                      "direction": "by_year_stem_and_sex",
                      "sex_for_rule": "female",
                      "count": 8,
                      "display_rounding": "floor"
                    }
                  }
                },
                "ambiguous_with_fold": {
                  "summary": "서머타임 종료로 중복된 시각(fold=1)",
                  "description": "1987-10-11 03:00 KDT에 시계가 02:00으로 돌아가 02:30이 두 번 있었다. fold를 생략하면 422 AMBIGUOUS_LOCAL_TIME이 되고, 여기서는 나중(표준시 UTC+9)을 뜻하는 fold=1을 지정했다.",
                  "value": {
                    "birth": {
                      "calendar": "gregorian",
                      "local_datetime": "1987-10-11T02:30:00",
                      "timezone": "Asia/Seoul",
                      "longitude": 126.978,
                      "fold": 1
                    }
                  }
                },
                "with_derived": {
                  "summary": "파생 정보 요청(include_derived=true)",
                  "description": "`contracts/example-request.json`과 같다. 정책의 규칙표가 승인된 경우에만 200이다. 현재 번들의 규칙표 `kr-core-v1`은 승인 상태라 파생 정보(십신·지장간)를 반환한다. 지장간은 현대 통용표(여기·중기·본기)이며 연해자평·삼명통회 원문과 다른 곳이 있다. 규칙표가 승인되지 않은 번들에서는 503 RULESET_UNAVAILABLE이다(`GET /v1/policies`의 `derived_available`로 미리 확인).",
                  "value": {
                    "birth": {
                      "calendar": "gregorian",
                      "local_datetime": "2000-01-07T12:00:00",
                      "timezone": "Asia/Seoul",
                      "longitude": 126.978,
                      "latitude": 37.5665,
                      "uncertainty_seconds": 60,
                      "longitude_uncertainty_degrees": 0.01
                    },
                    "policy_id": "kr-solar-v1",
                    "options": {
                      "clock_basis": "local_apparent_solar",
                      "day_boundary": "midnight"
                    },
                    "daewoon": {
                      "direction": "forward",
                      "count": 10,
                      "display_rounding": "half_up"
                    },
                    "include_derived": true
                  }
                }
              }
            }
          },
          "description": "계산할 출생 기록과 옵션(`ChartRequest`). UTF-8 JSON 객체, 최대 16 KiB. 정의되지 않은 필드는 422, 중복 키는 400이다."
        },
        "responses": {
          "200": {
            "description": "계산 성공. 명목(nominal) 결과(`pillars`, `basis`, `daewoon`, `derived`)와 계산 근거(`provenance`), 시간 해석(`time`), 불확실성 후보(`uncertainty`)를 반환한다. 응답은 결정적이다(`request_id` 제외).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChartResponse"
                },
                "examples": {
                  "nominal_with_daewoon": {
                    "summary": "진태양시·순행 대운",
                    "description": "요청 예시 `nominal_with_daewoon`을 로컬 서버에서 실행해 받은 실제 응답이다. 지면을 줄이려고 `daewoon.periods`와 후보의 `daewoon.periods`는 10개 중 앞 3개만 남겼다. 그 외 값은 원본 그대로다.",
                    "value": {
                      "request_id": "37004717dbf8c99b2c1d5ca3fc57daf1",
                      "provenance": {
                        "engine_id": "four-pillars-for-agents/dev+skyfield-1.55+de440s",
                        "dataset_id": "kr-2026c-de440s-d9750dac2a",
                        "policy_id": "kr-solar-v1",
                        "ruleset_id": "kr-core-v1",
                        "tzdb_id": "2026c",
                        "ephemeris_sha256": "c1c7feeab882263fc493a9d5a5b2ddd71b54826cdf65d8d17a76126b260a49f2",
                        "timescale_model_id": "iers-finals-mjd61293+smh2016",
                        "resolved_policy": {
                          "policy_id": "kr-solar-v1",
                          "clock_basis": "local_apparent_solar",
                          "day_boundary": "midnight",
                          "year_boundary": "lichun_instant",
                          "month_boundary": "jie_instant",
                          "solar_definition": "geocentric_apparent_of_date",
                          "pre_1972_mode": "civil_ut1_approx",
                          "future_utc_mode": "hold_last_announced_tai_minus_utc",
                          "term_equality": "inclusive_zero",
                          "daewoon_duration_scale": "TT",
                          "daewoon_start_method": "mean_gregorian_year",
                          "year_length_days": "365.2425",
                          "display_rounding": "half_up",
                          "daewoon_count": 10,
                          "hemisphere_rule": "no_shift",
                          "ruleset_id": "kr-core-v1",
                          "include_derived": false,
                          "requested_daewoon_direction": "forward",
                          "sex_for_rule": null
                        },
                        "calculation_fingerprint": "2bbcf9ed5b2b2ac677efb7f8daeeefcaff3b124e208782f1d0b89ee9304b376f",
                        "fingerprint_key_id": "fp-1"
                      },
                      "time": {
                        "civil_label": "2000-01-07T12:00:00",
                        "timezone": "Asia/Seoul",
                        "total_offset_seconds": 32400,
                        "dst_offset_seconds": 0,
                        "fold": 0,
                        "instant": {
                          "tt_us": "486064184000",
                          "utc": "2000-01-07T03:00:00.000000Z",
                          "reference_label": "2000-01-07T03:00:00.000000",
                          "reference_scale": "UTC",
                          "time_quality": "observed"
                        },
                        "clock_label": "2000-01-07T11:22:03.218357",
                        "clock_basis": "local_apparent_solar",
                        "equation_of_time_seconds": -351.853545,
                        "dut1_seconds": 0.351902,
                        "longitude_correction_seconds": -1925.28,
                        "total_clock_correction_seconds": -2276.781643
                      },
                      "pillars": {
                        "year": {
                          "cycle_index": 15,
                          "stem_index": 5,
                          "branch_index": 3,
                          "hangul": "기묘",
                          "hanja": "己卯"
                        },
                        "month": {
                          "cycle_index": 13,
                          "stem_index": 3,
                          "branch_index": 1,
                          "hangul": "정축",
                          "hanja": "丁丑"
                        },
                        "day": {
                          "cycle_index": 0,
                          "stem_index": 0,
                          "branch_index": 0,
                          "hangul": "갑자",
                          "hanja": "甲子"
                        },
                        "hour": {
                          "cycle_index": 6,
                          "stem_index": 6,
                          "branch_index": 6,
                          "hangul": "경오",
                          "hanja": "庚午"
                        }
                      },
                      "basis": {
                        "lichun": {
                          "term_id": "1999-21",
                          "term_index": 21,
                          "name": "입춘",
                          "longitude_degrees": 315,
                          "is_jie": true,
                          "month_ordinal": 0,
                          "instant": {
                            "tt_us": "-28616513803801",
                            "utc": "1999-02-04T06:57:02.012199Z",
                            "reference_label": "1999-02-04T06:57:02.012199",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "current_jie": {
                          "term_id": "2000-19",
                          "term_index": 19,
                          "name": "소한",
                          "longitude_degrees": 285,
                          "is_jie": true,
                          "month_ordinal": 11,
                          "instant": {
                            "tt_us": "392505046448",
                            "utc": "2000-01-06T01:00:40.862448Z",
                            "reference_label": "2000-01-06T01:00:40.862448",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "next_jie": {
                          "term_id": "2000-21",
                          "term_index": 21,
                          "name": "입춘",
                          "longitude_degrees": 315,
                          "is_jie": true,
                          "month_ordinal": 0,
                          "instant": {
                            "tt_us": "2940086864513",
                            "utc": "2000-02-04T12:40:22.680513Z",
                            "reference_label": "2000-02-04T12:40:22.680513",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "day_date": "2000-01-07",
                        "day_jdn": 2451551,
                        "hour_basis_day_date": "2000-01-07"
                      },
                      "daewoon": {
                        "direction": "forward",
                        "basis_term": {
                          "term_id": "2000-21",
                          "term_index": 21,
                          "name": "입춘",
                          "longitude_degrees": 315,
                          "is_jie": true,
                          "month_ordinal": 0,
                          "instant": {
                            "tt_us": "2940086864513",
                            "utc": "2000-02-04T12:40:22.680513Z",
                            "reference_label": "2000-02-04T12:40:22.680513",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "delta_us": "2454022680513",
                        "start_age_years": {
                          "numerator": "818007560171",
                          "denominator": "86400000000"
                        },
                        "display_age": 9,
                        "display_rounding": "half_up",
                        "synthetic_360_age": {
                          "years": 9,
                          "months": 5,
                          "days": {
                            "numerator": "4407560171",
                            "denominator": "240000000"
                          }
                        },
                        "start_method": "mean_gregorian_year",
                        "start": {
                          "tt_us": "299257190479756",
                          "utc": "2009-06-26T02:58:44.295756Z",
                          "reference_label": "2009-06-26T02:58:44.295756",
                          "reference_scale": "UTC",
                          "time_quality": "observed"
                        },
                        "periods": [
                          {
                            "index": 1,
                            "pillar": {
                              "cycle_index": 14,
                              "stem_index": 4,
                              "branch_index": 2,
                              "hangul": "무인",
                              "hanja": "戊寅"
                            },
                            "start_age_years": {
                              "numerator": "818007560171",
                              "denominator": "86400000000"
                            },
                            "end_age_years": {
                              "numerator": "1682007560171",
                              "denominator": "86400000000"
                            },
                            "start": {
                              "tt_us": "299257190479756",
                              "utc": "2009-06-26T02:58:44.295756Z",
                              "reference_label": "2009-06-26T02:58:44.295756",
                              "reference_scale": "UTC",
                              "time_quality": "observed"
                            },
                            "end": {
                              "tt_us": "614826710479756",
                              "utc": "2019-06-26T13:10:41.295756Z",
                              "reference_label": "2019-06-26T13:10:41.295756",
                              "reference_scale": "UTC",
                              "time_quality": "observed"
                            }
                          },
                          {
                            "index": 2,
                            "pillar": {
                              "cycle_index": 15,
                              "stem_index": 5,
                              "branch_index": 3,
                              "hangul": "기묘",
                              "hanja": "己卯"
                            },
                            "start_age_years": {
                              "numerator": "1682007560171",
                              "denominator": "86400000000"
                            },
                            "end_age_years": {
                              "numerator": "2546007560171",
                              "denominator": "86400000000"
                            },
                            "start": {
                              "tt_us": "614826710479756",
                              "utc": "2019-06-26T13:10:41.295756Z",
                              "reference_label": "2019-06-26T13:10:41.295756",
                              "reference_scale": "UTC",
                              "time_quality": "observed"
                            },
                            "end": {
                              "tt_us": "930396230479756",
                              "utc": "2029-06-25T23:22:41.295756Z",
                              "reference_label": "2029-06-25T23:22:41.295756",
                              "reference_scale": "UTC_PROVISIONAL",
                              "time_quality": "predicted"
                            }
                          },
                          {
                            "index": 3,
                            "pillar": {
                              "cycle_index": 16,
                              "stem_index": 6,
                              "branch_index": 4,
                              "hangul": "경진",
                              "hanja": "庚辰"
                            },
                            "start_age_years": {
                              "numerator": "2546007560171",
                              "denominator": "86400000000"
                            },
                            "end_age_years": {
                              "numerator": "3410007560171",
                              "denominator": "86400000000"
                            },
                            "start": {
                              "tt_us": "930396230479756",
                              "utc": "2029-06-25T23:22:41.295756Z",
                              "reference_label": "2029-06-25T23:22:41.295756",
                              "reference_scale": "UTC_PROVISIONAL",
                              "time_quality": "predicted"
                            },
                            "end": {
                              "tt_us": "1245965750479756",
                              "utc": "2039-06-26T09:34:41.295756Z",
                              "reference_label": "2039-06-26T09:34:41.295756",
                              "reference_scale": "UTC_PROVISIONAL",
                              "time_quality": "predicted"
                            }
                          }
                        ]
                      },
                      "derived": null,
                      "uncertainty": {
                        "boundary_status": "stable",
                        "input_seconds": 60,
                        "instant_error_bound_seconds": 0,
                        "clock_error_bound_seconds": 0.013686,
                        "nearest_boundaries": [
                          {
                            "kind": "lichun",
                            "distance_seconds": 2454022.680513,
                            "description": "nearest 입춘 instant (TT)"
                          },
                          {
                            "kind": "jie",
                            "distance_seconds": 93559.137552,
                            "description": "nearest 절 instant (TT)"
                          },
                          {
                            "kind": "day",
                            "distance_seconds": 40923.218357,
                            "description": "day pillar boundary on the calculation clock"
                          },
                          {
                            "kind": "hour",
                            "distance_seconds": 1323.218357,
                            "description": "hour branch boundary on the calculation clock"
                          },
                          {
                            "kind": "timezone_transition",
                            "distance_seconds": 354880800,
                            "description": "nearest civil offset transition"
                          }
                        ],
                        "candidates": [
                          {
                            "pillars": {
                              "year": {
                                "cycle_index": 15,
                                "stem_index": 5,
                                "branch_index": 3,
                                "hangul": "기묘",
                                "hanja": "己卯"
                              },
                              "month": {
                                "cycle_index": 13,
                                "stem_index": 3,
                                "branch_index": 1,
                                "hangul": "정축",
                                "hanja": "丁丑"
                              },
                              "day": {
                                "cycle_index": 0,
                                "stem_index": 0,
                                "branch_index": 0,
                                "hangul": "갑자",
                                "hanja": "甲子"
                              },
                              "hour": {
                                "cycle_index": 6,
                                "stem_index": 6,
                                "branch_index": 6,
                                "hangul": "경오",
                                "hanja": "庚午"
                              }
                            },
                            "daewoon": {
                              "direction": "forward",
                              "basis_term": {
                                "term_id": "2000-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "2940086864513",
                                  "utc": "2000-02-04T12:40:22.680513Z",
                                  "reference_label": "2000-02-04T12:40:22.680513",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "delta_us": "2454022680513",
                              "start_age_years": {
                                "numerator": "818007560171",
                                "denominator": "86400000000"
                              },
                              "display_age": 9,
                              "display_rounding": "half_up",
                              "synthetic_360_age": {
                                "years": 9,
                                "months": 5,
                                "days": {
                                  "numerator": "4407560171",
                                  "denominator": "240000000"
                                }
                              },
                              "start_method": "mean_gregorian_year",
                              "start": {
                                "tt_us": "299257190479756",
                                "utc": "2009-06-26T02:58:44.295756Z",
                                "reference_label": "2009-06-26T02:58:44.295756",
                                "reference_scale": "UTC",
                                "time_quality": "observed"
                              },
                              "periods": [
                                {
                                  "index": 1,
                                  "pillar": {
                                    "cycle_index": 14,
                                    "stem_index": 4,
                                    "branch_index": 2,
                                    "hangul": "무인",
                                    "hanja": "戊寅"
                                  },
                                  "start_age_years": {
                                    "numerator": "818007560171",
                                    "denominator": "86400000000"
                                  },
                                  "end_age_years": {
                                    "numerator": "1682007560171",
                                    "denominator": "86400000000"
                                  },
                                  "start": {
                                    "tt_us": "299257190479756",
                                    "utc": "2009-06-26T02:58:44.295756Z",
                                    "reference_label": "2009-06-26T02:58:44.295756",
                                    "reference_scale": "UTC",
                                    "time_quality": "observed"
                                  },
                                  "end": {
                                    "tt_us": "614826710479756",
                                    "utc": "2019-06-26T13:10:41.295756Z",
                                    "reference_label": "2019-06-26T13:10:41.295756",
                                    "reference_scale": "UTC",
                                    "time_quality": "observed"
                                  }
                                },
                                {
                                  "index": 2,
                                  "pillar": {
                                    "cycle_index": 15,
                                    "stem_index": 5,
                                    "branch_index": 3,
                                    "hangul": "기묘",
                                    "hanja": "己卯"
                                  },
                                  "start_age_years": {
                                    "numerator": "1682007560171",
                                    "denominator": "86400000000"
                                  },
                                  "end_age_years": {
                                    "numerator": "2546007560171",
                                    "denominator": "86400000000"
                                  },
                                  "start": {
                                    "tt_us": "614826710479756",
                                    "utc": "2019-06-26T13:10:41.295756Z",
                                    "reference_label": "2019-06-26T13:10:41.295756",
                                    "reference_scale": "UTC",
                                    "time_quality": "observed"
                                  },
                                  "end": {
                                    "tt_us": "930396230479756",
                                    "utc": "2029-06-25T23:22:41.295756Z",
                                    "reference_label": "2029-06-25T23:22:41.295756",
                                    "reference_scale": "UTC_PROVISIONAL",
                                    "time_quality": "predicted"
                                  }
                                },
                                {
                                  "index": 3,
                                  "pillar": {
                                    "cycle_index": 16,
                                    "stem_index": 6,
                                    "branch_index": 4,
                                    "hangul": "경진",
                                    "hanja": "庚辰"
                                  },
                                  "start_age_years": {
                                    "numerator": "2546007560171",
                                    "denominator": "86400000000"
                                  },
                                  "end_age_years": {
                                    "numerator": "3410007560171",
                                    "denominator": "86400000000"
                                  },
                                  "start": {
                                    "tt_us": "930396230479756",
                                    "utc": "2029-06-25T23:22:41.295756Z",
                                    "reference_label": "2029-06-25T23:22:41.295756",
                                    "reference_scale": "UTC_PROVISIONAL",
                                    "time_quality": "predicted"
                                  },
                                  "end": {
                                    "tt_us": "1245965750479756",
                                    "utc": "2039-06-26T09:34:41.295756Z",
                                    "reference_label": "2039-06-26T09:34:41.295756",
                                    "reference_scale": "UTC_PROVISIONAL",
                                    "time_quality": "predicted"
                                  }
                                }
                              ]
                            },
                            "daewoon_start_age_min": {
                              "numerator": "19171575629",
                              "denominator": "2025000000"
                            },
                            "daewoon_start_age_max": {
                              "numerator": "1227041840257",
                              "denominator": "129600000000"
                            },
                            "basis": {
                              "lichun": {
                                "term_id": "1999-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "-28616513803801",
                                  "utc": "1999-02-04T06:57:02.012199Z",
                                  "reference_label": "1999-02-04T06:57:02.012199",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "current_jie": {
                                "term_id": "2000-19",
                                "term_index": 19,
                                "name": "소한",
                                "longitude_degrees": 285,
                                "is_jie": true,
                                "month_ordinal": 11,
                                "instant": {
                                  "tt_us": "392505046448",
                                  "utc": "2000-01-06T01:00:40.862448Z",
                                  "reference_label": "2000-01-06T01:00:40.862448",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "next_jie": {
                                "term_id": "2000-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "2940086864513",
                                  "utc": "2000-02-04T12:40:22.680513Z",
                                  "reference_label": "2000-02-04T12:40:22.680513",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "day_date": "2000-01-07",
                              "day_jdn": 2451551,
                              "hour_basis_day_date": "2000-01-07"
                            },
                            "reason_codes": [
                              "NOMINAL"
                            ]
                          }
                        ],
                        "warnings": []
                      }
                    }
                  },
                  "minimal": {
                    "summary": "필수 필드만(대운·파생 없음)",
                    "description": "요청 예시 `minimal`의 실제 응답이다. `daewoon`과 `derived`는 null이다.",
                    "value": {
                      "request_id": "5c452e7210485cff9ee3225a712345f2",
                      "provenance": {
                        "engine_id": "four-pillars-for-agents/dev+skyfield-1.55+de440s",
                        "dataset_id": "kr-2026c-de440s-d9750dac2a",
                        "policy_id": "kr-solar-v1",
                        "ruleset_id": "kr-core-v1",
                        "tzdb_id": "2026c",
                        "ephemeris_sha256": "c1c7feeab882263fc493a9d5a5b2ddd71b54826cdf65d8d17a76126b260a49f2",
                        "timescale_model_id": "iers-finals-mjd61293+smh2016",
                        "resolved_policy": {
                          "policy_id": "kr-solar-v1",
                          "clock_basis": "local_apparent_solar",
                          "day_boundary": "midnight",
                          "year_boundary": "lichun_instant",
                          "month_boundary": "jie_instant",
                          "solar_definition": "geocentric_apparent_of_date",
                          "pre_1972_mode": "civil_ut1_approx",
                          "future_utc_mode": "hold_last_announced_tai_minus_utc",
                          "term_equality": "inclusive_zero",
                          "daewoon_duration_scale": "TT",
                          "daewoon_start_method": "mean_gregorian_year",
                          "year_length_days": "365.2425",
                          "display_rounding": "half_up",
                          "daewoon_count": 10,
                          "hemisphere_rule": "no_shift",
                          "ruleset_id": "kr-core-v1",
                          "include_derived": false,
                          "requested_daewoon_direction": null,
                          "sex_for_rule": null
                        },
                        "calculation_fingerprint": "41d810477d2aaca0ce123e0abeb838c8bd9ad506599eec063f8a33a9c1d3de0a",
                        "fingerprint_key_id": "fp-1"
                      },
                      "time": {
                        "civil_label": "1990-05-15T08:30:00",
                        "timezone": "Asia/Seoul",
                        "total_offset_seconds": 32400,
                        "dst_offset_seconds": 0,
                        "fold": 0,
                        "instant": {
                          "tt_us": "-304000142816000",
                          "utc": "1990-05-14T23:30:00.000000Z",
                          "reference_label": "1990-05-14T23:30:00.000000",
                          "reference_scale": "UTC",
                          "time_quality": "observed"
                        },
                        "clock_label": "1990-05-15T08:01:42.593229",
                        "clock_basis": "local_apparent_solar",
                        "equation_of_time_seconds": 222.54781,
                        "dut1_seconds": 0.045419,
                        "longitude_correction_seconds": -1920,
                        "total_clock_correction_seconds": -1697.406771
                      },
                      "pillars": {
                        "year": {
                          "cycle_index": 6,
                          "stem_index": 6,
                          "branch_index": 6,
                          "hangul": "경오",
                          "hanja": "庚午"
                        },
                        "month": {
                          "cycle_index": 17,
                          "stem_index": 7,
                          "branch_index": 5,
                          "hangul": "신사",
                          "hanja": "辛巳"
                        },
                        "day": {
                          "cycle_index": 16,
                          "stem_index": 6,
                          "branch_index": 4,
                          "hangul": "경진",
                          "hanja": "庚辰"
                        },
                        "hour": {
                          "cycle_index": 16,
                          "stem_index": 6,
                          "branch_index": 4,
                          "hangul": "경진",
                          "hanja": "庚辰"
                        }
                      },
                      "basis": {
                        "lichun": {
                          "term_id": "1990-21",
                          "term_index": 21,
                          "name": "입춘",
                          "longitude_degrees": 315,
                          "is_jie": true,
                          "month_ordinal": 0,
                          "instant": {
                            "tt_us": "-312630302836773",
                            "utc": "1990-02-04T02:13:59.979227Z",
                            "reference_label": "1990-02-04T02:13:59.979227",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "current_jie": {
                          "term_id": "1990-03",
                          "term_index": 3,
                          "name": "입하",
                          "longitude_degrees": 45,
                          "is_jie": true,
                          "month_ordinal": 3,
                          "instant": {
                            "tt_us": "-304795416839224",
                            "utc": "1990-05-05T18:35:25.976776Z",
                            "reference_label": "1990-05-05T18:35:25.976776",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "next_jie": {
                          "term_id": "1990-05",
                          "term_index": 5,
                          "name": "망종",
                          "longitude_degrees": 75,
                          "is_jie": true,
                          "month_ordinal": 4,
                          "instant": {
                            "tt_us": "-302101964805526",
                            "utc": "1990-06-05T22:46:18.010474Z",
                            "reference_label": "1990-06-05T22:46:18.010474",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "day_date": "1990-05-15",
                        "day_jdn": 2448027,
                        "hour_basis_day_date": "1990-05-15"
                      },
                      "daewoon": null,
                      "derived": null,
                      "uncertainty": {
                        "boundary_status": "stable",
                        "input_seconds": 0,
                        "instant_error_bound_seconds": 0,
                        "clock_error_bound_seconds": 0.014561,
                        "nearest_boundaries": [
                          {
                            "kind": "lichun",
                            "distance_seconds": 8630160.020773,
                            "description": "nearest 입춘 instant (TT)"
                          },
                          {
                            "kind": "jie",
                            "distance_seconds": 795274.023224,
                            "description": "nearest 절 instant (TT)"
                          },
                          {
                            "kind": "day",
                            "distance_seconds": 28902.593229,
                            "description": "day pillar boundary on the calculation clock"
                          },
                          {
                            "kind": "hour",
                            "distance_seconds": 3497.406771,
                            "description": "hour branch boundary on the calculation clock"
                          },
                          {
                            "kind": "timezone_transition",
                            "distance_seconds": 50394600,
                            "description": "nearest civil offset transition"
                          }
                        ],
                        "candidates": [
                          {
                            "pillars": {
                              "year": {
                                "cycle_index": 6,
                                "stem_index": 6,
                                "branch_index": 6,
                                "hangul": "경오",
                                "hanja": "庚午"
                              },
                              "month": {
                                "cycle_index": 17,
                                "stem_index": 7,
                                "branch_index": 5,
                                "hangul": "신사",
                                "hanja": "辛巳"
                              },
                              "day": {
                                "cycle_index": 16,
                                "stem_index": 6,
                                "branch_index": 4,
                                "hangul": "경진",
                                "hanja": "庚辰"
                              },
                              "hour": {
                                "cycle_index": 16,
                                "stem_index": 6,
                                "branch_index": 4,
                                "hangul": "경진",
                                "hanja": "庚辰"
                              }
                            },
                            "daewoon": null,
                            "daewoon_start_age_min": null,
                            "daewoon_start_age_max": null,
                            "basis": {
                              "lichun": {
                                "term_id": "1990-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "-312630302836773",
                                  "utc": "1990-02-04T02:13:59.979227Z",
                                  "reference_label": "1990-02-04T02:13:59.979227",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "current_jie": {
                                "term_id": "1990-03",
                                "term_index": 3,
                                "name": "입하",
                                "longitude_degrees": 45,
                                "is_jie": true,
                                "month_ordinal": 3,
                                "instant": {
                                  "tt_us": "-304795416839224",
                                  "utc": "1990-05-05T18:35:25.976776Z",
                                  "reference_label": "1990-05-05T18:35:25.976776",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "next_jie": {
                                "term_id": "1990-05",
                                "term_index": 5,
                                "name": "망종",
                                "longitude_degrees": 75,
                                "is_jie": true,
                                "month_ordinal": 4,
                                "instant": {
                                  "tt_us": "-302101964805526",
                                  "utc": "1990-06-05T22:46:18.010474Z",
                                  "reference_label": "1990-06-05T22:46:18.010474",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "day_date": "1990-05-15",
                              "day_jdn": 2448027,
                              "hour_basis_day_date": "1990-05-15"
                            },
                            "reason_codes": [
                              "NOMINAL"
                            ]
                          }
                        ],
                        "warnings": []
                      }
                    }
                  },
                  "boundary_sensitive": {
                    "summary": "입춘 경계에 걸친 후보 2개",
                    "description": "요청 예시 `boundary_sensitive`의 실제 응답이다. `boundary_status=sensitive`이고, 두 번째 후보는 입춘 이후라 연주와 월주가 다르다(`reason_codes`). civil 모드라서 `equation_of_time_seconds`, `dut1_seconds`, `longitude_correction_seconds`는 null이고 총 보정은 0이다.",
                    "value": {
                      "request_id": "84a8329987af5d1933b2c5da826ee64d",
                      "provenance": {
                        "engine_id": "four-pillars-for-agents/dev+skyfield-1.55+de440s",
                        "dataset_id": "kr-2026c-de440s-d9750dac2a",
                        "policy_id": "kr-solar-v1",
                        "ruleset_id": "kr-core-v1",
                        "tzdb_id": "2026c",
                        "ephemeris_sha256": "c1c7feeab882263fc493a9d5a5b2ddd71b54826cdf65d8d17a76126b260a49f2",
                        "timescale_model_id": "iers-finals-mjd61293+smh2016",
                        "resolved_policy": {
                          "policy_id": "kr-solar-v1",
                          "clock_basis": "civil",
                          "day_boundary": "midnight",
                          "year_boundary": "lichun_instant",
                          "month_boundary": "jie_instant",
                          "solar_definition": "geocentric_apparent_of_date",
                          "pre_1972_mode": "civil_ut1_approx",
                          "future_utc_mode": "hold_last_announced_tai_minus_utc",
                          "term_equality": "inclusive_zero",
                          "daewoon_duration_scale": "TT",
                          "daewoon_start_method": "mean_gregorian_year",
                          "year_length_days": "365.2425",
                          "display_rounding": "half_up",
                          "daewoon_count": 10,
                          "hemisphere_rule": "no_shift",
                          "ruleset_id": "kr-core-v1",
                          "include_derived": false,
                          "requested_daewoon_direction": null,
                          "sex_for_rule": null
                        },
                        "calculation_fingerprint": "174dd8852ecbf3d710daf3a75fc33e2757473cff61865d9df9bfe7c5001394f9",
                        "fingerprint_key_id": "fp-1"
                      },
                      "time": {
                        "civil_label": "2000-02-04T21:40:00",
                        "timezone": "Asia/Seoul",
                        "total_offset_seconds": 32400,
                        "dst_offset_seconds": 0,
                        "fold": 0,
                        "instant": {
                          "tt_us": "2940064184000",
                          "utc": "2000-02-04T12:40:00.000000Z",
                          "reference_label": "2000-02-04T12:40:00.000000",
                          "reference_scale": "UTC",
                          "time_quality": "observed"
                        },
                        "clock_label": "2000-02-04T21:40:00.000000",
                        "clock_basis": "civil",
                        "equation_of_time_seconds": null,
                        "dut1_seconds": null,
                        "longitude_correction_seconds": null,
                        "total_clock_correction_seconds": 0
                      },
                      "pillars": {
                        "year": {
                          "cycle_index": 15,
                          "stem_index": 5,
                          "branch_index": 3,
                          "hangul": "기묘",
                          "hanja": "己卯"
                        },
                        "month": {
                          "cycle_index": 13,
                          "stem_index": 3,
                          "branch_index": 1,
                          "hangul": "정축",
                          "hanja": "丁丑"
                        },
                        "day": {
                          "cycle_index": 28,
                          "stem_index": 8,
                          "branch_index": 4,
                          "hangul": "임진",
                          "hanja": "壬辰"
                        },
                        "hour": {
                          "cycle_index": 47,
                          "stem_index": 7,
                          "branch_index": 11,
                          "hangul": "신해",
                          "hanja": "辛亥"
                        }
                      },
                      "basis": {
                        "lichun": {
                          "term_id": "1999-21",
                          "term_index": 21,
                          "name": "입춘",
                          "longitude_degrees": 315,
                          "is_jie": true,
                          "month_ordinal": 0,
                          "instant": {
                            "tt_us": "-28616513803801",
                            "utc": "1999-02-04T06:57:02.012199Z",
                            "reference_label": "1999-02-04T06:57:02.012199",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "current_jie": {
                          "term_id": "2000-19",
                          "term_index": 19,
                          "name": "소한",
                          "longitude_degrees": 285,
                          "is_jie": true,
                          "month_ordinal": 11,
                          "instant": {
                            "tt_us": "392505046448",
                            "utc": "2000-01-06T01:00:40.862448Z",
                            "reference_label": "2000-01-06T01:00:40.862448",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "next_jie": {
                          "term_id": "2000-21",
                          "term_index": 21,
                          "name": "입춘",
                          "longitude_degrees": 315,
                          "is_jie": true,
                          "month_ordinal": 0,
                          "instant": {
                            "tt_us": "2940086864513",
                            "utc": "2000-02-04T12:40:22.680513Z",
                            "reference_label": "2000-02-04T12:40:22.680513",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        "day_date": "2000-02-04",
                        "day_jdn": 2451579,
                        "hour_basis_day_date": "2000-02-04"
                      },
                      "daewoon": null,
                      "derived": null,
                      "uncertainty": {
                        "boundary_status": "sensitive",
                        "input_seconds": 600,
                        "instant_error_bound_seconds": 0,
                        "clock_error_bound_seconds": 0,
                        "nearest_boundaries": [
                          {
                            "kind": "lichun",
                            "distance_seconds": 22.680513,
                            "description": "nearest 입춘 instant (TT)"
                          },
                          {
                            "kind": "jie",
                            "distance_seconds": 22.680513,
                            "description": "nearest 절 instant (TT)"
                          },
                          {
                            "kind": "day",
                            "distance_seconds": 8400,
                            "description": "day pillar boundary on the calculation clock"
                          },
                          {
                            "kind": "hour",
                            "distance_seconds": 2400,
                            "description": "hour branch boundary on the calculation clock"
                          },
                          {
                            "kind": "timezone_transition",
                            "distance_seconds": 357334800,
                            "description": "nearest civil offset transition"
                          }
                        ],
                        "candidates": [
                          {
                            "pillars": {
                              "year": {
                                "cycle_index": 15,
                                "stem_index": 5,
                                "branch_index": 3,
                                "hangul": "기묘",
                                "hanja": "己卯"
                              },
                              "month": {
                                "cycle_index": 13,
                                "stem_index": 3,
                                "branch_index": 1,
                                "hangul": "정축",
                                "hanja": "丁丑"
                              },
                              "day": {
                                "cycle_index": 28,
                                "stem_index": 8,
                                "branch_index": 4,
                                "hangul": "임진",
                                "hanja": "壬辰"
                              },
                              "hour": {
                                "cycle_index": 47,
                                "stem_index": 7,
                                "branch_index": 11,
                                "hangul": "신해",
                                "hanja": "辛亥"
                              }
                            },
                            "daewoon": null,
                            "daewoon_start_age_min": null,
                            "daewoon_start_age_max": null,
                            "basis": {
                              "lichun": {
                                "term_id": "1999-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "-28616513803801",
                                  "utc": "1999-02-04T06:57:02.012199Z",
                                  "reference_label": "1999-02-04T06:57:02.012199",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "current_jie": {
                                "term_id": "2000-19",
                                "term_index": 19,
                                "name": "소한",
                                "longitude_degrees": 285,
                                "is_jie": true,
                                "month_ordinal": 11,
                                "instant": {
                                  "tt_us": "392505046448",
                                  "utc": "2000-01-06T01:00:40.862448Z",
                                  "reference_label": "2000-01-06T01:00:40.862448",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "next_jie": {
                                "term_id": "2000-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "2940086864513",
                                  "utc": "2000-02-04T12:40:22.680513Z",
                                  "reference_label": "2000-02-04T12:40:22.680513",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "day_date": "2000-02-04",
                              "day_jdn": 2451579,
                              "hour_basis_day_date": "2000-02-04"
                            },
                            "reason_codes": [
                              "NOMINAL"
                            ]
                          },
                          {
                            "pillars": {
                              "year": {
                                "cycle_index": 16,
                                "stem_index": 6,
                                "branch_index": 4,
                                "hangul": "경진",
                                "hanja": "庚辰"
                              },
                              "month": {
                                "cycle_index": 14,
                                "stem_index": 4,
                                "branch_index": 2,
                                "hangul": "무인",
                                "hanja": "戊寅"
                              },
                              "day": {
                                "cycle_index": 28,
                                "stem_index": 8,
                                "branch_index": 4,
                                "hangul": "임진",
                                "hanja": "壬辰"
                              },
                              "hour": {
                                "cycle_index": 47,
                                "stem_index": 7,
                                "branch_index": 11,
                                "hangul": "신해",
                                "hanja": "辛亥"
                              }
                            },
                            "daewoon": null,
                            "daewoon_start_age_min": null,
                            "daewoon_start_age_max": null,
                            "basis": {
                              "lichun": {
                                "term_id": "2000-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "2940086864513",
                                  "utc": "2000-02-04T12:40:22.680513Z",
                                  "reference_label": "2000-02-04T12:40:22.680513",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "current_jie": {
                                "term_id": "2000-21",
                                "term_index": 21,
                                "name": "입춘",
                                "longitude_degrees": 315,
                                "is_jie": true,
                                "month_ordinal": 0,
                                "instant": {
                                  "tt_us": "2940086864513",
                                  "utc": "2000-02-04T12:40:22.680513Z",
                                  "reference_label": "2000-02-04T12:40:22.680513",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "next_jie": {
                                "term_id": "2000-23",
                                "term_index": 23,
                                "name": "경칩",
                                "longitude_degrees": 345,
                                "is_jie": true,
                                "month_ordinal": 1,
                                "instant": {
                                  "tt_us": "5510622841983",
                                  "utc": "2000-03-05T06:42:38.657983Z",
                                  "reference_label": "2000-03-05T06:42:38.657983",
                                  "reference_scale": "UTC",
                                  "time_quality": "observed"
                                },
                                "numeric_error_seconds": 1e-06,
                                "model_error_seconds": 1
                              },
                              "day_date": "2000-02-04",
                              "day_jdn": 2451579,
                              "hour_basis_day_date": "2000-02-04"
                            },
                            "reason_codes": [
                              "YEAR_PILLAR_DIFFERS",
                              "MONTH_PILLAR_DIFFERS"
                            ]
                          }
                        ],
                        "warnings": []
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                },
                "description": "항상 `no-store`. 출생 정보가 담긴 응답이므로 어떤 캐시에도 저장하지 않는다."
              }
            }
          },
          "400": {
            "description": "`MALFORMED_JSON`: 본문이 JSON 구문에 맞지 않거나, 같은 객체에 중복 키가 있거나, JSON 값 뒤에 후행 데이터가 있거나, 객체·배열 중첩이 32단계를 넘거나, 본문을 읽지 못했다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "malformed_json": {
                    "summary": "JSON 구문 오류",
                    "value": {
                      "code": "MALFORMED_JSON",
                      "message": "Request body is not valid JSON.",
                      "request_id": "aff69cefba67a9afb0323afb903ed41d",
                      "details": []
                    }
                  },
                  "duplicate_key": {
                    "summary": "중복 키",
                    "description": "`{\"birth\":{},\"birth\":{}}`처럼 같은 키가 반복되면 마지막 값을 조용히 쓰지 않고 거절한다.",
                    "value": {
                      "code": "MALFORMED_JSON",
                      "message": "Request body is not valid JSON.",
                      "request_id": "3bec0a34ca9e7b36c7291e53e33a541e",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY`: `X-API-Key` 헤더가 없거나, 길이가 35~128자 범위 밖이거나, 등록되지 않은 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "키 누락",
                    "value": {
                      "code": "INVALID_API_KEY",
                      "message": "API key is missing or invalid.",
                      "request_id": "72bea0d9b6b107757da7c78fd1c3b2c1",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`QUOTA_FORBIDDEN`: 등록됐지만 폐기(revoked)된 키다. 계약·허용 범위를 확인한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "quota_forbidden": {
                    "summary": "폐기된 키",
                    "value": {
                      "code": "QUOTA_FORBIDDEN",
                      "message": "The API key is not allowed to use this resource.",
                      "request_id": "dc7cbeffac51c720bae999a9c38a0424",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "`DATASET_NOT_FOUND`: 요청한 `dataset_id`가 서버에 없다. `POLICY_NOT_FOUND`: 지정한(생략 시 활성) dataset에 요청한 `policy_id`의 승인 정책이 없다(정책은 번들별로 불변이다). 두 경우 모두 다른 데이터로 대체하지 않는다. `GET /v1/policies`에서 가용 ID를 확인한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dataset_not_found": {
                    "summary": "알 수 없는 dataset_id",
                    "value": {
                      "code": "DATASET_NOT_FOUND",
                      "message": "The requested dataset_id is not available.",
                      "request_id": "a1cdfc22b87ef7e77ec08ae1323f43c5",
                      "details": [
                        {
                          "field": "dataset_id",
                          "reason": "unknown"
                        }
                      ]
                    }
                  },
                  "policy_not_found": {
                    "summary": "알 수 없는 policy_id",
                    "description": "policy_id는 스키마 enum 검사보다 먼저 확인하므로 422가 아니라 404로 응답한다.",
                    "value": {
                      "code": "POLICY_NOT_FOUND",
                      "message": "The requested policy_id is not available.",
                      "request_id": "c6ed4d4da4381494317f62e454e37961",
                      "details": [
                        {
                          "field": "policy_id",
                          "reason": "unknown"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "요청 형식은 올바르지만 검증이나 의미 해석에 실패했다. 가능한 code:\n- `INVALID_INPUT`: 스키마 위반(누락 필드, 타입, 패턴, 범위, enum, 정의되지 않은 필드), 실재하지 않는 날짜, 경도 오차가 지원 경도 범위를 넘음, 정책이 허용하지 않는 옵션, 너무 긴 숫자 표기(32자 초과 또는 지수 절댓값 20 초과, `number_too_large`)\n- `UNSUPPORTED_RANGE`: 민간 연도가 1900~2100 밖이거나 번들 데이터 범위 밖\n- `UNSUPPORTED_CALENDAR`: `birth.calendar`가 gregorian이 아님\n- `NONEXISTENT_LOCAL_TIME`: DST·오프셋 전환으로 존재하지 않는 시각. 서버가 임의로 시각을 옮기지 않는다\n- `AMBIGUOUS_LOCAL_TIME`: 두 번 존재하는 시각인데 `birth.fold`가 없음. `details`에 fold별 `reference_label`과 `offset_seconds` 후보가 담긴다\n- `INVALID_FOLD`: 중복이 아닌 시각에 fold=1\n- `CONFLICTING_OPTIONS`: forward/reverse와 `sex_for_rule`을 함께 지정\n- `DAEWOON_SEX_REQUIRED`: `by_year_stem_and_sex`인데 `sex_for_rule`이 없음\n- `UNCERTAINTY_TOO_WIDE`: 불확실성 범위에서 후보(또는 분할 구간)가 한도(후보 32개)를 넘음\n- 음력 입력(`calendar=korean_lunar`) 관련: `INVALID_INPUT`의 `lunar_month_not_found`, `lunar_day_out_of_range`, `leap_month_not_in_this_year`, 허용되지 않는 자오선의 `not_allowed_by_policy`; `gregorian`에 `lunar_leap_month`를 함께 보내면 `CONFLICTING_OPTIONS`(`not_allowed_with_gregorian`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_input_schema": {
                    "summary": "스키마 위반",
                    "description": "birth.longitude 누락, local_datetime 패턴 위반, 정의되지 않은 최상위 필드 `extra`가 동시에 있는 요청이다. `required` 위반의 `field`는 누락된 속성이 아니라 그 속성을 가진 객체의 경로(`birth`)다.",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "b6fbf20f706e24fd36581c16d463ead4",
                      "details": [
                        {
                          "field": "birth",
                          "reason": "required"
                        },
                        {
                          "field": "birth.local_datetime",
                          "reason": "pattern"
                        },
                        {
                          "field": "$",
                          "reason": "additionalProperties"
                        }
                      ]
                    }
                  },
                  "number_too_large": {
                    "summary": "과도한 숫자 표기",
                    "description": "`\"longitude\": 1.26978e99`처럼 지수 절댓값이 20을 넘거나 숫자 표기가 32자를 넘으면 스키마 검증 전에 거절한다(`field`는 `$`).",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "f2894046e2f333042a816e8eceeed3e0",
                      "details": [
                        {
                          "field": "$",
                          "reason": "number_too_large"
                        }
                      ]
                    }
                  },
                  "invalid_datetime": {
                    "summary": "실재하지 않는 날짜",
                    "description": "`2001-02-29T12:00:00`: 패턴은 맞지만 2001년은 윤년이 아니다.",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "07bebcc256db0e3f7dde6b1e4d86c6b8",
                      "details": [
                        {
                          "field": "birth.local_datetime",
                          "reason": "not_a_real_datetime"
                        }
                      ]
                    }
                  },
                  "longitude_range": {
                    "summary": "경도 오차가 지원 범위를 넘음",
                    "description": "longitude=131.8, longitude_uncertainty_degrees=0.5 → 상한 132.3이 132를 넘는다.",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "c4b44656a594052f59fadeae1bc0cd52",
                      "details": [
                        {
                          "field": "birth.longitude_uncertainty_degrees",
                          "reason": "range_exceeds_supported_longitudes"
                        }
                      ]
                    }
                  },
                  "unsupported_range": {
                    "summary": "지원 연도 밖",
                    "description": "`1899-12-31T12:00:00`.",
                    "value": {
                      "code": "UNSUPPORTED_RANGE",
                      "message": "Input is outside the supported range.",
                      "request_id": "4eb29dbb880ad165b976fa3c56acba5e",
                      "details": [
                        {
                          "field": "birth.local_datetime",
                          "reason": "year_1900_2100"
                        }
                      ]
                    }
                  },
                  "unsupported_calendar": {
                    "summary": "음력 등 비지원 달력",
                    "description": "`calendar: \"lunar\"`. enum 위반이지만 INVALID_INPUT보다 먼저 UNSUPPORTED_CALENDAR로 판정한다.",
                    "value": {
                      "code": "UNSUPPORTED_CALENDAR",
                      "message": "Only the gregorian calendar is supported.",
                      "request_id": "830f8e45c47505b6769560484c24b694",
                      "details": [
                        {
                          "field": "birth.calendar",
                          "reason": "enum"
                        }
                      ]
                    }
                  },
                  "nonexistent_local_time": {
                    "summary": "서머타임 시작 공백",
                    "description": "1987-05-10 02:00 KST에 시계가 03:00으로 넘어가 `1987-05-10T02:30:00`은 존재하지 않는다.",
                    "value": {
                      "code": "NONEXISTENT_LOCAL_TIME",
                      "message": "The local time does not exist in this time zone (DST or offset gap).",
                      "request_id": "bda65a567614eda744ac075001e2af71",
                      "details": [
                        {
                          "field": "birth.local_datetime",
                          "reason": "nonexistent"
                        }
                      ]
                    }
                  },
                  "ambiguous_local_time": {
                    "summary": "서머타임 종료 중복",
                    "description": "`1987-10-11T02:30:00`에 fold가 없다. fold=0은 KDT(UTC+10), fold=1은 KST(UTC+9)다. 선택한 fold로 다시 요청한다.",
                    "value": {
                      "code": "AMBIGUOUS_LOCAL_TIME",
                      "message": "The local time occurs twice; choose a fold from details.",
                      "request_id": "f9e269630cd09b0160f6f3c3a357e404",
                      "details": [
                        {
                          "field": "birth.fold",
                          "fold": 0,
                          "reference_label": "1987-10-10T16:30:00.000000",
                          "offset_seconds": 36000
                        },
                        {
                          "field": "birth.fold",
                          "fold": 1,
                          "reference_label": "1987-10-10T17:30:00.000000",
                          "offset_seconds": 32400
                        }
                      ]
                    }
                  },
                  "invalid_fold": {
                    "summary": "중복이 아닌 시각에 fold=1",
                    "value": {
                      "code": "INVALID_FOLD",
                      "message": "fold=1 is only valid for a repeated local time.",
                      "request_id": "bc976fcbb63df76bc1fca53dab56ecac",
                      "details": [
                        {
                          "field": "birth.fold",
                          "reason": "not_ambiguous"
                        }
                      ]
                    }
                  },
                  "conflicting_options": {
                    "summary": "명시 방향과 성별 동시 지정",
                    "description": "`daewoon: {direction: forward, sex_for_rule: male}`.",
                    "value": {
                      "code": "CONFLICTING_OPTIONS",
                      "message": "Options conflict with each other.",
                      "request_id": "1c67c3c20c7504e627cd01266d7cddd8",
                      "details": [
                        {
                          "field": "daewoon.sex_for_rule",
                          "reason": "not_allowed_with_explicit_direction"
                        }
                      ]
                    }
                  },
                  "daewoon_sex_required": {
                    "summary": "성별 규칙 입력 누락",
                    "description": "`daewoon: {direction: by_year_stem_and_sex}`.",
                    "value": {
                      "code": "DAEWOON_SEX_REQUIRED",
                      "message": "sex_for_rule is required for direction by_year_stem_and_sex.",
                      "request_id": "c332ce20a61566c02742b15e7eb79e94",
                      "details": [
                        {
                          "field": "daewoon.sex_for_rule",
                          "reason": "required"
                        }
                      ]
                    }
                  },
                  "uncertainty_too_wide": {
                    "summary": "후보 과다",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "UNCERTAINTY_TOO_WIDE",
                      "message": "The uncertainty range produces too many candidates.",
                      "request_id": "5c1d0f7e9a2b4c6d8e0f1a2b3c4d5e6f",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`: 키별 요청률(토큰 버킷) 한도를 넘었다. `Retry-After` 헤더의 초만큼 기다린 뒤 재시도한다. 계산은 멱등이므로 같은 요청을 다시 보내도 안전하다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "요청률 초과",
                    "description": "초당 0.1회 한도로 발급한 키로 연속 호출해 받은 실제 응답이다(`Retry-After: 10`). 레이트 리밋은 인증 직후 공통 미들웨어에서 적용하므로 본문 형식은 모든 `/v1/*` 엔드포인트에서 같다.",
                    "value": {
                      "code": "RATE_LIMITED",
                      "message": "Rate limit exceeded; retry later.",
                      "request_id": "c140271cdbe843985ae49d81930ed0d8",
                      "details": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "다시 시도하기 전 기다릴 초(정수, 1 이상). 키의 토큰 버킷에 다음 토큰이 생길 때까지 남은 시간을 초 단위로 올림한 값이다.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "example": 10
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`: 예기치 않은 내부 오류다. 응답과 `X-Request-Id`의 `request_id`로 운영팀에 문의한다. 서버 로그에는 요청 내용 없이 request_id와 오류 유형만 남는다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "internal_error": {
                    "summary": "내부 오류",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "INTERNAL_ERROR",
                      "message": "Internal error.",
                      "request_id": "0f9e8d7c6b5a49382716a5b4c3d2e1f0",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "일시적으로 계산할 수 없다. 모두 재시도 가능하며, 서버는 근사값이나 부분 결과로 대체하지 않는다.\n- `DATASET_UNAVAILABLE`: 요청한 dataset이 배포됐지만 로드·검증에 실패했다\n- `RULESET_UNAVAILABLE`: `include_derived=true`인데 정책의 규칙표가 승인되지 않았다(`GET /v1/policies`의 `derived_available=false`). 파생 정보 없이 요청하면 계산된다\n- `SERVICE_OVERLOADED`: 동시 계산 한도가 차서 부하를 차단했다\n- `CALCULATION_TIMEOUT`: 계산 시한(기본 2초)을 넘겼다",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "ruleset_unavailable": {
                    "summary": "승인되지 않은 규칙표",
                    "description": "`include_derived=true` 요청(요청 예시 `with_derived`)을 draft 규칙표 번들에서 실행한 실제 응답이다.",
                    "value": {
                      "code": "RULESET_UNAVAILABLE",
                      "message": "The derived-rule set is not approved for use.",
                      "request_id": "1736924dc991a0d8a9f322fe4ef78ec8",
                      "details": []
                    }
                  },
                  "dataset_unavailable": {
                    "summary": "데이터 번들 미가용",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "DATASET_UNAVAILABLE",
                      "message": "Required dataset is not available.",
                      "request_id": "7a6b5c4d3e2f10a9b8c7d6e5f4a3b2c1",
                      "details": []
                    }
                  },
                  "service_overloaded": {
                    "summary": "부하 차단",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "SERVICE_OVERLOADED",
                      "message": "Server is overloaded; retry later.",
                      "request_id": "3b4c5d6e7f8091a2b3c4d5e6f708192a",
                      "details": []
                    }
                  },
                  "calculation_timeout": {
                    "summary": "계산 시한 초과",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "CALCULATION_TIMEOUT",
                      "message": "Calculation exceeded the time limit.",
                      "request_id": "e1d2c3b4a5968778695a4b3c2d1e0f9a",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "`PAYLOAD_TOO_LARGE`: 요청 본문이 16 KiB(16384바이트)를 넘는다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "payload_too_large": {
                    "summary": "본문 16 KiB 초과",
                    "value": {
                      "code": "PAYLOAD_TOO_LARGE",
                      "message": "Request body exceeds the size limit.",
                      "request_id": "51ecc6e96ca4addf68ef66aa013ab439",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "`UNSUPPORTED_MEDIA_TYPE`: `Content-Type`이 `application/json`이 아니다(누락 포함).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "unsupported_media_type": {
                    "summary": "text/plain 본문",
                    "value": {
                      "code": "UNSUPPORTED_MEDIA_TYPE",
                      "message": "Content-Type must be application/json.",
                      "request_id": "0fa3d81e9c80e85b546ee9d4700596d4",
                      "details": []
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Charts"
        ],
        "description": "출생 기록 1건으로 사주팔자를 계산한다. 요청하면 대운과 파생 정보(십신·지장간)도 계산하며, 계산 근거(provenance), 시간 해석 결과, 경계 근접도, 불확실성 후보를 함께 반환한다.\n\n**인증·권한**: `X-API-Key` 헤더 필수. 활성 키면 호출할 수 있다(별도 scope 없음).\n\n**요청 헤더**\n- `X-API-Key` (필수): API 키.\n- `Content-Type` (필수): `application/json`. `; charset=utf-8` 같은 파라미터는 허용하고, 그 외 미디어 타입은 415 UNSUPPORTED_MEDIA_TYPE.\n- `traceparent` (선택): W3C Trace Context.\n\n**요청 본문**: `ChartRequest` JSON, 최대 16 KiB(초과 시 413). 중복 키, 후행 데이터, 32단계를 넘는 중첩이 있으면 400 MALFORMED_JSON, 정의되지 않은 필드는 422 INVALID_INPUT이다.\n\n**처리 순서** (먼저 걸린 오류 하나만 반환)\n1. 인증 → 401 / 403, 레이트 리밋 → 429\n2. `Content-Type` → 415, 본문 크기 → 413\n3. 동시 계산 한도 → 503 SERVICE_OVERLOADED\n4. JSON 구문, 중복 키, 중첩 깊이 32 초과 → 400 MALFORMED_JSON. 숫자 표기가 32자를 넘거나 지수 절댓값이 20을 넘으면 → 422 INVALID_INPUT(`number_too_large`)\n5. 의미 선검사: 서버에 없는 `dataset_id` → 404 DATASET_NOT_FOUND, 배포됐지만 로드·검증에 실패한 dataset → 503 DATASET_UNAVAILABLE, 지정한(생략 시 활성) dataset에 없는 `policy_id` → 404 POLICY_NOT_FOUND, `calendar`≠gregorian → 422 UNSUPPORTED_CALENDAR, 대운 방향과 성별 조합 → 422 DAEWOON_SEX_REQUIRED / CONFLICTING_OPTIONS. 이 검사는 스키마 검증보다 먼저 수행하므로, 예를 들어 알 수 없는 policy_id는 enum 위반이 아니라 404로 응답한다.\n6. 스키마 검증 → 422 INVALID_INPUT (`details`에 위반 필드와 JSON Schema 키워드, 최대 10개)\n7. 정규화: 실재하지 않는 날짜 → 422 INVALID_INPUT(`not_a_real_datetime`), 민간 연도 1900~2100 밖 → 422 UNSUPPORTED_RANGE, `longitude ± longitude_uncertainty_degrees`가 124~132 밖 → 422 INVALID_INPUT, 정책이 허용하지 않는 options 값 → 422 INVALID_INPUT(`not_allowed_by_policy`)\n8. 시간대 해석: DST·오프셋 전환 공백 → 422 NONEXISTENT_LOCAL_TIME, 두 번 존재하는 시각에 fold 미지정 → 422 AMBIGUOUS_LOCAL_TIME, 중복이 아닌 시각에 fold=1 → 422 INVALID_FOLD\n9. `include_derived=true`인데 승인된 규칙표가 없음 → 503 RULESET_UNAVAILABLE(빈 결과로 위장하지 않음)\n10. 불확실성 후보 열거: 후보 32개 초과 → 422 UNCERTAINTY_TOO_WIDE, 계산 시한(기본 2초) 초과 → 503 CALCULATION_TIMEOUT\n\n**기본값 해석**: 생략한 옵션은 policy 기본값으로 채우고, 실제 적용값 전체를 `provenance.resolved_policy`에 반환한다. `daewoon`을 생략하면 대운을 계산하지 않고 `daewoon: null`이다. `include_derived`를 생략하거나 false면 `derived: null`이다.\n\n**연주·월주는 절입 순간(TT) 기준**이고, **일주·시주는 `clock_basis` 시간축 기준**이다. 기본 정책 `kr-solar-v1`은 진태양시(`local_apparent_solar`)와 00시 날 경계(`midnight`)를 쓴다.\n\n**멱등성**: 부작용 없는 순수 계산이다. 같은 요청을 재시도해도 안전하며 결정적 필드는 항상 같다(`request_id`만 다름).\n\n**캐시**: 응답은 항상 `Cache-Control: no-store`다.\n\n**재현성**: 같은 결과가 필요하면 응답의 `provenance.dataset_id`와 `provenance.policy_id`를 요청에 명시해 고정한다.\n\n**개인정보**: 출생 정보는 로그·트레이스·메트릭에 기록하지 않고 저장하지 않는다.\n\n**레이트 리밋**: 키별 한도를 넘으면 429와 `Retry-After`를 반환한다.\n\n**호출 예시**\n\n```bash\ncurl -sS -X POST \"${API_BASE}/v1/charts\" \\\n  -H \"X-API-Key: ${SAJU_API_KEY}\" \\\n  -H 'Content-Type: application/json' \\\n  --data '{\"birth\":{\"calendar\":\"gregorian\",\"local_datetime\":\"2000-01-07T12:00:00\",\"timezone\":\"Asia/Seoul\",\"longitude\":126.978,\"uncertainty_seconds\":60},\"daewoon\":{\"direction\":\"forward\"}}'\n```"
      }
    },
    "/v1/solar-terms": {
      "get": {
        "operationId": "listSolarTerms",
        "summary": "24절기 조회",
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1900,
              "maximum": 2100
            },
            "description": "필수. 조회할 연도(정수, 1900~2100, 10진수 4자리 이내). 누락되거나 반복되면 422 INVALID_INPUT(`required`), 정수가 아니면 422 INVALID_INPUT(`integer`), 범위 밖이면 422 UNSUPPORTED_RANGE(`range_1900_2100`). 기본값 없음.",
            "example": 2026
          },
          {
            "name": "dataset_id",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "선택. 사용할 데이터 번들 ID. 생략하면 서버 활성 번들(`GET /v1/policies`의 `active_dataset_id`)을 쓴다. 빈 문자열이면 422 INVALID_INPUT(`minLength`), 서버에 없는 ID면 404 DATASET_NOT_FOUND, 배포됐지만 로드·검증에 실패했으면 503 DATASET_UNAVAILABLE이다.",
            "example": "kr-2026c-de440s-d9750dac2a"
          }
        ],
        "responses": {
          "200": {
            "description": "해당 연도의 24절기(정확히 24개, 시간순). `dataset_id`는 실제 사용한 번들 ID다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TermsResponse"
                },
                "examples": {
                  "year_2026": {
                    "summary": "2026년 24절기",
                    "description": "`GET /v1/solar-terms?year=2026`의 실제 응답이다.",
                    "value": {
                      "dataset_id": "kr-2026c-de440s-d9750dac2a",
                      "items": [
                        {
                          "term_id": "2026-19",
                          "term_index": 19,
                          "name": "소한",
                          "longitude_degrees": 285,
                          "is_jie": true,
                          "month_ordinal": 11,
                          "instant": {
                            "tt_us": "820873458639459",
                            "utc": "2026-01-05T08:23:09.455459Z",
                            "reference_label": "2026-01-05T08:23:09.455459",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-20",
                          "term_index": 20,
                          "name": "대한",
                          "longitude_degrees": 300,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "822145565255299",
                            "utc": "2026-01-20T01:44:56.071299Z",
                            "reference_label": "2026-01-20T01:44:56.071299",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-21",
                          "term_index": 21,
                          "name": "입춘",
                          "longitude_degrees": 315,
                          "is_jie": true,
                          "month_ordinal": 0,
                          "instant": {
                            "tt_us": "823420997134703",
                            "utc": "2026-02-03T20:02:07.950703Z",
                            "reference_label": "2026-02-03T20:02:07.950703",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-22",
                          "term_index": 22,
                          "name": "우수",
                          "longitude_degrees": 330,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "824701984683942",
                            "utc": "2026-02-18T15:51:55.499942Z",
                            "reference_label": "2026-02-18T15:51:55.499942",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-23",
                          "term_index": 23,
                          "name": "경칩",
                          "longitude_degrees": 345,
                          "is_jie": true,
                          "month_ordinal": 1,
                          "instant": {
                            "tt_us": "825991208302510",
                            "utc": "2026-03-05T13:58:59.118510Z",
                            "reference_label": "2026-03-05T13:58:59.118510",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-00",
                          "term_index": 0,
                          "name": "춘분",
                          "longitude_degrees": 0,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "827290026633159",
                            "utc": "2026-03-20T14:45:57.449159Z",
                            "reference_label": "2026-03-20T14:45:57.449159",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-01",
                          "term_index": 1,
                          "name": "청명",
                          "longitude_degrees": 15,
                          "is_jie": true,
                          "month_ordinal": 2,
                          "instant": {
                            "tt_us": "828600068356492",
                            "utc": "2026-04-04T18:39:59.172492Z",
                            "reference_label": "2026-04-04T18:39:59.172492",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-02",
                          "term_index": 2,
                          "name": "곡우",
                          "longitude_degrees": 30,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "829921215701422",
                            "utc": "2026-04-20T01:39:06.517422Z",
                            "reference_label": "2026-04-20T01:39:06.517422",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-03",
                          "term_index": 3,
                          "name": "입하",
                          "longitude_degrees": 45,
                          "is_jie": true,
                          "month_ordinal": 3,
                          "instant": {
                            "tt_us": "831253792849597",
                            "utc": "2026-05-05T11:48:43.665597Z",
                            "reference_label": "2026-05-05T11:48:43.665597",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-04",
                          "term_index": 4,
                          "name": "소만",
                          "longitude_degrees": 60,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "832595873513335",
                            "utc": "2026-05-21T00:36:44.329335Z",
                            "reference_label": "2026-05-21T00:36:44.329335",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-05",
                          "term_index": 5,
                          "name": "망종",
                          "longitude_degrees": 75,
                          "is_jie": true,
                          "month_ordinal": 4,
                          "instant": {
                            "tt_us": "833946571073728",
                            "utc": "2026-06-05T15:48:21.889728Z",
                            "reference_label": "2026-06-05T15:48:21.889728",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-06",
                          "term_index": 6,
                          "name": "하지",
                          "longitude_degrees": 90,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "835302339532517",
                            "utc": "2026-06-21T08:24:30.348517Z",
                            "reference_label": "2026-06-21T08:24:30.348517",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-07",
                          "term_index": 7,
                          "name": "소서",
                          "longitude_degrees": 105,
                          "is_jie": true,
                          "month_ordinal": 5,
                          "instant": {
                            "tt_us": "836661486558564",
                            "utc": "2026-07-07T01:56:57.374564Z",
                            "reference_label": "2026-07-07T01:56:57.374564",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-08",
                          "term_index": 8,
                          "name": "대서",
                          "longitude_degrees": 120,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "838019654403722",
                            "utc": "2026-07-22T19:13:05.219722Z",
                            "reference_label": "2026-07-22T19:13:05.219722",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-09",
                          "term_index": 9,
                          "name": "입추",
                          "longitude_degrees": 135,
                          "is_jie": true,
                          "month_ordinal": 6,
                          "instant": {
                            "tt_us": "839375033704370",
                            "utc": "2026-08-07T11:42:44.520370Z",
                            "reference_label": "2026-08-07T11:42:44.520370",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-10",
                          "term_index": 10,
                          "name": "처서",
                          "longitude_degrees": 150,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "840723597613995",
                            "utc": "2026-08-23T02:18:48.429995Z",
                            "reference_label": "2026-08-23T02:18:48.429995",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-11",
                          "term_index": 11,
                          "name": "백로",
                          "longitude_degrees": 165,
                          "is_jie": true,
                          "month_ordinal": 7,
                          "instant": {
                            "tt_us": "842064146402181",
                            "utc": "2026-09-07T14:41:17.218181Z",
                            "reference_label": "2026-09-07T14:41:17.218181",
                            "reference_scale": "UTC",
                            "time_quality": "observed"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-12",
                          "term_index": 12,
                          "name": "추분",
                          "longitude_degrees": 180,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "843393982349247",
                            "utc": "2026-09-23T00:05:13.165247Z",
                            "reference_label": "2026-09-23T00:05:13.165247",
                            "reference_scale": "UTC",
                            "time_quality": "predicted"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-13",
                          "term_index": 13,
                          "name": "한로",
                          "longitude_degrees": 195,
                          "is_jie": true,
                          "month_ordinal": 8,
                          "instant": {
                            "tt_us": "844713026682705",
                            "utc": "2026-10-08T06:29:17.498705Z",
                            "reference_label": "2026-10-08T06:29:17.498705",
                            "reference_scale": "UTC",
                            "time_quality": "predicted"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-14",
                          "term_index": 14,
                          "name": "상강",
                          "longitude_degrees": 210,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "846020345473194",
                            "utc": "2026-10-23T09:37:56.289194Z",
                            "reference_label": "2026-10-23T09:37:56.289194",
                            "reference_scale": "UTC",
                            "time_quality": "predicted"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-15",
                          "term_index": 15,
                          "name": "입동",
                          "longitude_degrees": 225,
                          "is_jie": true,
                          "month_ordinal": 9,
                          "instant": {
                            "tt_us": "847317193521020",
                            "utc": "2026-11-07T09:52:04.337020Z",
                            "reference_label": "2026-11-07T09:52:04.337020",
                            "reference_scale": "UTC",
                            "time_quality": "predicted"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-16",
                          "term_index": 16,
                          "name": "소설",
                          "longitude_degrees": 240,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "848604269747075",
                            "utc": "2026-11-22T07:23:20.563075Z",
                            "reference_label": "2026-11-22T07:23:20.563075",
                            "reference_scale": "UTC",
                            "time_quality": "predicted"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-17",
                          "term_index": 17,
                          "name": "대설",
                          "longitude_degrees": 255,
                          "is_jie": true,
                          "month_ordinal": 10,
                          "instant": {
                            "tt_us": "849884020652610",
                            "utc": "2026-12-07T02:52:31.468610Z",
                            "reference_label": "2026-12-07T02:52:31.468610",
                            "reference_scale": "UTC",
                            "time_quality": "predicted"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        },
                        {
                          "term_id": "2026-18",
                          "term_index": 18,
                          "name": "동지",
                          "longitude_degrees": 270,
                          "is_jie": false,
                          "month_ordinal": null,
                          "instant": {
                            "tt_us": "851158283366579",
                            "utc": "2026-12-21T20:50:14.182579Z",
                            "reference_label": "2026-12-21T20:50:14.182579",
                            "reference_scale": "UTC",
                            "time_quality": "predicted"
                          },
                          "numeric_error_seconds": 1e-06,
                          "model_error_seconds": 1
                        }
                      ],
                      "year": 2026
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "현재 구현은 이 엔드포인트에서 400을 반환하지 않는다(GET 요청이라 본문을 해석하지 않는다). 계약 호환을 위해 남겨 둔 예약 항목이며, 쿼리 오류는 422로 응답한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY`: `X-API-Key`가 없거나, 형식이 잘못됐거나, 등록되지 않은 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "잘못된 키",
                    "value": {
                      "code": "INVALID_API_KEY",
                      "message": "API key is missing or invalid.",
                      "request_id": "14c69c2b54502ca6416f3b0efb926614",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`QUOTA_FORBIDDEN`: 폐기(revoked)된 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "quota_forbidden": {
                    "summary": "폐기된 키",
                    "value": {
                      "code": "QUOTA_FORBIDDEN",
                      "message": "The API key is not allowed to use this resource.",
                      "request_id": "dc7cbeffac51c720bae999a9c38a0424",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "`DATASET_NOT_FOUND`: 요청한 `dataset_id`가 서버에 없다(`details[0].field=dataset_id`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dataset_not_found": {
                    "summary": "알 수 없는 dataset_id",
                    "value": {
                      "code": "DATASET_NOT_FOUND",
                      "message": "The requested dataset_id is not available.",
                      "request_id": "0a52e40c267c86b2d8447c07b97e0ec8",
                      "details": [
                        {
                          "field": "dataset_id",
                          "reason": "unknown"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "`INVALID_INPUT`: `year` 누락·반복·정수 아님, 빈 `dataset_id`, 정의되지 않은 쿼리 파라미터. `UNSUPPORTED_RANGE`: `year`가 1900~2100 밖이다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "year_required": {
                    "summary": "year 누락",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "aacbe4e82ee708d05d8701c19670272f",
                      "details": [
                        {
                          "field": "year",
                          "reason": "required"
                        }
                      ]
                    }
                  },
                  "year_not_integer": {
                    "summary": "year가 정수가 아님",
                    "description": "`?year=20x6`.",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "c328c0275cc2ee2e633d6a39165196ed",
                      "details": [
                        {
                          "field": "year",
                          "reason": "integer"
                        }
                      ]
                    }
                  },
                  "unknown_parameter": {
                    "summary": "정의되지 않은 파라미터",
                    "description": "`?year=2026&lang=ko`.",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "e0a0889d79ddf3dbd8baedaad251a134",
                      "details": [
                        {
                          "field": "lang",
                          "reason": "unknown_parameter"
                        }
                      ]
                    }
                  },
                  "empty_dataset_id": {
                    "summary": "빈 dataset_id",
                    "description": "`?year=2026&dataset_id=`.",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "cffa773fc63942ee1bb6801efe35c144",
                      "details": [
                        {
                          "field": "dataset_id",
                          "reason": "minLength"
                        }
                      ]
                    }
                  },
                  "unsupported_range": {
                    "summary": "범위 밖 연도",
                    "description": "`?year=1899`.",
                    "value": {
                      "code": "UNSUPPORTED_RANGE",
                      "message": "Input is outside the supported range.",
                      "request_id": "543d936ad460b4bed7cebd92ce142a2f",
                      "details": [
                        {
                          "field": "year",
                          "reason": "range_1900_2100"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`: 키별 요청률 한도를 넘었다. `Retry-After`만큼 기다린 뒤 재시도한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "요청률 초과",
                    "value": {
                      "code": "RATE_LIMITED",
                      "message": "Rate limit exceeded; retry later.",
                      "request_id": "c140271cdbe843985ae49d81930ed0d8",
                      "details": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "다시 시도하기 전 기다릴 초(정수, 1 이상). 키의 토큰 버킷에 다음 토큰이 생길 때까지 남은 시간을 초 단위로 올림한 값이다.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "example": 10
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`: 절기 시각을 렌더링하다 예기치 않은 오류가 났다. `request_id`로 문의한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "internal_error": {
                    "summary": "내부 오류",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "INTERNAL_ERROR",
                      "message": "Internal error.",
                      "request_id": "4d3c2b1a0f9e8d7c6b5a493827160514",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "`DATASET_UNAVAILABLE`: 지정한(또는 활성) 번들이 배포됐지만 로드·검증에 실패했다. 이 엔드포인트는 RULESET_UNAVAILABLE, SERVICE_OVERLOADED, CALCULATION_TIMEOUT을 반환하지 않는다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dataset_unavailable": {
                    "summary": "데이터 번들 미가용",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "DATASET_UNAVAILABLE",
                      "message": "Required dataset is not available.",
                      "request_id": "9f8e7d6c5b4a39281706f5e4d3c2b1a0",
                      "details": []
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Reference"
        ],
        "description": "지정한 연도의 24절기 사건 시각을 조회한다. 절기는 지구 중심 겉보기 태양 황경(날짜의 황도·분점) 기준이며, 번들에 미리 계산된 값을 쓴다.\n\n**동작**: 결과는 번들의 기준 렌더링 연도로 묶은 24개 사건이며, 시간순이다(그 해 1월 소한부터 12월 동지까지). `term_id`는 `{연도}-{term_index 2자리}` 형식이다(예: `2026-21`은 2026년 입춘). 12절(`is_jie=true`)은 월주 경계이고 `month_ordinal`(인월=0)을 가진다. 1972년 이전 시각은 `utc=null`, `reference_scale=UT1_APPROX`로, 윤초 확정 범위 이후 시각은 `UTC_PROVISIONAL`(`time_quality=predicted`)로 표시한다.\n\n**인증·권한**: `X-API-Key` 헤더 필수(별도 scope 없음).\n\n**쿼리 파라미터**: `year`(필수), `dataset_id`(선택)만 받는다. 그 외 파라미터는 422 INVALID_INPUT(`unknown_parameter`)이다. 파라미터를 반복하면 `year`는 422(`required`), `dataset_id`는 422(`minLength`)다.\n\n**멱등성·캐시**: 부작용 없는 조회이며 같은 `dataset_id`와 `year`면 결과가 같다. 응답은 `Cache-Control: no-store`다.\n\n**재현성**: `dataset_id`를 지정하면 그 번들 값으로 고정된다. 생략하면 서버 활성 번들을 쓰고, 실제 사용한 ID를 응답 `dataset_id`로 반환한다.\n\n**레이트 리밋**: 키별 한도를 넘으면 429와 `Retry-After`를 반환한다.\n\n**호출 예시**\n\n```bash\ncurl -sS \"${API_BASE}/v1/solar-terms?year=2026\" \\\n  -H \"X-API-Key: ${SAJU_API_KEY}\"\n```"
      }
    },
    "/v1/policies": {
      "get": {
        "operationId": "listPolicies",
        "summary": "지원 정책 조회",
        "responses": {
          "200": {
            "description": "승인된 정책 목록. `defaults`의 `requested_daewoon_direction`과 `sex_for_rule`은 요청 기록용 필드라 항상 null이다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PoliciesResponse"
                },
                "examples": {
                  "policies": {
                    "summary": "kr-solar-v1",
                    "description": "`GET /v1/policies`의 응답 예다(규칙표 `kr-core-v1`이 승인 상태라 `derived_available=true`).",
                    "value": {
                      "items": [
                        {
                          "policy_id": "kr-solar-v1",
                          "status": "approved",
                          "supported_birth_dates": [
                            "1900-01-01",
                            "2100-12-31"
                          ],
                          "supported_timezones": [
                            "Asia/Seoul"
                          ],
                          "active_dataset_id": "kr-2026c-de440s-d9750dac2a",
                          "available_dataset_ids": [
                            "kr-2026c-de440s-d9750dac2a"
                          ],
                          "defaults": {
                            "policy_id": "kr-solar-v1",
                            "clock_basis": "local_apparent_solar",
                            "day_boundary": "midnight",
                            "year_boundary": "lichun_instant",
                            "month_boundary": "jie_instant",
                            "solar_definition": "geocentric_apparent_of_date",
                            "pre_1972_mode": "civil_ut1_approx",
                            "future_utc_mode": "hold_last_announced_tai_minus_utc",
                            "term_equality": "inclusive_zero",
                            "daewoon_duration_scale": "TT",
                            "daewoon_start_method": "mean_gregorian_year",
                            "year_length_days": "365.2425",
                            "display_rounding": "half_up",
                            "daewoon_count": 10,
                            "hemisphere_rule": "no_shift",
                            "ruleset_id": "kr-core-v1",
                            "include_derived": false,
                            "requested_daewoon_direction": null,
                            "sex_for_rule": null
                          },
                          "allowed_clock_bases": [
                            "civil",
                            "local_mean_solar",
                            "local_apparent_solar"
                          ],
                          "allowed_day_boundaries": [
                            "midnight",
                            "zi_start_23",
                            "split_zi"
                          ],
                          "derived_available": true
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "현재 구현은 이 엔드포인트에서 400을 반환하지 않는다(GET 요청이라 본문을 해석하지 않는다). 계약 호환을 위해 남겨 둔 예약 항목이다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY`: `X-API-Key`가 없거나, 형식이 잘못됐거나, 등록되지 않은 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "키 누락",
                    "value": {
                      "code": "INVALID_API_KEY",
                      "message": "API key is missing or invalid.",
                      "request_id": "72bea0d9b6b107757da7c78fd1c3b2c1",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`QUOTA_FORBIDDEN`: 폐기(revoked)된 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "quota_forbidden": {
                    "summary": "폐기된 키",
                    "value": {
                      "code": "QUOTA_FORBIDDEN",
                      "message": "The API key is not allowed to use this resource.",
                      "request_id": "dc7cbeffac51c720bae999a9c38a0424",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "현재 구현은 이 엔드포인트에서 404 DATASET_NOT_FOUND / POLICY_NOT_FOUND를 반환하지 않는다(ID를 입력받지 않는다). 계약 호환을 위해 남겨 둔 예약 항목이다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`INVALID_INPUT`: 쿼리 파라미터를 보냈다(`details[0]` = `{field: \"$\", reason: \"no_parameters_allowed\"}`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "no_parameters_allowed": {
                    "summary": "쿼리 파라미터 포함",
                    "description": "`?policy_id=kr-solar-v1`.",
                    "value": {
                      "code": "INVALID_INPUT",
                      "message": "Request failed validation.",
                      "request_id": "9d363141bab0387757276620127f08f0",
                      "details": [
                        {
                          "field": "$",
                          "reason": "no_parameters_allowed"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`: 키별 요청률 한도를 넘었다. `Retry-After`만큼 기다린 뒤 재시도한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "요청률 초과",
                    "description": "초당 0.1회 한도 키로 이 엔드포인트를 연속 호출해 받은 실제 응답이다.",
                    "value": {
                      "code": "RATE_LIMITED",
                      "message": "Rate limit exceeded; retry later.",
                      "request_id": "c140271cdbe843985ae49d81930ed0d8",
                      "details": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "다시 시도하기 전 기다릴 초(정수, 1 이상). 키의 토큰 버킷에 다음 토큰이 생길 때까지 남은 시간을 초 단위로 올림한 값이다.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "example": 10
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`: 예기치 않은 내부 오류다. `request_id`로 문의한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "internal_error": {
                    "summary": "내부 오류",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "INTERNAL_ERROR",
                      "message": "Internal error.",
                      "request_id": "1a2b3c4d5e6f708192a3b4c5d6e7f801",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "현재 구현은 이 엔드포인트에서 503을 반환하지 않는다(서버는 활성 번들을 로드한 뒤에만 기동한다). 계약 호환을 위해 남겨 둔 예약 항목이다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Reference"
        ],
        "description": "서버가 승인(`approved`)한 계산 정책 목록을 조회한다. 각 정책의 지원 범위, 기본값(`defaults`), 요청으로 바꿀 수 있는 옵션 목록, 활성·가용 데이터 번들 ID, 파생 정보 가용 여부를 담는다. 클라이언트는 이 응답으로 `POST /v1/charts`에 쓸 `policy_id`·`dataset_id`·`options` 값을 정하고, `derived_available`로 `include_derived=true`를 쓸 수 있는지 미리 확인한다.\n\n**인증·권한**: `X-API-Key` 헤더 필수(별도 scope 없음).\n\n**파라미터**: 없음. 쿼리 파라미터를 하나라도 보내면 422 INVALID_INPUT(`no_parameters_allowed`)이다.\n\n**멱등성·캐시**: 부작용 없는 조회다. 응답은 `Cache-Control: no-store`다. 활성 번들 교체나 규칙표 승인 상태에 따라 내용이 바뀔 수 있다.\n\n**레이트 리밋**: 키별 한도를 넘으면 429와 `Retry-After`를 반환한다.\n\n**호출 예시**\n\n```bash\ncurl -sS \"${API_BASE}/v1/policies\" \\\n  -H \"X-API-Key: ${SAJU_API_KEY}\"\n```"
      }
    },
    "/health/live": {
      "get": {
        "operationId": "liveness",
        "security": [],
        "responses": {
          "200": {
            "description": "프로세스 생존. 항상 `{\"status\":\"ok\"}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "examples": {
                  "ok": {
                    "summary": "생존",
                    "value": {
                      "status": "ok"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Health"
        ],
        "summary": "생존 확인",
        "description": "프로세스가 살아 있고 HTTP 요청을 처리할 수 있으면 200 `{\"status\":\"ok\"}`를 반환한다. 데이터 준비 여부는 보지 않는다(준비 상태는 `/health/ready`). Kubernetes liveness probe용이다.\n\n**인증**: 없음(`security: []`). 레이트 리밋이 없고 요청 로그를 남기지 않는다. 파라미터 없음. 응답은 `Cache-Control: no-store`다.\n\n```bash\ncurl -sS \"${API_BASE}/health/live\"\n```"
      }
    },
    "/health/ready": {
      "get": {
        "operationId": "readiness",
        "security": [],
        "responses": {
          "200": {
            "description": "트래픽 수신 가능. 번들이 준비됐고 드레인 중이 아니다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "examples": {
                  "ready": {
                    "summary": "준비됨",
                    "value": {
                      "status": "ok"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "트래픽 수신 불가. 번들이 준비되지 않았거나 graceful shutdown 드레인 중이다. 본문은 오류 형식이 아니라 `Health`다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "examples": {
                  "not_ready": {
                    "summary": "준비 안 됨",
                    "description": "드레인 중 응답이다. 본문은 `Health` enum 값 그대로다.",
                    "value": {
                      "status": "not_ready"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Health"
        ],
        "summary": "준비 상태 확인",
        "description": "검증된 데이터 번들이 로드됐고 종료 드레인 중이 아니면 200 `{\"status\":\"ok\"}`, 아니면 503 `{\"status\":\"not_ready\"}`를 반환한다. 종료 신호(SIGTERM 등)를 받으면 먼저 not_ready로 바꾼 뒤(드레인 지연, 기본 5초) 연결을 정리하므로, 로드밸런서가 새 트래픽을 보내지 않게 된다. Kubernetes readiness probe용이다.\n\n**인증**: 없음(`security: []`). 레이트 리밋이 없고 요청 로그를 남기지 않는다. 파라미터 없음. 응답은 `Cache-Control: no-store`다.\n\n```bash\ncurl -sS -o /dev/null -w '%{http_code}\\n' \"${API_BASE}/health/ready\"\n```"
      }
    },
    "/v1/lunar-months": {
      "get": {
        "operationId": "listLunarMonths",
        "summary": "음력 연도의 월 목록 조회",
        "description": "한 음력 연도의 달 목록을 돌려준다. 파라미터 이름이 `lunar_year`인 것은 음력 연도가 사주의 연주 기준과 무관하기 때문이다. **사주 연주·월주는 입춘과 절의 실제 순간으로 정하며 음력 설날이나 음력 연도와 관계가 없다.**",
        "parameters": [
          {
            "name": "lunar_year",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "필수. 조회할 음력 연도(정수, 10진수 4자리 이내). 누락되거나 반복되면 422 INVALID_INPUT(`required`), 정수가 아니면 422 INVALID_INPUT(`integer`), 표 범위 밖이면 422 UNSUPPORTED_RANGE(`outside_table`).",
            "example": 2017
          },
          {
            "name": "lunar_meridian",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "kr_kasi",
                "kr_standard_utc9",
                "kr_historical_offsets"
              ]
            },
            "description": "선택. 기준 자오선. 생략하면 정책 기본값을 쓴다. 허용되지 않는 값이면 422 INVALID_INPUT(`not_allowed_by_policy`).",
            "example": "kr_kasi"
          },
          {
            "name": "policy_id",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "선택. 기준 자오선의 기본값과 허용 목록을 읽을 정책. 생략하면 `kr-solar-v1`을 쓴다. 빈 문자열이면 422 INVALID_INPUT(`minLength`), 없는 정책이면 404 POLICY_NOT_FOUND."
          },
          {
            "name": "dataset_id",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "선택. 사용할 데이터 번들 ID. 생략하면 서버 활성 번들을 쓴다. 빈 문자열이면 422 INVALID_INPUT(`minLength`), 없는 ID면 404 DATASET_NOT_FOUND, 로드 실패면 503 DATASET_UNAVAILABLE."
          }
        ],
        "responses": {
          "200": {
            "description": "그 음력 연도의 달 목록(초하루 순).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LunarMonthsResponse"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY`: `X-API-Key`가 없거나, 형식이 잘못됐거나, 등록되지 않은 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "잘못된 키",
                    "value": {
                      "code": "INVALID_API_KEY",
                      "message": "API key is missing or invalid.",
                      "request_id": "14c69c2b54502ca6416f3b0efb926614",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "파라미터 검증 실패. `lunar_year` 누락·형식·범위, 허용되지 않는 `lunar_meridian`, 빈 `dataset_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/convert": {
      "get": {
        "operationId": "convertDate",
        "summary": "양력↔음력 날짜 변환",
        "description": "양력 날짜를 한국 음력으로, 또는 음력 날짜(윤달 포함)를 양력으로 바꾸고 그 날짜의 일진을 함께 돌려준다. 기본 기준은 한국천문연구원(KASI) 공식 음양력이며 1900-01-01..2050-12-13의 모든 날짜가 KASI 발표값과 일치한다. 그 이후는 계산 확장이며 `LUNAR_BEYOND_OFFICIAL_RANGE` 경고가 붙는다.\n\n인증: `X-API-Key`. 멱등·결정적 조회다. 쿼리는 엄격하게 검사한다: 알 수 없는 파라미터는 422 INVALID_INPUT(`unknown_parameter`), 같은 파라미터 반복은 `repeated`, 빈 값은 `minLength`, 퍼센트 인코딩 오류는 `$`/`malformed_query`.\n\n날짜는 쿼리 문자열로 전달되므로 게이트웨이·마켓플레이스(RapidAPI 등)의 액세스 로그와 분석, 브라우저 기록에 남을 수 있다. 서버와 서버 앞 프록시는 쿼리 값을 로그에 남기지 않는다. 출생 정보처럼 URL에 남기면 안 되는 값은 `POST /v1/charts` 본문으로 보낸다(POST도 게이트웨이의 본문 로깅까지 막아 주지는 않는다).\n\n```\ncurl -H \"X-API-Key: $KEY\" 'https://api.perpetual.smartlink.ai.kr/v1/convert?calendar=korean_lunar&date=1985-08-15'\n```",
        "parameters": [
          {
            "name": "calendar",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "gregorian",
                "korean_lunar"
              ]
            },
            "description": "필수. `date`의 달력. 누락이면 422 INVALID_INPUT(`required`), 그 밖의 값이면 422 UNSUPPORTED_CALENDAR(`unsupported`).",
            "example": "korean_lunar"
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            },
            "description": "필수. 조회할 날짜 `YYYY-MM-DD`(정확히 10자, 시각·오프셋 없음). `calendar=korean_lunar`이면 음력 날짜로 읽으므로 음력 2월 30일처럼 양력에 없는 날짜도 쓸 수 있다. 누락이면 422 INVALID_INPUT(`required`), 형식이 다르면 `pattern`, 양력에 없는 날짜면 `not_a_real_date`, 음력에 없는 달·날이면 `lunar_month_not_found`/`lunar_day_out_of_range`. 변환된 양력 날짜가 1900-01-01..2100-12-31 밖이면 422 UNSUPPORTED_RANGE(`supported_range`).",
            "example": "2024-02-04"
          },
          {
            "name": "lunar_leap_month",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "선택. `calendar=korean_lunar`일 때 그 달이 윤달이면 `true`(생략하면 평달). `true`/`false` 외의 값이면 422 INVALID_INPUT(`type`), `gregorian`과 함께 오면 값과 관계없이 422 CONFLICTING_OPTIONS(`not_allowed_with_gregorian`), 그해에 그 번호의 윤달이 없으면 422 INVALID_INPUT(`leap_month_not_in_this_year`).",
            "example": "false"
          },
          {
            "name": "lunar_meridian",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "kr_kasi",
                "kr_standard_utc9",
                "kr_historical_offsets"
              ]
            },
            "description": "선택. 음력 변환의 기준 자오선. 생략하면 정책 기본값(`kr_kasi`, 한국천문연구원 공식 음양력)을 쓴다. 허용되지 않는 값이면 422 INVALID_INPUT(`not_allowed_by_policy`).",
            "example": "kr_kasi"
          },
          {
            "name": "policy_id",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "선택. 지원 날짜 범위·시간대·자오선 기본값을 읽을 정책. 생략하면 `kr-solar-v1`. 빈 값이면 422 INVALID_INPUT(`minLength`), 없는 정책이면 404 POLICY_NOT_FOUND."
          },
          {
            "name": "dataset_id",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "선택. 사용할 데이터 번들 ID(재현용 고정). 생략하면 서버 활성 번들. 빈 값이면 422 INVALID_INPUT(`minLength`), 없는 ID면 404 DATASET_NOT_FOUND, 로드 실패면 503 DATASET_UNAVAILABLE."
          }
        ],
        "responses": {
          "200": {
            "description": "변환 결과.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConvertResponse"
                },
                "examples": {
                  "chuseok_1985": {
                    "summary": "음력 1985-08-15 (추석)",
                    "description": "`GET /v1/convert?calendar=korean_lunar&date=1985-08-15`의 실제 응답이다.",
                    "value": {
                      "dataset_id": "kr-v3-2026c-de440s-0c26b6295d",
                      "policy_id": "kr-solar-v1",
                      "lunar_meridian": "kr_kasi",
                      "date": "1985-09-29",
                      "korean_lunar": {
                        "lunar_year": 1985,
                        "month": 8,
                        "is_leap": false,
                        "day": 15,
                        "lunar_meridian": "kr_kasi",
                        "assignment_quality": "stable"
                      },
                      "calendar_day_ganzhi": {
                        "cycle_index": 7,
                        "stem_index": 7,
                        "branch_index": 7,
                        "hangul": "신미",
                        "hanja": "辛未"
                      },
                      "warnings": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "현재 구현은 이 엔드포인트에서 400을 반환하지 않는다(GET 요청이라 본문을 해석하지 않는다). 계약 호환을 위해 남겨 둔 예약 항목이며, 쿼리 오류는 422로 응답한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY`: `X-API-Key`가 없거나, 형식이 잘못됐거나, 등록되지 않은 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "잘못된 키",
                    "value": {
                      "code": "INVALID_API_KEY",
                      "message": "API key is missing or invalid.",
                      "request_id": "14c69c2b54502ca6416f3b0efb926614",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`QUOTA_FORBIDDEN`: 폐기(revoked)된 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "quota_forbidden": {
                    "summary": "폐기된 키",
                    "value": {
                      "code": "QUOTA_FORBIDDEN",
                      "message": "The API key is not allowed to use this resource.",
                      "request_id": "dc7cbeffac51c720bae999a9c38a0424",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "`DATASET_NOT_FOUND`: 요청한 `dataset_id`가 서버에 없다(`details[0].field=dataset_id`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dataset_not_found": {
                    "summary": "알 수 없는 dataset_id",
                    "value": {
                      "code": "DATASET_NOT_FOUND",
                      "message": "The requested dataset_id is not available.",
                      "request_id": "0a52e40c267c86b2d8447c07b97e0ec8",
                      "details": [
                        {
                          "field": "dataset_id",
                          "reason": "unknown"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "파라미터 검증 실패: `calendar`·`date` 누락·형식, 존재하지 않는 양력·음력 날짜, 없는 윤달, 허용되지 않는 `lunar_meridian`, `gregorian`과 `lunar_leap_month` 동시 지정(CONFLICTING_OPTIONS), 지원 범위 밖(UNSUPPORTED_RANGE), 지원하지 않는 달력(UNSUPPORTED_CALENDAR), 쿼리 형식 오류.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`: 키별 요청률 한도를 넘었다. `Retry-After`만큼 기다린 뒤 재시도한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "요청률 초과",
                    "value": {
                      "code": "RATE_LIMITED",
                      "message": "Rate limit exceeded; retry later.",
                      "request_id": "c140271cdbe843985ae49d81930ed0d8",
                      "details": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "다시 시도하기 전 기다릴 초(정수, 1 이상). 키의 토큰 버킷에 다음 토큰이 생길 때까지 남은 시간을 초 단위로 올림한 값이다.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "example": 10
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`: 절기 시각을 렌더링하다 예기치 않은 오류가 났다. `request_id`로 문의한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "internal_error": {
                    "summary": "내부 오류",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "INTERNAL_ERROR",
                      "message": "Internal error.",
                      "request_id": "4d3c2b1a0f9e8d7c6b5a493827160514",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "`SERVICE_OVERLOADED`: 동시 계산 슬롯(명식 계산과 공유)이 약 50ms 안에 비지 않았다. `CALCULATION_TIMEOUT`: 계산 시한(기본 2초)을 넘겼다. `DATASET_UNAVAILABLE`: 지정한(또는 활성) 번들이 배포됐지만 로드·검증에 실패했다. 이 엔드포인트는 RULESET_UNAVAILABLE을 반환하지 않는다. 모두 그대로 재시도해도 안전하다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dataset_unavailable": {
                    "summary": "데이터 번들 미가용",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "DATASET_UNAVAILABLE",
                      "message": "Required dataset is not available.",
                      "request_id": "9f8e7d6c5b4a39281706f5e4d3c2b1a0",
                      "details": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/ganzhi": {
      "get": {
        "operationId": "getGanzhi",
        "summary": "날짜의 일진·연주·월주",
        "description": "한 날짜의 일진(日辰)과, 그 날짜가 시작하는 순간의 연주·월주, 그리고 그 날짜 안에서 절이 들어와 연주·월주가 바뀌면 그 시각과 바뀐 기둥을 돌려준다. 날짜의 소속은 물리 시각(TT)으로 정한다: 날짜는 그 시간대에서 그 날짜의 첫 순간부터 다음 날짜의 첫 순간 직전까지다(한국은 1908–1961년 시각 변경이 대부분 자정에 있었으므로 00:00이 없는 날이 있다).\n\n**시주는 내지 않는다** — 출생지 경도와 시계 기준에 따라 달라지므로 `POST /v1/charts`를 쓴다. `calendar_day_ganzhi`는 명식의 일주와 다를 수 있다(스키마 설명 참고).\n\n인증: `X-API-Key`. 멱등·결정적 조회다. 쿼리는 엄격하게 검사한다: 알 수 없는 파라미터는 422 INVALID_INPUT(`unknown_parameter`), 같은 파라미터 반복은 `repeated`, 빈 값은 `minLength`, 퍼센트 인코딩 오류는 `$`/`malformed_query`.\n\n날짜는 쿼리 문자열로 전달되므로 게이트웨이·마켓플레이스(RapidAPI 등)의 액세스 로그와 분석, 브라우저 기록에 남을 수 있다. 서버와 서버 앞 프록시는 쿼리 값을 로그에 남기지 않는다. 출생 정보처럼 URL에 남기면 안 되는 값은 `POST /v1/charts` 본문으로 보낸다(POST도 게이트웨이의 본문 로깅까지 막아 주지는 않는다).\n\n```\ncurl -H \"X-API-Key: $KEY\" 'https://api.perpetual.smartlink.ai.kr/v1/ganzhi?date=2024-02-04'\n```",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
            },
            "description": "필수. 조회할 날짜 `YYYY-MM-DD`(정확히 10자, 시각·오프셋 없음). `calendar=korean_lunar`이면 음력 날짜로 읽으므로 음력 2월 30일처럼 양력에 없는 날짜도 쓸 수 있다. 누락이면 422 INVALID_INPUT(`required`), 형식이 다르면 `pattern`, 양력에 없는 날짜면 `not_a_real_date`, 음력에 없는 달·날이면 `lunar_month_not_found`/`lunar_day_out_of_range`. 변환된 양력 날짜가 1900-01-01..2100-12-31 밖이면 422 UNSUPPORTED_RANGE(`supported_range`).",
            "example": "2024-02-04"
          },
          {
            "name": "calendar",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "gregorian",
                "korean_lunar"
              ],
              "default": "gregorian"
            },
            "description": "선택, 기본 `gregorian`. `date`의 달력. 그 밖의 값이면 422 UNSUPPORTED_CALENDAR(`unsupported`).",
            "example": "gregorian"
          },
          {
            "name": "lunar_leap_month",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "description": "선택. `calendar=korean_lunar`일 때 그 달이 윤달이면 `true`(생략하면 평달). `true`/`false` 외의 값이면 422 INVALID_INPUT(`type`), `gregorian`과 함께 오면 값과 관계없이 422 CONFLICTING_OPTIONS(`not_allowed_with_gregorian`), 그해에 그 번호의 윤달이 없으면 422 INVALID_INPUT(`leap_month_not_in_this_year`).",
            "example": "false"
          },
          {
            "name": "timezone",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "Asia/Seoul"
              ],
              "default": "Asia/Seoul"
            },
            "description": "선택, 기본 `Asia/Seoul`. 날짜의 경계(그 날짜의 00:00)를 정하는 IANA 시간대. 정책의 `supported_timezones` 밖이면 422 INVALID_INPUT(`not_allowed_by_policy`).",
            "example": "Asia/Seoul"
          },
          {
            "name": "lunar_meridian",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "kr_kasi",
                "kr_standard_utc9",
                "kr_historical_offsets"
              ]
            },
            "description": "선택. 음력 변환의 기준 자오선. 생략하면 정책 기본값(`kr_kasi`, 한국천문연구원 공식 음양력)을 쓴다. 허용되지 않는 값이면 422 INVALID_INPUT(`not_allowed_by_policy`).",
            "example": "kr_kasi"
          },
          {
            "name": "policy_id",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "선택. 지원 날짜 범위·시간대·자오선 기본값을 읽을 정책. 생략하면 `kr-solar-v1`. 빈 값이면 422 INVALID_INPUT(`minLength`), 없는 정책이면 404 POLICY_NOT_FOUND."
          },
          {
            "name": "dataset_id",
            "in": "query",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "선택. 사용할 데이터 번들 ID(재현용 고정). 생략하면 서버 활성 번들. 빈 값이면 422 INVALID_INPUT(`minLength`), 없는 ID면 404 DATASET_NOT_FOUND, 로드 실패면 503 DATASET_UNAVAILABLE."
          }
        ],
        "responses": {
          "200": {
            "description": "그 날짜의 일진·연주·월주와 절 전환.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GanzhiResponse"
                },
                "examples": {
                  "lichun_2024": {
                    "summary": "2024-02-04 (입춘 당일)",
                    "description": "`GET /v1/ganzhi?date=2024-02-04`의 실제 응답이다. 17:27:07에 입춘이 들어와 연주가 癸卯에서 甲辰으로, 월주가 乙丑에서 丙寅으로 바뀐다.",
                    "value": {
                      "dataset_id": "kr-v3-2026c-de440s-0c26b6295d",
                      "policy_id": "kr-solar-v1",
                      "lunar_meridian": "kr_kasi",
                      "timezone": "Asia/Seoul",
                      "date": "2024-02-04",
                      "korean_lunar": {
                        "lunar_year": 2023,
                        "month": 12,
                        "is_leap": false,
                        "day": 25,
                        "lunar_meridian": "kr_kasi",
                        "assignment_quality": "stable"
                      },
                      "calendar_day_ganzhi": {
                        "cycle_index": 34,
                        "stem_index": 4,
                        "branch_index": 10,
                        "hangul": "무술",
                        "hanja": "戊戌"
                      },
                      "day_start": {
                        "local_time": "2024-02-04T00:00:00.000000",
                        "instant": {
                          "tt_us": "760244469184000",
                          "utc": "2024-02-03T15:00:00.000000Z",
                          "reference_label": "2024-02-03T15:00:00.000000",
                          "reference_scale": "UTC",
                          "time_quality": "observed"
                        }
                      },
                      "day_end": {
                        "local_time": "2024-02-05T00:00:00.000000",
                        "instant": {
                          "tt_us": "760330869184000",
                          "utc": "2024-02-04T15:00:00.000000Z",
                          "reference_label": "2024-02-04T15:00:00.000000",
                          "reference_scale": "UTC",
                          "time_quality": "observed"
                        }
                      },
                      "at_start": {
                        "year": {
                          "cycle_index": 39,
                          "stem_index": 9,
                          "branch_index": 3,
                          "hangul": "계묘",
                          "hanja": "癸卯"
                        },
                        "month": {
                          "cycle_index": 1,
                          "stem_index": 1,
                          "branch_index": 1,
                          "hangul": "을축",
                          "hanja": "乙丑"
                        }
                      },
                      "transitions": [
                        {
                          "local_time": "2024-02-04T17:27:07.592529",
                          "term": {
                            "term_id": "2024-21",
                            "term_index": 21,
                            "name": "입춘",
                            "longitude_degrees": 315,
                            "is_jie": true,
                            "month_ordinal": 0,
                            "instant": {
                              "tt_us": "760307296776529",
                              "utc": "2024-02-04T08:27:07.592529Z",
                              "reference_label": "2024-02-04T08:27:07.592529",
                              "reference_scale": "UTC",
                              "time_quality": "observed"
                            },
                            "numeric_error_seconds": 1e-06,
                            "model_error_seconds": 1
                          },
                          "year": {
                            "cycle_index": 40,
                            "stem_index": 0,
                            "branch_index": 4,
                            "hangul": "갑진",
                            "hanja": "甲辰"
                          },
                          "month": {
                            "cycle_index": 2,
                            "stem_index": 2,
                            "branch_index": 2,
                            "hangul": "병인",
                            "hanja": "丙寅"
                          }
                        }
                      ],
                      "boundary_status": "stable",
                      "nearest_jie_seconds": 23572.407471,
                      "warnings": []
                    }
                  },
                  "midnight_gap_1948": {
                    "summary": "1948-06-01 (00:00이 없는 날)",
                    "description": "`GET /v1/ganzhi?date=1948-06-01`의 실제 응답이다. 서머타임 시작으로 00:00이 01:00으로 건너뛰어 날짜가 01:00에 시작한다. 1972년 이전이라 `boundary_status`는 `unknown`이다.",
                    "value": {
                      "dataset_id": "kr-v3-2026c-de440s-0c26b6295d",
                      "policy_id": "kr-solar-v1",
                      "lunar_meridian": "kr_kasi",
                      "timezone": "Asia/Seoul",
                      "date": "1948-06-01",
                      "korean_lunar": {
                        "lunar_year": 1948,
                        "month": 4,
                        "is_leap": false,
                        "day": 24,
                        "lunar_meridian": "kr_kasi",
                        "assignment_quality": "stable"
                      },
                      "calendar_day_ganzhi": {
                        "cycle_index": 53,
                        "stem_index": 3,
                        "branch_index": 5,
                        "hangul": "정사",
                        "hanja": "丁巳"
                      },
                      "day_start": {
                        "local_time": "1948-06-01T01:00:00.000000",
                        "instant": {
                          "tt_us": "-1627937971622686",
                          "utc": null,
                          "reference_label": "1948-05-31T15:00:00.000000",
                          "reference_scale": "UT1_APPROX",
                          "time_quality": "historical_approximation"
                        }
                      },
                      "day_end": {
                        "local_time": "1948-06-02T00:00:00.000000",
                        "instant": {
                          "tt_us": "-1627855171621806",
                          "utc": null,
                          "reference_label": "1948-06-01T14:00:00.000000",
                          "reference_scale": "UT1_APPROX",
                          "time_quality": "historical_approximation"
                        }
                      },
                      "at_start": {
                        "year": {
                          "cycle_index": 24,
                          "stem_index": 4,
                          "branch_index": 0,
                          "hangul": "무자",
                          "hanja": "戊子"
                        },
                        "month": {
                          "cycle_index": 53,
                          "stem_index": 3,
                          "branch_index": 5,
                          "hangul": "정사",
                          "hanja": "丁巳"
                        }
                      },
                      "transitions": [],
                      "boundary_status": "unknown",
                      "nearest_jie_seconds": 364820.022583,
                      "warnings": [
                        "HISTORICAL_TIME_APPROXIMATION",
                        "MODEL_ERROR_UNKNOWN"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "현재 구현은 이 엔드포인트에서 400을 반환하지 않는다(GET 요청이라 본문을 해석하지 않는다). 계약 호환을 위해 남겨 둔 예약 항목이며, 쿼리 오류는 422로 응답한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "`INVALID_API_KEY`: `X-API-Key`가 없거나, 형식이 잘못됐거나, 등록되지 않은 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "invalid_api_key": {
                    "summary": "잘못된 키",
                    "value": {
                      "code": "INVALID_API_KEY",
                      "message": "API key is missing or invalid.",
                      "request_id": "14c69c2b54502ca6416f3b0efb926614",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "`QUOTA_FORBIDDEN`: 폐기(revoked)된 키다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "quota_forbidden": {
                    "summary": "폐기된 키",
                    "value": {
                      "code": "QUOTA_FORBIDDEN",
                      "message": "The API key is not allowed to use this resource.",
                      "request_id": "dc7cbeffac51c720bae999a9c38a0424",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "`DATASET_NOT_FOUND`: 요청한 `dataset_id`가 서버에 없다(`details[0].field=dataset_id`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dataset_not_found": {
                    "summary": "알 수 없는 dataset_id",
                    "value": {
                      "code": "DATASET_NOT_FOUND",
                      "message": "The requested dataset_id is not available.",
                      "request_id": "0a52e40c267c86b2d8447c07b97e0ec8",
                      "details": [
                        {
                          "field": "dataset_id",
                          "reason": "unknown"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "파라미터 검증 실패: `date` 누락·형식, 존재하지 않는 날짜, 없는 윤달, 허용되지 않는 `timezone`·`lunar_meridian`, `gregorian`과 `lunar_leap_month` 동시 지정(CONFLICTING_OPTIONS), 지원 범위 밖(UNSUPPORTED_RANGE), 지원하지 않는 달력(UNSUPPORTED_CALENDAR), 쿼리 형식 오류.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "`RATE_LIMITED`: 키별 요청률 한도를 넘었다. `Retry-After`만큼 기다린 뒤 재시도한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "rate_limited": {
                    "summary": "요청률 초과",
                    "value": {
                      "code": "RATE_LIMITED",
                      "message": "Rate limit exceeded; retry later.",
                      "request_id": "c140271cdbe843985ae49d81930ed0d8",
                      "details": []
                    }
                  }
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "다시 시도하기 전 기다릴 초(정수, 1 이상). 키의 토큰 버킷에 다음 토큰이 생길 때까지 남은 시간을 초 단위로 올림한 값이다.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                },
                "example": 10
              }
            }
          },
          "500": {
            "description": "`INTERNAL_ERROR`: 절기 시각을 렌더링하다 예기치 않은 오류가 났다. `request_id`로 문의한다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "internal_error": {
                    "summary": "내부 오류",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "INTERNAL_ERROR",
                      "message": "Internal error.",
                      "request_id": "4d3c2b1a0f9e8d7c6b5a493827160514",
                      "details": []
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "`SERVICE_OVERLOADED`: 동시 계산 슬롯(명식 계산과 공유)이 약 50ms 안에 비지 않았다. `CALCULATION_TIMEOUT`: 계산 시한(기본 2초)을 넘겼다. `DATASET_UNAVAILABLE`: 지정한(또는 활성) 번들이 배포됐지만 로드·검증에 실패했다. 이 엔드포인트는 RULESET_UNAVAILABLE을 반환하지 않는다. 모두 그대로 재시도해도 안전하다.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "dataset_unavailable": {
                    "summary": "데이터 번들 미가용",
                    "description": "재현이 어려운 조건이라 서버 코드(internal/apierr)의 고정 메시지로 구성한 형식 예시다.",
                    "value": {
                      "code": "DATASET_UNAVAILABLE",
                      "message": "Required dataset is not available.",
                      "request_id": "9f8e7d6c5b4a39281706f5e4d3c2b1a0",
                      "details": []
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "운영 CLI(`keyctl issue`)로 발급한 API 키(`sk_` 접두, 35~128자). `/v1/*` 호출마다 `X-API-Key` 헤더로 보낸다. 누락·형식 오류·미등록이면 401 INVALID_API_KEY, 폐기된 키면 403 QUOTA_FORBIDDEN이다. scope나 role은 없고, 활성 키는 모든 `/v1/*` 엔드포인트를 호출할 수 있다. 키마다 tenant와 요청률 한도(토큰 버킷)가 지정된다. 키는 비밀 정보이므로 URL·로그·클라이언트 번들에 넣지 않는다."
      }
    },
    "schemas": {
      "Birth": {
        "type": "object",
        "properties": {
          "calendar": {
            "type": "string",
            "enum": [
              "gregorian",
              "korean_lunar"
            ],
            "description": "필수. 달력 체계. `gregorian`은 `local_datetime`의 날짜부를 그레고리력으로, `korean_lunar`는 한국 음력으로 읽는다. 음력일 때 날짜부는 그레고리력으로 실재하지 않아도 되며(예: 음력 2월 30일), 연도 범위 검사는 변환된 양력 날짜에 적용한다. 다른 문자열은 스키마 enum 검사보다 먼저 판정해 422 UNSUPPORTED_CALENDAR를 반환한다.",
            "examples": [
              "gregorian"
            ]
          },
          "local_datetime": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d$",
            "description": "필수. 시간대 오프셋 없는 민간 벽시계 시각 `YYYY-MM-DDTHH:mm:ss`. 실제 존재하는 그레고리력 날짜, 민간 연도 1900~2100, 초 00~59여야 한다. Z나 오프셋, 소수 초, 윤초 `:60`, `24:00`, 초 생략은 허용하지 않는다. 패턴 위반은 422 INVALID_INPUT(`pattern`), 실재하지 않는 날짜(예: 2001-02-29)는 422 INVALID_INPUT(`not_a_real_datetime`), 연도 범위 밖은 422 UNSUPPORTED_RANGE(`year_1900_2100`)다. 서머타임 등으로 존재하지 않는 시각은 422 NONEXISTENT_LOCAL_TIME, 두 번 존재하는 시각은 `fold`가 필요하다. `calendar`가 `korean_lunar`이면 날짜부를 음력으로 읽는다. 이때 그레고리력으로 실재하는 날짜일 필요가 없고(음력 2월 30일은 정상), 지원 연도 검사는 **변환된 양력 날짜**에 적용한다(음력 1899-12-01은 양력 1900-01-01이므로 유효하다). 그 음력 날짜가 없으면 422 INVALID_INPUT이며 사유는 `lunar_month_not_found`(표에 없는 연·월), `lunar_day_out_of_range`(그 달의 일수를 넘음), `leap_month_not_in_this_year`(그 해에 그런 윤달이 없음)다.",
            "examples": [
              "2000-01-07T12:00:00"
            ]
          },
          "timezone": {
            "type": "string",
            "enum": [
              "Asia/Seoul"
            ],
            "description": "필수. IANA 시간대 ID. 1차는 `Asia/Seoul`만 지원한다. 과거 표준시 변경과 서머타임(예: 1987~1988년)은 번들에 고정된 tzdb(`provenance.tzdb_id`)로 반영한다.",
            "examples": [
              "Asia/Seoul"
            ]
          },
          "longitude": {
            "type": "number",
            "minimum": 124,
            "maximum": 132,
            "description": "필수. 출생지 경도(도, 동경 양수), 124~132. 소수 6자리(마이크로도)까지 쓰고 그 아래 자릿수는 반올림한다. 지수 표기도 받는다. 숫자 표기는 32자 이내, 지수는 ±20 이내여야 한다(넘으면 422 INVALID_INPUT `number_too_large`). 기본값이 없고 서울 경도를 묵시 대입하지 않는다. 태양시 모드에서 경도 보정(경도×240초)에 쓴다.",
            "examples": [
              126.978
            ]
          },
          "latitude": {
            "type": "number",
            "minimum": 33,
            "maximum": 39,
            "description": "선택. 출생지 위도(도, 북위 양수), 33~39. 현재 계산(절기, 태양시)에는 쓰지 않아 결과에 영향이 없다. 국경 판정에도 쓰지 않는다. 기본값 없음.",
            "examples": [
              37.5665
            ]
          },
          "fold": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1,
            "description": "선택. 서머타임 종료 등으로 같은 벽시계 시각이 두 번 있을 때 어느 쪽인지 고른다. 0=먼저 발생, 1=나중 발생. 중복 시각인데 생략하면 422 AMBIGUOUS_LOCAL_TIME(details에 fold별 후보), 중복이 아닌 시각에 1이면 422 INVALID_FOLD다. 중복이 아닌 시각은 생략하거나 0을 보낸다.",
            "examples": [
              1
            ]
          },
          "uncertainty_seconds": {
            "type": "number",
            "minimum": 0,
            "maximum": 3600,
            "default": 0,
            "description": "선택, 기본 0. 입력 시각의 대칭 ±범위(초), 0~3600. 소수를 받으며 마이크로초 단위로 반올림한다. 서버는 범위 안의 모든 절입, 일·시 경계, 시간대 전환에서 구간을 나눠 가능한 기둥 후보를 `uncertainty.candidates`로 반환한다. 0은 불확실 범위를 지정하지 않았다는 뜻일 뿐 기록의 정확성을 보증하지 않는다. 후보가 너무 많으면 422 UNCERTAINTY_TOO_WIDE다.",
            "examples": [
              60
            ]
          },
          "longitude_uncertainty_degrees": {
            "type": "number",
            "minimum": 0,
            "maximum": 0.5,
            "default": 0,
            "description": "선택, 기본 0. 경도의 대칭 ±범위(도), 0~0.5. 1°당 240초로 계산 시간축 오차에 전파된다. `longitude ± 이 값`이 124~132를 벗어나면 422 INVALID_INPUT(`range_exceeds_supported_longitudes`)이다.",
            "examples": [
              0.01
            ]
          },
          "lunar_leap_month": {
            "type": "boolean",
            "description": "선택. `calendar`가 `korean_lunar`일 때만 허용한다. 그 달이 윤달임을 뜻하며, 생략하면 평달이다. 윤달은 직전 달과 같은 번호를 쓰므로 월 번호만으로는 구별되지 않는다. 스키마 기본값을 두지 않는다(생략과 false를 구분하지 않되 명시적 false도 허용). `gregorian`과 함께 보내면 422 CONFLICTING_OPTIONS(`not_allowed_with_gregorian`), 그 음력 연도에 해당 윤달이 없으면 422 INVALID_INPUT(`leap_month_not_in_this_year`)이다.",
            "examples": [
              true
            ]
          }
        },
        "required": [
          "calendar",
          "local_datetime",
          "timezone",
          "longitude"
        ],
        "additionalProperties": false,
        "description": "출생 기록. 시간대 오프셋 없는 민간(벽시계) 시각, IANA 시간대, 동경 경도로 구성된다. 정의되지 않은 필드는 422 INVALID_INPUT(`additionalProperties`)이다."
      },
      "Options": {
        "type": "object",
        "properties": {
          "clock_basis": {
            "type": "string",
            "enum": [
              "civil",
              "local_mean_solar",
              "local_apparent_solar"
            ],
            "description": "선택, 기본 policy 값(`kr-solar-v1`: `local_apparent_solar`). 일주·시주를 판정할 시간축이다. `civil`: 입력 민간시 그대로(서머타임 포함 벽시계). `local_mean_solar`: 지방평균태양시(UT1 + 경도×240초). `local_apparent_solar`: 진태양시(평균태양시 + 균시차). 연주·월주는 항상 절입 순간(TT) 기준이라 영향을 받지 않는다.",
            "examples": [
              "local_apparent_solar"
            ]
          },
          "day_boundary": {
            "type": "string",
            "enum": [
              "midnight",
              "zi_start_23",
              "split_zi"
            ],
            "description": "선택, 기본 policy 값(`kr-solar-v1`: `midnight`). 날 경계와 자시 처리 방식이며 `clock_basis` 시간축으로 판정한다. `midnight`: 00시에 날이 바뀐다. `zi_start_23`: 23시 이후는 일주와 시간 천간 모두 다음 날 기준. `split_zi`: 일주는 당일을 유지하고 23시 이후 시간 천간만 다음 날 기준(야자시 분리).",
            "examples": [
              "midnight"
            ]
          },
          "lunar_meridian": {
            "type": "string",
            "enum": [
              "kr_kasi",
              "kr_standard_utc9",
              "kr_historical_offsets"
            ],
            "description": "선택. 음력 날짜를 읽을 기준 자오선. 생략하면 정책 기본값(`GET /v1/policies`의 `defaults.lunar_meridian`, 현재 `kr_kasi`)을 쓴다. `kr_kasi`는 한국천문연구원(KASI) 공식 음양력과 같은 기준으로 1911-12-31까지 UTC+8, 1912-01-01부터 UTC+9이며 1900-01-01~2050-12-13의 모든 날짜에서 KASI 발표값과 일치한다(KASI 자료는 2050년까지이며 마지막 달의 끝을 확정할 수 없어 그 이후는 계산 확장). `kr_standard_utc9`는 전 구간 UTC+9, `kr_historical_offsets`는 그 시대 한국 민간시(1908 이전 LMT +8:27:52, 1954~1961 +8:30, 서머타임 포함)다. 다른 자오선을 지정하면 그 기준으로만 계산하며 기본값과의 차이를 후보로 내지 않는다. 정책이 허용하지 않는 값이면 422 INVALID_INPUT(`not_allowed_by_policy`).",
            "examples": [
              "kr_kasi"
            ]
          }
        },
        "required": [],
        "additionalProperties": false,
        "description": "계산 옵션 재지정. 생략한 항목은 policy 기본값(`GET /v1/policies`의 `defaults`)을 쓴다. 정책의 `allowed_clock_bases` / `allowed_day_boundaries`에 없는 값은 조용히 무시하지 않고 422 INVALID_INPUT(`not_allowed_by_policy`)으로 거절한다."
      },
      "DaewoonRequest": {
        "type": "object",
        "properties": {
          "direction": {
            "type": "string",
            "enum": [
              "forward",
              "reverse",
              "by_year_stem_and_sex"
            ],
            "description": "필수. 대운 방향. `forward`=순행, `reverse`=역행, `by_year_stem_and_sex`=사주 연간(입춘 기준)의 음양과 `sex_for_rule`로 정한다(양간 남성·음간 여성은 순행, 그 외 역행).",
            "examples": [
              "forward"
            ]
          },
          "sex_for_rule": {
            "type": "string",
            "enum": [
              "male",
              "female"
            ],
            "description": "조건부 필수. `direction=by_year_stem_and_sex`일 때만 필수이며, 없으면 422 DAEWOON_SEX_REQUIRED다. forward/reverse와 함께 보내면 422 CONFLICTING_OPTIONS다. 순역 결정 규칙의 입력으로만 쓰고 다른 계산에는 쓰지 않는다. 기본값 없음.",
            "examples": [
              "female"
            ]
          },
          "count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "default": 10,
            "description": "선택, 기본 10(policy `daewoon_count`). 반환할 대운 개수, 1~12.",
            "examples": [
              10
            ]
          },
          "display_rounding": {
            "type": "string",
            "enum": [
              "floor",
              "ceil",
              "half_up"
            ],
            "default": "half_up",
            "description": "선택, 기본 `half_up`(policy 값). 정수 대운수(`display_age`) 표시에만 쓰는 반올림 방식이다. `floor`=내림, `ceil`=올림, `half_up`=반올림. 대운 시작 시점은 반올림하지 않은 유리수 원값으로 계산한다.",
            "examples": [
              "half_up"
            ]
          }
        },
        "required": [
          "direction"
        ],
        "additionalProperties": false,
        "allOf": [
          {
            "if": {
              "properties": {
                "direction": {
                  "const": "by_year_stem_and_sex"
                }
              }
            },
            "then": {
              "required": [
                "sex_for_rule"
              ]
            },
            "else": {
              "not": {
                "required": [
                  "sex_for_rule"
                ]
              }
            }
          }
        ],
        "description": "대운 계산 요청. `ChartRequest.daewoon`을 생략하면 대운을 계산하지 않는다(응답 `daewoon: null`). `sex_for_rule`은 `direction=by_year_stem_and_sex`일 때만 필수이고 그 외에는 허용하지 않는다."
      },
      "ChartRequest": {
        "type": "object",
        "properties": {
          "birth": {
            "$ref": "#/components/schemas/Birth",
            "description": "필수. 출생 기록."
          },
          "policy_id": {
            "type": "string",
            "enum": [
              "kr-solar-v1"
            ],
            "default": "kr-solar-v1",
            "description": "선택, 기본 `kr-solar-v1`. 불변 계산 프로파일 ID. 정책은 데이터 번들에 포함되므로, 지정한(생략 시 활성) dataset에 이 ID의 승인 정책이 없으면 스키마 enum 검사보다 먼저 판정해 404 POLICY_NOT_FOUND를 반환한다. 재현하려면 이전 응답의 `provenance.policy_id`를 지정한다.",
            "examples": [
              "kr-solar-v1"
            ]
          },
          "dataset_id": {
            "type": "string",
            "minLength": 1,
            "description": "선택, 생략하면 서버 활성 번들(`GET /v1/policies`의 `active_dataset_id`)을 쓴다. 불변 천문·시간척도 데이터 번들 ID다. 서버에 없는 ID는 404 DATASET_NOT_FOUND, 빈 문자열은 422 INVALID_INPUT(`minLength`), 배포됐지만 로드·검증에 실패한 번들은 503 DATASET_UNAVAILABLE이다. 재현하려면 이전 응답의 `provenance.dataset_id`를 지정한다.",
            "examples": [
              "kr-2026c-de440s-d9750dac2a"
            ]
          },
          "options": {
            "$ref": "#/components/schemas/Options",
            "description": "선택. `clock_basis` / `day_boundary` 재지정. 생략하면 policy 기본값."
          },
          "daewoon": {
            "$ref": "#/components/schemas/DaewoonRequest",
            "description": "선택. 대운 계산 요청. 생략하면 대운을 계산하지 않는다."
          },
          "include_derived": {
            "type": "boolean",
            "default": false,
            "description": "선택, 기본 false. true면 십신·지장간 파생 정보(`derived`)를 계산한다. 정책의 규칙표가 승인되지 않았으면 503 RULESET_UNAVAILABLE이다(`GET /v1/policies`의 `derived_available`로 미리 확인). 미승인 규칙으로 계산한 결과를 성공으로 위장하지 않는다.",
            "examples": [
              false
            ]
          }
        },
        "required": [
          "birth"
        ],
        "additionalProperties": false,
        "description": "`POST /v1/charts` 요청 본문. 필수는 `birth`뿐이고, 나머지는 생략하면 policy 기본값을 쓴다. 정의되지 않은 필드는 422 INVALID_INPUT(`additionalProperties`), 중복 키는 400 MALFORMED_JSON이다."
      },
      "ResolvedPolicy": {
        "type": "object",
        "properties": {
          "policy_id": {
            "type": "string",
            "minLength": 1,
            "description": "적용된 정책 ID."
          },
          "clock_basis": {
            "type": "string",
            "enum": [
              "civil",
              "local_mean_solar",
              "local_apparent_solar"
            ],
            "description": "적용된 일·시주 시간축(`Options.clock_basis` 참고)."
          },
          "day_boundary": {
            "type": "string",
            "enum": [
              "midnight",
              "zi_start_23",
              "split_zi"
            ],
            "description": "적용된 날 경계·자시 처리(`Options.day_boundary` 참고)."
          },
          "year_boundary": {
            "type": "string",
            "enum": [
              "lichun_instant"
            ],
            "description": "연주 경계. `lichun_instant`: 입춘 순간(TT)에 해가 바뀐다."
          },
          "month_boundary": {
            "type": "string",
            "enum": [
              "jie_instant"
            ],
            "description": "월주 경계. `jie_instant`: 12절(節) 절입 순간에 달이 바뀐다."
          },
          "solar_definition": {
            "type": "string",
            "enum": [
              "geocentric_apparent_of_date"
            ],
            "description": "절기 황경 정의. `geocentric_apparent_of_date`: 지구 중심 겉보기 태양 황경, 날짜의 황도·분점 기준."
          },
          "pre_1972_mode": {
            "type": "string",
            "enum": [
              "civil_ut1_approx"
            ],
            "description": "1972년 이전 시각 처리. `civil_ut1_approx`: 오프셋을 뺀 민간시를 UT1 근사로 보고 모델 ΔT로 TT를 구한다(`utc=null`, `reference_scale=UT1_APPROX`)."
          },
          "future_utc_mode": {
            "type": "string",
            "enum": [
              "hold_last_announced_tai_minus_utc"
            ],
            "description": "윤초 확정 범위 이후 처리. `hold_last_announced_tai_minus_utc`: 마지막으로 공표된 TAI−UTC를 유지한 잠정 UTC(`UTC_PROVISIONAL`)와 예측 ΔT를 쓴다."
          },
          "term_equality": {
            "type": "string",
            "enum": [
              "inclusive_zero"
            ],
            "description": "출생 순간이 절입 순간과 같을 때의 처리. `inclusive_zero`: 절입 순간부터 새 연·월을 적용하고, 대운 간격 Δ는 0이다."
          },
          "daewoon_duration_scale": {
            "type": "string",
            "enum": [
              "TT"
            ],
            "description": "대운 간격 시간척도. `TT`: 연속 SI초(시계 표시의 균시차 차이를 섞지 않음)."
          },
          "daewoon_start_method": {
            "type": "string",
            "enum": [
              "mean_gregorian_year"
            ],
            "description": "대운 실제 시작 순간의 환산 규칙. `mean_gregorian_year`: 출생 TT + 시작 나이 × 365.2425일."
          },
          "year_length_days": {
            "const": "365.2425",
            "type": "string",
            "description": "환산에 쓰는 1년 길이(일), 정밀도 보존을 위한 10진 문자열. 그레고리력 평균 연 길이이며 제품 환산 규칙이다(회귀년 값이 아님)."
          },
          "display_rounding": {
            "type": "string",
            "enum": [
              "floor",
              "ceil",
              "half_up"
            ],
            "description": "적용된 대운수 표시 반올림 방식."
          },
          "daewoon_count": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "적용된 대운 개수(요청 `count` 또는 policy 기본값)."
          },
          "hemisphere_rule": {
            "type": "string",
            "enum": [
              "no_shift"
            ],
            "description": "남반구 월주 보정. `no_shift`: 보정하지 않는다(1차)."
          },
          "ruleset_id": {
            "type": "string",
            "minLength": 1,
            "description": "정책이 가리키는 파생 정보 규칙표 ID. `include_derived`와 관계없이 항상 표시한다."
          },
          "include_derived": {
            "type": "boolean",
            "description": "적용된 `include_derived` 값."
          },
          "requested_daewoon_direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "forward",
              "reverse",
              "by_year_stem_and_sex",
              null
            ],
            "description": "요청한 `daewoon.direction` 원값. 대운을 요청하지 않았으면 null. 계산 결과 방향(순행/역행)은 `daewoon.direction`에 있다."
          },
          "sex_for_rule": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "male",
              "female",
              null
            ],
            "description": "요청한 `daewoon.sex_for_rule`. 지정하지 않았거나 대운을 요청하지 않았으면 null."
          },
          "lunar_meridian": {
            "type": "string",
            "enum": [
              "kr_kasi",
              "kr_standard_utc9",
              "kr_historical_offsets"
            ],
            "description": "음력 날짜를 읽은 기준 자오선. 양력 요청에도 채워지며, 같은 답을 재현하려면 함께 고정해야 한다. 기본값 `kr_kasi`는 KASI 공식 음양력 발표값과 1900-01-01~2050-12-13의 모든 날짜에서 일치한다(대조 게이트 V2). 그 이후는 계산 확장이다."
          }
        },
        "required": [
          "policy_id",
          "clock_basis",
          "day_boundary",
          "year_boundary",
          "month_boundary",
          "solar_definition",
          "pre_1972_mode",
          "future_utc_mode",
          "term_equality",
          "daewoon_duration_scale",
          "daewoon_start_method",
          "year_length_days",
          "display_rounding",
          "daewoon_count",
          "hemisphere_rule",
          "ruleset_id",
          "include_derived",
          "requested_daewoon_direction",
          "sex_for_rule",
          "lunar_meridian"
        ],
        "additionalProperties": false,
        "description": "실제 계산에 적용된 정책 값 전체(요청의 재지정 + policy 기본값). 계산 응답의 `provenance.resolved_policy`와 `GET /v1/policies`의 `defaults`가 이 형식이다. `defaults`에서는 요청 기록 필드가 null이다."
      },
      "Instant": {
        "type": "object",
        "properties": {
          "tt_us": {
            "type": "string",
            "pattern": "^-?(0|[1-9][0-9]*)$",
            "description": "J2000.0 TT(JD_TT 2451545.0, 2000-01-01T12:00:00 TT)부터의 경과 마이크로초. 64비트 정수를 JavaScript에서도 정밀하게 다루도록 10진 문자열로 보낸다(음수 가능).",
            "examples": [
              "486064184000"
            ]
          },
          "utc": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "description": "UTC 시각(RFC 3339, 소수 6자리, `Z`). 1972년 이전(`UT1_APPROX`)은 null이다. 윤초 확정 범위 이후는 잠정값이다(`reference_scale=UTC_PROVISIONAL`)."
          },
          "reference_label": {
            "type": "string",
            "description": "`reference_scale` 척도의 달력 라벨(`YYYY-MM-DDTHH:mm:ss.ffffff`). 시간대 접미사가 없고 소수 초가 붙을 수 있다."
          },
          "reference_scale": {
            "type": "string",
            "enum": [
              "UTC",
              "UTC_PROVISIONAL",
              "UT1_APPROX"
            ],
            "description": "라벨의 시간척도. `UTC`=관측 UTC, `UTC_PROVISIONAL`=윤초 확정 범위 이후의 잠정 UTC, `UT1_APPROX`=1972년 이전의 민간시 기반 UT1 근사."
          },
          "time_quality": {
            "type": "string",
            "enum": [
              "observed",
              "historical_approximation",
              "predicted"
            ],
            "description": "시간 변환의 품질. `observed`=관측 자료 기반(출생 기록 자체가 정확하다는 뜻은 아님), `historical_approximation`=1972년 이전 근사, `predicted`=미래 예측 모델."
          }
        },
        "required": [
          "tt_us",
          "utc",
          "reference_label",
          "reference_scale",
          "time_quality"
        ],
        "additionalProperties": false,
        "description": "하나의 물리적 순간. TT 정수 값과 UTC 또는 기준 척도 라벨로 표현한다.",
        "examples": [
          {
            "tt_us": "486064184000",
            "utc": "2000-01-07T03:00:00.000000Z",
            "reference_label": "2000-01-07T03:00:00.000000",
            "reference_scale": "UTC",
            "time_quality": "observed"
          }
        ]
      },
      "Pillar": {
        "type": "object",
        "properties": {
          "cycle_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 59,
            "description": "60갑자 인덱스(0=갑자 … 59=계해). `stem_index = cycle_index mod 10`, `branch_index = cycle_index mod 12`."
          },
          "stem_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9,
            "description": "천간 인덱스 0~9 = 갑 을 병 정 무 기 경 신 임 계. 짝수는 양간이다."
          },
          "branch_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 11,
            "description": "지지 인덱스 0~11 = 자 축 인 묘 진 사 오 미 신 유 술 해."
          },
          "hangul": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "한글 두 글자 표기."
          },
          "hanja": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "한자 두 글자 표기."
          }
        },
        "required": [
          "cycle_index",
          "stem_index",
          "branch_index",
          "hangul",
          "hanja"
        ],
        "additionalProperties": false,
        "description": "60갑자 기둥 하나. `cycle_index`, `stem_index`, `branch_index`, 한글, 한자는 모두 같은 갑자를 가리킨다.",
        "examples": [
          {
            "cycle_index": 0,
            "stem_index": 0,
            "branch_index": 0,
            "hangul": "갑자",
            "hanja": "甲子"
          }
        ]
      },
      "Pillars": {
        "type": "object",
        "properties": {
          "year": {
            "$ref": "#/components/schemas/Pillar",
            "description": "연주. 입춘 순간 기준 연도로 정한다(1월과 입춘 전 출생은 이전 해)."
          },
          "month": {
            "$ref": "#/components/schemas/Pillar",
            "description": "월주. 12절 절입 순간을 기준으로 정한다. 월간은 연간에서 유도한다."
          },
          "day": {
            "$ref": "#/components/schemas/Pillar",
            "description": "일주. `clock_basis` 시간축의 날짜에 `day_boundary`를 적용한 날짜의 JDN으로 정한다."
          },
          "hour": {
            "$ref": "#/components/schemas/Pillar",
            "description": "시주. `clock_basis` 시간축의 시각으로 정한다. 시지 경계는 23·01·03…21시이고, 시간 천간은 `basis.hour_basis_day_date`의 일간에서 유도한다."
          }
        },
        "required": [
          "year",
          "month",
          "day",
          "hour"
        ],
        "additionalProperties": false,
        "description": "사주팔자(연·월·일·시주)."
      },
      "Term": {
        "type": "object",
        "properties": {
          "term_id": {
            "type": "string",
            "minLength": 1,
            "description": "`{기준 렌더링 연도}-{term_index 2자리}` 형식 ID(예: `2026-21` = 2026년 입춘)."
          },
          "term_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 23,
            "description": "절기 번호 0~23. 춘분(0, 황경 0°)부터 15°씩: 0 춘분, 1 청명, 2 곡우, 3 입하, 4 소만, 5 망종, 6 하지, 7 소서, 8 대서, 9 입추, 10 처서, 11 백로, 12 추분, 13 한로, 14 상강, 15 입동, 16 소설, 17 대설, 18 동지, 19 소한, 20 대한, 21 입춘, 22 우수, 23 경칩."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "한글 절기 이름."
          },
          "longitude_degrees": {
            "type": "integer",
            "minimum": 0,
            "maximum": 345,
            "description": "태양 겉보기 황경(도) = `term_index × 15`."
          },
          "is_jie": {
            "type": "boolean",
            "description": "true면 12절(節)로 월주 경계다. false면 12중기(中氣)다."
          },
          "month_ordinal": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 11
              },
              {
                "type": "null"
              }
            ],
            "description": "절(節)의 월 번호. 인월=0(입춘), 묘월=1(경칩) … 축월=11(소한). 중기는 null."
          },
          "instant": {
            "$ref": "#/components/schemas/Instant",
            "description": "절입 순간."
          },
          "numeric_error_seconds": {
            "type": "number",
            "minimum": 0,
            "description": "근 탐색의 수치 수렴 오차 상한(초)."
          },
          "model_error_seconds": {
            "anyOf": [
              {
                "type": "number",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ],
            "description": "천문·시간척도 모델 오차 상한(초). 알 수 없으면 null이며, 이 경우 `boundary_status`가 unknown이 될 수 있다."
          }
        },
        "required": [
          "term_id",
          "term_index",
          "name",
          "longitude_degrees",
          "is_jie",
          "month_ordinal",
          "instant",
          "numeric_error_seconds",
          "model_error_seconds"
        ],
        "additionalProperties": false,
        "description": "절기 사건 하나.",
        "examples": [
          {
            "term_id": "1999-21",
            "term_index": 21,
            "name": "입춘",
            "longitude_degrees": 315,
            "is_jie": true,
            "month_ordinal": 0,
            "instant": {
              "tt_us": "-28616513803801",
              "utc": "1999-02-04T06:57:02.012199Z",
              "reference_label": "1999-02-04T06:57:02.012199",
              "reference_scale": "UTC",
              "time_quality": "observed"
            },
            "numeric_error_seconds": 1e-06,
            "model_error_seconds": 1
          }
        ]
      },
      "TimeResult": {
        "type": "object",
        "properties": {
          "civil_label": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d$",
            "description": "정규화된 입력 민간 시각(시간대 없는 라벨)."
          },
          "timezone": {
            "type": "string",
            "minLength": 1,
            "description": "적용한 IANA 시간대."
          },
          "total_offset_seconds": {
            "type": "integer",
            "description": "그 순간 적용된 총 UTC 오프셋(초, 서머타임 포함). 예: KST 32400, KDT 36000."
          },
          "dst_offset_seconds": {
            "type": "integer",
            "description": "총 오프셋 중 서머타임 몫(초). 표준시면 0."
          },
          "fold": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1,
            "description": "적용된 fold(0 또는 1). 중복 시각이 아니면 0."
          },
          "instant": {
            "$ref": "#/components/schemas/Instant",
            "description": "출생 순간."
          },
          "clock_label": {
            "type": "string",
            "description": "`clock_basis` 시간축의 계산용 달력 라벨. 실제 순간이 아니라 계산 좌표이므로 Z나 오프셋을 붙이지 않는다. 소수 초가 붙을 수 있고 날짜 이동도 보존한다."
          },
          "clock_basis": {
            "type": "string",
            "enum": [
              "civil",
              "local_mean_solar",
              "local_apparent_solar"
            ],
            "description": "적용한 시간축."
          },
          "equation_of_time_seconds": {
            "anyOf": [
              {
                "type": "number",
                "minimum": -1800,
                "maximum": 1800
              },
              {
                "type": "null"
              }
            ],
            "description": "균시차 E(초, 진태양시 − 평균태양시). `local_apparent_solar`일 때만 값이 있고 그 외에는 null."
          },
          "dut1_seconds": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "UT1 − UTC(초). 태양시 모드이면서 1972년 이후일 때만 값이 있다. civil 모드나 1972년 이전은 null."
          },
          "longitude_correction_seconds": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "경도항(초) = `longitude × 240 − (total_offset_seconds − dst_offset_seconds)`. 당시 표준 오프셋에 대한 보정이며 서머타임은 별도다. civil 모드는 null."
          },
          "total_clock_correction_seconds": {
            "type": "number",
            "description": "`clock_label − civil_label`(초, 날짜 이동 포함). civil 모드는 0."
          },
          "lunar_date": {
            "$ref": "#/components/schemas/LunarDate",
            "description": "그 민간 날짜의 음력 표기. 요청 달력과 무관하게 항상 채운다."
          }
        },
        "required": [
          "civil_label",
          "timezone",
          "total_offset_seconds",
          "dst_offset_seconds",
          "fold",
          "instant",
          "clock_label",
          "clock_basis",
          "equation_of_time_seconds",
          "dut1_seconds",
          "longitude_correction_seconds",
          "total_clock_correction_seconds",
          "lunar_date"
        ],
        "additionalProperties": false,
        "description": "입력 시각의 해석 결과(시간대, 오프셋, 순간)와 일·시주 계산에 쓴 시간축 라벨."
      },
      "Basis": {
        "type": "object",
        "properties": {
          "lichun": {
            "$ref": "#/components/schemas/Term",
            "description": "연주 판정에 쓴 입춘(출생 순간 이전 또는 같은 순간의 가장 최근 입춘). 1월과 입춘 전 출생이면 이전 해 입춘이다."
          },
          "current_jie": {
            "$ref": "#/components/schemas/Term",
            "description": "출생 순간이 속한 절월을 시작한 절(출생 순간 이전 또는 같은 순간의 가장 최근 절)."
          },
          "next_jie": {
            "$ref": "#/components/schemas/Term",
            "description": "출생 이후 다음 절."
          },
          "day_date": {
            "type": "string",
            "format": "date",
            "description": "일주에 쓴 날짜(`clock_basis` 시간축 + `day_boundary`)."
          },
          "day_jdn": {
            "type": "integer",
            "description": "`day_date`의 율리우스 일수(JDN, 그 날 정오에 대응하는 정수). 일주 `cycle_index = (JDN + 49) mod 60`."
          },
          "hour_basis_day_date": {
            "type": "string",
            "format": "date",
            "description": "시간 천간 계산에 쓴 날짜. `split_zi`에서 23시 이후면 `day_date` 다음 날이고, 그 외에는 `day_date`와 같다."
          }
        },
        "required": [
          "lichun",
          "current_jie",
          "next_jie",
          "day_date",
          "day_jdn",
          "hour_basis_day_date"
        ],
        "additionalProperties": false,
        "description": "기둥 판정 근거."
      },
      "Rational": {
        "type": "object",
        "properties": {
          "numerator": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "description": "분자(0 이상 정수 문자열)."
          },
          "denominator": {
            "type": "string",
            "pattern": "^[1-9][0-9]*$",
            "description": "분모(1 이상 정수 문자열)."
          }
        },
        "required": [
          "numerator",
          "denominator"
        ],
        "additionalProperties": false,
        "description": "음이 아닌 기약분수. 정밀도를 보존하려고 분자와 분모를 10진 정수 문자열로 보낸다.",
        "examples": [
          {
            "numerator": "818007560171",
            "denominator": "86400000000"
          }
        ]
      },
      "SyntheticAge": {
        "type": "object",
        "properties": {
          "years": {
            "type": "integer",
            "minimum": 0,
            "description": "합성 연수."
          },
          "months": {
            "type": "integer",
            "minimum": 0,
            "maximum": 11,
            "description": "합성 개월 수(0~11)."
          },
          "days": {
            "$ref": "#/components/schemas/Rational",
            "description": "합성 일수의 나머지(유리수, 0 이상 30 미만)."
          }
        },
        "required": [
          "years",
          "months",
          "days"
        ],
        "additionalProperties": false,
        "description": "전통 360일 환산 표현. `start_age_years × 360`을 합성 일수로 보고 360일=1년, 30일=1개월로 나눈다(출생-절입 간격 1시간 = 5일). `days`는 [0, 30) 범위의 나머지다. 실제 달력 기간이 아니다."
      },
      "LuckPeriod": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "대운 순번(1부터)."
          },
          "pillar": {
            "$ref": "#/components/schemas/Pillar",
            "description": "대운 간지. 월주에서 방향에 따라 `index`만큼 이동한 간지다(첫 대운은 월주 자체가 아니다)."
          },
          "start_age_years": {
            "$ref": "#/components/schemas/Rational",
            "description": "시작 나이(년, 유리수) = 대운 시작 나이 + 10 × (index − 1)."
          },
          "end_age_years": {
            "$ref": "#/components/schemas/Rational",
            "description": "끝 나이(년, 유리수) = 시작 나이 + 10."
          },
          "start": {
            "$ref": "#/components/schemas/Instant",
            "description": "시작 순간 = 출생 TT + 시작 나이 × 365.2425일."
          },
          "end": {
            "$ref": "#/components/schemas/Instant",
            "description": "끝 순간. 다음 대운의 시작과 같다."
          }
        },
        "required": [
          "index",
          "pillar",
          "start_age_years",
          "end_age_years",
          "start",
          "end"
        ],
        "additionalProperties": false,
        "description": "대운 하나. 구간은 [start, end)다."
      },
      "DaewoonResult": {
        "type": "object",
        "properties": {
          "direction": {
            "type": "string",
            "enum": [
              "forward",
              "reverse"
            ],
            "description": "계산된 방향(순행/역행). `by_year_stem_and_sex` 요청도 결정된 방향으로 표시한다."
          },
          "basis_term": {
            "$ref": "#/components/schemas/Term",
            "description": "대운수 계산의 기준 절. 순행은 출생 이후(같은 순간 포함) 첫 절, 역행은 출생 이전(같은 순간 포함) 마지막 절이다."
          },
          "delta_us": {
            "type": "string",
            "pattern": "^(0|[1-9][0-9]*)$",
            "description": "출생과 기준 절 사이의 TT 간격(마이크로초, 음이 아닌 정수 문자열)."
          },
          "start_age_years": {
            "$ref": "#/components/schemas/Rational",
            "description": "대운 시작 나이 원값(년, 기약분수) = `delta_us / 259200000000`."
          },
          "display_age": {
            "type": "integer",
            "minimum": 0,
            "description": "`display_rounding`을 적용한 정수 대운수. 표시 전용이며 시작 시점 계산에는 쓰지 않는다."
          },
          "display_rounding": {
            "type": "string",
            "enum": [
              "floor",
              "ceil",
              "half_up"
            ],
            "description": "적용한 표시 반올림 방식."
          },
          "synthetic_360_age": {
            "$ref": "#/components/schemas/SyntheticAge",
            "description": "시작 나이의 전통 360일 환산 표현."
          },
          "start_method": {
            "type": "string",
            "enum": [
              "mean_gregorian_year"
            ],
            "description": "실제 시작 순간 환산 규칙(항상 노출)."
          },
          "start": {
            "$ref": "#/components/schemas/Instant",
            "description": "첫 대운의 실제 시작 순간."
          },
          "periods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LuckPeriod"
            },
            "minItems": 1,
            "maxItems": 12,
            "description": "요청한 개수(`count`)만큼의 대운."
          }
        },
        "required": [
          "direction",
          "basis_term",
          "delta_us",
          "start_age_years",
          "display_age",
          "display_rounding",
          "synthetic_360_age",
          "start_method",
          "start",
          "periods"
        ],
        "additionalProperties": false,
        "description": "대운 계산 결과. 간격은 TT 연속 초로 계산하고, 3일 = 1년으로 환산한다."
      },
      "HiddenStem": {
        "type": "object",
        "properties": {
          "stem_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9,
            "description": "지장간 천간 인덱스(0~9)."
          },
          "role": {
            "type": "string",
            "enum": [
              "main",
              "middle",
              "residual"
            ],
            "description": "역할. `main`=본기, `middle`=중기, `residual`=여기. 배열 순서로 역할을 추정하지 않는다."
          },
          "ten_god": {
            "type": "string",
            "enum": [
              "비견",
              "겁재",
              "식신",
              "상관",
              "편재",
              "정재",
              "편관",
              "정관",
              "편인",
              "정인"
            ],
            "description": "일간 대비 십신. 비견·겁재(같은 오행), 식신·상관(일간이 생함), 편재·정재(일간이 극함), 편관·정관(일간을 극함), 편인·정인(일간을 생함). 앞쪽이 음양 같음, 뒤쪽이 음양 다름."
          },
          "rule_id": {
            "type": "string",
            "minLength": 1,
            "description": "근거가 된 규칙표 항목 ID."
          }
        },
        "required": [
          "stem_index",
          "role",
          "ten_god",
          "rule_id"
        ],
        "additionalProperties": false,
        "description": "지지에 숨은 천간(지장간) 하나."
      },
      "StemRelation": {
        "type": "object",
        "properties": {
          "pillar": {
            "type": "string",
            "enum": [
              "year",
              "month",
              "day",
              "hour"
            ],
            "description": "기둥(year, month, day, hour)."
          },
          "stem_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9,
            "description": "그 기둥의 천간 인덱스."
          },
          "relation": {
            "type": "string",
            "enum": [
              "day_master",
              "비견",
              "겁재",
              "식신",
              "상관",
              "편재",
              "정재",
              "편관",
              "정관",
              "편인",
              "정인"
            ],
            "description": "일간 대비 십신. 일주 천간(일간 자신)은 `day_master`. 비견·겁재(같은 오행), 식신·상관(일간이 생함), 편재·정재(일간이 극함), 편관·정관(일간을 극함), 편인·정인(일간을 생함). 앞쪽이 음양 같음, 뒤쪽이 음양 다름."
          }
        },
        "required": [
          "pillar",
          "stem_index",
          "relation"
        ],
        "additionalProperties": false,
        "description": "기둥별 천간의 십신 관계."
      },
      "BranchRelation": {
        "type": "object",
        "properties": {
          "pillar": {
            "type": "string",
            "enum": [
              "year",
              "month",
              "day",
              "hour"
            ],
            "description": "기둥(year, month, day, hour)."
          },
          "branch_index": {
            "type": "integer",
            "minimum": 0,
            "maximum": 11,
            "description": "그 기둥의 지지 인덱스."
          },
          "main_stem_ten_god": {
            "type": "string",
            "enum": [
              "비견",
              "겁재",
              "식신",
              "상관",
              "편재",
              "정재",
              "편관",
              "정관",
              "편인",
              "정인"
            ],
            "description": "지지 본기(main) 천간의 일간 대비 십신."
          },
          "hidden_stems": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HiddenStem"
            },
            "minItems": 1,
            "maxItems": 3,
            "description": "지장간 목록(1~3개). 각 항목의 `role`로 본기·중기·여기를 구분한다."
          }
        },
        "required": [
          "pillar",
          "branch_index",
          "main_stem_ten_god",
          "hidden_stems"
        ],
        "additionalProperties": false,
        "description": "기둥별 지지의 본기 십신과 지장간 목록."
      },
      "Derived": {
        "type": "object",
        "properties": {
          "ruleset_id": {
            "type": "string",
            "minLength": 1,
            "description": "사용한 규칙표 ID."
          },
          "stem_relations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StemRelation"
            },
            "minItems": 4,
            "maxItems": 4,
            "description": "연·월·일·시주 천간의 십신(4개)."
          },
          "branch_relations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BranchRelation"
            },
            "minItems": 4,
            "maxItems": 4,
            "description": "연·월·일·시주 지지의 본기 십신과 지장간(4개)."
          }
        },
        "required": [
          "ruleset_id",
          "stem_relations",
          "branch_relations"
        ],
        "additionalProperties": false,
        "description": "`include_derived=true`일 때의 파생 정보(십신, 지장간). 규칙표(`ruleset_id`)는 명리 규칙이며 천문 계산과 따로 검토한다. draft 규칙표를 허용한 개발·스테이징 서버에서는 `uncertainty.warnings`에 `DRAFT_RULESET`이 붙는다.",
        "examples": [
          {
            "ruleset_id": "kr-core-v1",
            "stem_relations": [
              {
                "pillar": "year",
                "stem_index": 5,
                "relation": "정재"
              },
              {
                "pillar": "month",
                "stem_index": 3,
                "relation": "상관"
              },
              {
                "pillar": "day",
                "stem_index": 0,
                "relation": "day_master"
              },
              {
                "pillar": "hour",
                "stem_index": 6,
                "relation": "편관"
              }
            ],
            "branch_relations": [
              {
                "pillar": "year",
                "branch_index": 3,
                "main_stem_ten_god": "겁재",
                "hidden_stems": [
                  {
                    "stem_index": 0,
                    "role": "residual",
                    "ten_god": "비견",
                    "rule_id": "hidden_stems.branch_3"
                  },
                  {
                    "stem_index": 1,
                    "role": "main",
                    "ten_god": "겁재",
                    "rule_id": "hidden_stems.branch_3"
                  }
                ]
              },
              {
                "pillar": "month",
                "branch_index": 1,
                "main_stem_ten_god": "정재",
                "hidden_stems": [
                  {
                    "stem_index": 9,
                    "role": "residual",
                    "ten_god": "정인",
                    "rule_id": "hidden_stems.branch_1"
                  },
                  {
                    "stem_index": 7,
                    "role": "middle",
                    "ten_god": "정관",
                    "rule_id": "hidden_stems.branch_1"
                  },
                  {
                    "stem_index": 5,
                    "role": "main",
                    "ten_god": "정재",
                    "rule_id": "hidden_stems.branch_1"
                  }
                ]
              },
              {
                "pillar": "day",
                "branch_index": 0,
                "main_stem_ten_god": "정인",
                "hidden_stems": [
                  {
                    "stem_index": 8,
                    "role": "residual",
                    "ten_god": "편인",
                    "rule_id": "hidden_stems.branch_0"
                  },
                  {
                    "stem_index": 9,
                    "role": "main",
                    "ten_god": "정인",
                    "rule_id": "hidden_stems.branch_0"
                  }
                ]
              },
              {
                "pillar": "hour",
                "branch_index": 6,
                "main_stem_ten_god": "상관",
                "hidden_stems": [
                  {
                    "stem_index": 2,
                    "role": "residual",
                    "ten_god": "식신",
                    "rule_id": "hidden_stems.branch_6"
                  },
                  {
                    "stem_index": 5,
                    "role": "middle",
                    "ten_god": "정재",
                    "rule_id": "hidden_stems.branch_6"
                  },
                  {
                    "stem_index": 3,
                    "role": "main",
                    "ten_god": "상관",
                    "rule_id": "hidden_stems.branch_6"
                  }
                ]
              }
            ]
          }
        ]
      },
      "Candidate": {
        "type": "object",
        "properties": {
          "pillars": {
            "$ref": "#/components/schemas/Pillars",
            "description": "이 후보의 사주팔자."
          },
          "daewoon": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DaewoonResult"
              },
              {
                "type": "null"
              }
            ],
            "description": "이 후보의 대운. 후보마다 방향과 기준 절이 다를 수 있다. 대운을 요청하지 않았으면 null."
          },
          "daewoon_start_age_min": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rational"
              },
              {
                "type": "null"
              }
            ],
            "description": "이 후보 구간에서 대운 시작 나이의 최솟값(년). 대운을 요청하지 않았거나 오차 상한을 모르면 null."
          },
          "daewoon_start_age_max": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rational"
              },
              {
                "type": "null"
              }
            ],
            "description": "이 후보 구간에서 대운 시작 나이의 최댓값(년). 대운을 요청하지 않았거나 오차 상한을 모르면 null."
          },
          "basis": {
            "$ref": "#/components/schemas/Basis",
            "description": "이 후보의 판정 근거."
          },
          "reason_codes": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "description": "후보 사유. `NOMINAL`=명목(입력 그대로) 결과, `YEAR_PILLAR_DIFFERS` / `MONTH_PILLAR_DIFFERS` / `DAY_PILLAR_DIFFERS` / `HOUR_PILLAR_DIFFERS`=명목과 다른 기둥, `OTHER_FOLD`=중복 시각의 다른 fold에서 나온 후보."
          }
        },
        "required": [
          "pillars",
          "daewoon",
          "daewoon_start_age_min",
          "daewoon_start_age_max",
          "basis",
          "reason_codes"
        ],
        "additionalProperties": false,
        "description": "불확실성 범위 안에서 가능한 기둥 조합 하나. 기둥 조합이 같으면 하나로 합친다."
      },
      "BoundaryProximity": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "lichun",
              "jie",
              "day",
              "hour",
              "timezone_transition"
            ],
            "description": "경계 종류. `lichun`=입춘, `jie`=절입, `day`=일주 경계, `hour`=시지 경계, `timezone_transition`=민간 오프셋 전환."
          },
          "distance_seconds": {
            "type": "number",
            "minimum": 0,
            "description": "경계까지의 절대 거리(초). lichun/jie는 TT 기준, day/hour는 계산 시간축(`clock_label`) 기준, timezone_transition은 기준 척도 기준이다."
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "description": "사람이 읽을 설명(고정 영문 문구)."
          }
        },
        "required": [
          "kind",
          "distance_seconds",
          "description"
        ],
        "additionalProperties": false,
        "description": "명목 결과에서 종류별로 가장 가까운 경계까지의 거리."
      },
      "Uncertainty": {
        "type": "object",
        "properties": {
          "boundary_status": {
            "type": "string",
            "enum": [
              "stable",
              "sensitive",
              "unknown"
            ],
            "description": "`stable`=유한 오차 범위 전체에서 기둥 분류가 같다(후보 1개). `sensitive`=다른 기둥 후보가 있다(후보 2개 이상). `unknown`=모델 오차 상한을 몰라 후보 열거가 완전하지 않을 수 있다. stable이어도 대운 시작 나이 같은 연속값에는 오차가 있을 수 있다."
          },
          "input_seconds": {
            "type": "number",
            "minimum": 0,
            "description": "적용한 입력 시각 ±범위(초) = `birth.uncertainty_seconds`."
          },
          "instant_error_bound_seconds": {
            "anyOf": [
              {
                "type": "number",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ],
            "description": "출생 순간(TT) 변환의 모델 오차 상한(초, 입력 범위 자체는 제외). 입력 ±범위 안의 모든 해석 중 최댓값이다. 관측 UTC 구간은 0이다. 범위 어딘가가 1972년 이전 근사나 윤초 확정 범위 이후라서 상한을 모르면 null."
          },
          "clock_error_bound_seconds": {
            "anyOf": [
              {
                "type": "number",
                "minimum": 0
              },
              {
                "type": "null"
              }
            ],
            "description": "계산 시간축 라벨의 모델 오차 상한(초, 입력 범위 자체는 제외). 입력 ±범위 안의 모든 해석 중 최댓값이다. civil은 0, 태양시는 ΔT·균시차 보간 오차다. 범위 어딘가에서 모르면 null."
          },
          "nearest_boundaries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BoundaryProximity"
            },
            "description": "종류별로 가장 가까운 경계(명목 결과 기준)."
          },
          "candidates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Candidate"
            },
            "minItems": 1,
            "maxItems": 32,
            "description": "가능한 기둥 후보. 첫 번째가 명목 후보(`reason_codes: [\"NOMINAL\"]`)다. 최대 32개이고, 넘으면 422 UNCERTAINTY_TOO_WIDE다."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "description": "경고 코드. 명목 시각뿐 아니라 입력 ±범위 안의 어느 해석에든 해당하면 붙는다. `HISTORICAL_TIME_APPROXIMATION`=1972년 이전 UT1 근사, `FUTURE_TIME_MODEL`=윤초 확정 범위 이후의 잠정 시간 모델, `MODEL_ERROR_UNKNOWN`=오차 상한 미상, `DRAFT_RULESET`=draft 규칙표로 파생 정보를 계산함(개발·스테이징 전용). 음력 입력에서는 다음이 더해진다: `LUNAR_BOUNDARY_UNKNOWN`(음력 월 경계의 오차 상한이 없음 — 현재 번들은 모든 구간에 문헌 기반 상한이 있어 발생하지 않음), `LUNAR_BEYOND_OFFICIAL_RANGE`(음력으로 입력한 날짜가 KASI 공식 음양력 대조 구간의 마지막 날인 2050-12-13 이후로 풀림 — 공식 기준자료가 없는 계산 확장이며 답 자체는 바뀌지 않는다). 요청한 기준 자오선 하나로만 계산하므로 다른 자오선과의 차이는 경고나 후보로 나타나지 않는다."
          }
        },
        "required": [
          "boundary_status",
          "input_seconds",
          "instant_error_bound_seconds",
          "clock_error_bound_seconds",
          "nearest_boundaries",
          "candidates",
          "warnings"
        ],
        "additionalProperties": false,
        "description": "경계 민감도와 불확실성 후보. 입력 ±범위, 경도 오차, 시간척도·절기 모델 오차를 단순 합산한 보수적 상한으로 전파한다(통계적 신뢰구간이 아님)."
      },
      "Provenance": {
        "type": "object",
        "properties": {
          "engine_id": {
            "type": "string",
            "minLength": 1,
            "description": "`four-pillars-for-agents/{서버 버전}+{번들 엔진 ID}` 형식의 엔진 식별자."
          },
          "dataset_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 데이터 번들 ID(요청에서 생략했으면 활성 번들). 재현하려면 다음 요청에 지정한다."
          },
          "policy_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 정책 ID. 재현하려면 다음 요청에 지정한다."
          },
          "ruleset_id": {
            "type": "string",
            "minLength": 1,
            "description": "정책의 파생 정보 규칙표 ID."
          },
          "tzdb_id": {
            "type": "string",
            "minLength": 1,
            "description": "번들에 고정된 IANA tzdb 버전(예: `2026c`)."
          },
          "ephemeris_sha256": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "description": "천체력 파일의 SHA-256(소문자 16진수 64자)."
          },
          "timescale_model_id": {
            "type": "string",
            "minLength": 1,
            "description": "ΔT·EOP 시간척도 모델 ID."
          },
          "resolved_policy": {
            "$ref": "#/components/schemas/ResolvedPolicy",
            "description": "실제 적용한 정책 값 전체."
          },
          "calculation_fingerprint": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "description": "정규화 입력, dataset, 엔진, 규칙표에 대한 HMAC-SHA256(소문자 16진수 64자). `fingerprint_key_id`가 같으면 같은 조건의 계산은 같은 값을 가진다. 서버 비밀키 없이는 입력을 역산할 수 없다."
          },
          "fingerprint_key_id": {
            "type": "string",
            "minLength": 1,
            "description": "fingerprint HMAC 키 식별자. 키를 교체하면 바뀌며, 그때는 fingerprint 값을 서로 비교할 수 없다."
          }
        },
        "required": [
          "engine_id",
          "dataset_id",
          "policy_id",
          "ruleset_id",
          "tzdb_id",
          "ephemeris_sha256",
          "timescale_model_id",
          "resolved_policy",
          "calculation_fingerprint",
          "fingerprint_key_id"
        ],
        "additionalProperties": false,
        "description": "계산 근거. 결과 차이가 입력, 정책, 데이터 중 어디서 왔는지 추적하는 데 쓴다."
      },
      "ChartResponse": {
        "type": "object",
        "properties": {
          "request_id": {
            "type": "string",
            "minLength": 1,
            "description": "요청 ID(`X-Request-Id` 응답 헤더와 같다)."
          },
          "provenance": {
            "$ref": "#/components/schemas/Provenance",
            "description": "계산 근거."
          },
          "time": {
            "$ref": "#/components/schemas/TimeResult",
            "description": "입력 시각 해석 결과와 계산 시간축."
          },
          "pillars": {
            "$ref": "#/components/schemas/Pillars",
            "description": "명목 사주팔자."
          },
          "basis": {
            "$ref": "#/components/schemas/Basis",
            "description": "명목 결과의 판정 근거."
          },
          "daewoon": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DaewoonResult"
              },
              {
                "type": "null"
              }
            ],
            "description": "명목 결과의 대운. 요청에 `daewoon`이 없으면 null."
          },
          "derived": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Derived"
              },
              {
                "type": "null"
              }
            ],
            "description": "파생 정보. `include_derived=true`일 때만 값이 있고 그 외에는 null."
          },
          "uncertainty": {
            "$ref": "#/components/schemas/Uncertainty",
            "description": "경계 민감도와 후보."
          }
        },
        "required": [
          "request_id",
          "provenance",
          "time",
          "pillars",
          "basis",
          "daewoon",
          "derived",
          "uncertainty"
        ],
        "additionalProperties": false,
        "description": "`POST /v1/charts` 성공 응답. 최상위 `pillars`, `basis`, `daewoon`, `derived`는 명목 결과이고, 불확실성 후보는 `uncertainty.candidates`에 있다."
      },
      "ErrorDetail": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "minLength": 1,
            "description": "문제 위치. 본문 필드는 점 표기 경로(예: `birth.local_datetime`), 쿼리는 파라미터 이름, 본문·쿼리 전체는 `$`. 스키마 `required` 위반이면 누락된 속성이 아니라 그 속성을 가져야 하는 객체의 경로다(예: `birth`)."
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "description": "원인 키워드. 스키마 위반은 JSON Schema 키워드 이름(`required`, `type`, `pattern`, `enum`, `minimum`, `maximum`, `minLength`, `additionalProperties`, `not` 등). 그 외: `not_a_real_datetime`, `year_1900_2100`, `range_exceeds_supported_longitudes`, `not_allowed_by_policy`, `unknown`, `unsupported`, `nonexistent`, `not_ambiguous`, `out_of_range`, `not_allowed_with_explicit_direction`, `number_too_large`, `unknown_parameter`, `integer`, `range_1900_2100`, `no_parameters_allowed`, `invalid`, `repeated`, `malformed_query`, `not_a_real_date`, `supported_range`, `outside_table`, `not_allowed_with_gregorian`, `leap_month_not_in_this_year`, `lunar_month_not_found`, `lunar_day_out_of_range`. AMBIGUOUS_LOCAL_TIME 후보 항목에는 없다."
          },
          "fold": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1,
            "description": "AMBIGUOUS_LOCAL_TIME 후보의 fold 값(0=먼저, 1=나중)."
          },
          "reference_label": {
            "type": "string",
            "description": "AMBIGUOUS_LOCAL_TIME 후보의 기준 척도(UTC, 또는 1972년 이전 UT1 근사) 라벨. 시간대가 없고 소수 초가 붙을 수 있다."
          },
          "offset_seconds": {
            "type": "integer",
            "description": "AMBIGUOUS_LOCAL_TIME 후보에 적용될 총 UTC 오프셋(초)."
          }
        },
        "required": [],
        "additionalProperties": false,
        "description": "오류 상세 한 건. 요청 값, 서버 경로, 키는 담지 않는다."
      },
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "MALFORMED_JSON",
              "INVALID_API_KEY",
              "QUOTA_FORBIDDEN",
              "DATASET_NOT_FOUND",
              "POLICY_NOT_FOUND",
              "INVALID_INPUT",
              "UNSUPPORTED_RANGE",
              "UNSUPPORTED_CALENDAR",
              "NONEXISTENT_LOCAL_TIME",
              "AMBIGUOUS_LOCAL_TIME",
              "INVALID_FOLD",
              "CONFLICTING_OPTIONS",
              "DAEWOON_SEX_REQUIRED",
              "UNCERTAINTY_TOO_WIDE",
              "RATE_LIMITED",
              "DATASET_UNAVAILABLE",
              "RULESET_UNAVAILABLE",
              "INTERNAL_ERROR",
              "PAYLOAD_TOO_LARGE",
              "UNSUPPORTED_MEDIA_TYPE",
              "NOT_FOUND",
              "METHOD_NOT_ALLOWED",
              "SERVICE_OVERLOADED",
              "CALCULATION_TIMEOUT"
            ],
            "description": "기계가 읽는 오류 코드. HTTP 상태 매핑: 400 `MALFORMED_JSON`; 401 `INVALID_API_KEY`; 403 `QUOTA_FORBIDDEN`; 404 `DATASET_NOT_FOUND`, `POLICY_NOT_FOUND`, `NOT_FOUND`; 405 `METHOD_NOT_ALLOWED`; 413 `PAYLOAD_TOO_LARGE`; 415 `UNSUPPORTED_MEDIA_TYPE`; 422 `INVALID_INPUT`, `UNSUPPORTED_RANGE`, `UNSUPPORTED_CALENDAR`, `NONEXISTENT_LOCAL_TIME`, `AMBIGUOUS_LOCAL_TIME`, `INVALID_FOLD`, `CONFLICTING_OPTIONS`, `DAEWOON_SEX_REQUIRED`, `UNCERTAINTY_TOO_WIDE`; 429 `RATE_LIMITED`; 500 `INTERNAL_ERROR`; 503 `DATASET_UNAVAILABLE`, `RULESET_UNAVAILABLE`, `SERVICE_OVERLOADED`, `CALCULATION_TIMEOUT`."
          },
          "message": {
            "type": "string",
            "minLength": 1,
            "description": "code별 고정 영문 문구. 요청 값을 담지 않는다. 표시용이며 분기에는 쓰지 않는다."
          },
          "request_id": {
            "type": "string",
            "minLength": 1,
            "description": "요청 ID(`X-Request-Id` 응답 헤더와 같다). 운영 문의 시 전달한다."
          },
          "details": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "description": "상세 목록. 없으면 빈 배열이다. 스키마 위반은 최대 10개까지 담는다."
          }
        },
        "required": [
          "code",
          "message",
          "request_id",
          "details"
        ],
        "additionalProperties": false,
        "description": "모든 4xx·5xx 응답의 공통 오류 본문(RFC 7807 형식이 아님). 클라이언트는 `code`로 분기한다."
      },
      "TermsResponse": {
        "type": "object",
        "properties": {
          "dataset_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 데이터 번들 ID."
          },
          "year": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2100,
            "description": "요청한 연도."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Term"
            },
            "minItems": 24,
            "maxItems": 24,
            "description": "그 해의 24절기(정확히 24개). 시간순이며 1월 소한부터 12월 동지까지다."
          }
        },
        "required": [
          "dataset_id",
          "year",
          "items"
        ],
        "additionalProperties": false,
        "description": "`GET /v1/solar-terms` 응답."
      },
      "PolicyInfo": {
        "type": "object",
        "properties": {
          "policy_id": {
            "type": "string",
            "minLength": 1,
            "description": "정책 ID(`ChartRequest.policy_id`에 지정)."
          },
          "status": {
            "type": "string",
            "enum": [
              "approved"
            ],
            "description": "승인 상태. 이 목록에는 `approved`만 나온다."
          },
          "supported_birth_dates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "지원하는 출생 민간 날짜 범위 `[시작, 끝]`(양끝 포함)."
          },
          "supported_timezones": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "minItems": 1,
            "description": "지원 IANA 시간대."
          },
          "active_dataset_id": {
            "type": "string",
            "minLength": 1,
            "description": "`dataset_id`를 생략하면 쓰는 활성 번들 ID."
          },
          "available_dataset_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "minItems": 1,
            "description": "요청에 지정할 수 있는 번들 ID 목록."
          },
          "defaults": {
            "$ref": "#/components/schemas/ResolvedPolicy",
            "description": "요청에서 생략한 옵션에 적용되는 기본 정책 값. 요청 기록 필드(`requested_daewoon_direction`, `sex_for_rule`)는 null이다."
          },
          "allowed_clock_bases": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "civil",
                "local_mean_solar",
                "local_apparent_solar"
              ]
            },
            "description": "`options.clock_basis`로 지정할 수 있는 값."
          },
          "allowed_day_boundaries": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "midnight",
                "zi_start_23",
                "split_zi"
              ]
            },
            "description": "`options.day_boundary`로 지정할 수 있는 값."
          },
          "derived_available": {
            "type": "boolean",
            "description": "지금 `include_derived=true`를 쓸 수 있는지. false면 그 요청은 503 RULESET_UNAVAILABLE이다."
          },
          "allowed_lunar_meridians": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "kr_kasi",
                "kr_standard_utc9",
                "kr_historical_offsets"
              ]
            },
            "description": "`options.lunar_meridian`으로 고를 수 있는 기준 자오선 목록."
          }
        },
        "required": [
          "policy_id",
          "status",
          "supported_birth_dates",
          "supported_timezones",
          "active_dataset_id",
          "available_dataset_ids",
          "defaults",
          "allowed_clock_bases",
          "allowed_day_boundaries",
          "derived_available",
          "allowed_lunar_meridians"
        ],
        "additionalProperties": false,
        "description": "승인된 정책 하나의 지원 범위, 기본값, 허용 옵션."
      },
      "PoliciesResponse": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PolicyInfo"
            },
            "description": "승인된 정책 목록."
          }
        },
        "required": [
          "items"
        ],
        "additionalProperties": false,
        "description": "`GET /v1/policies` 응답."
      },
      "Health": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "not_ready"
            ],
            "description": "`ok`=정상. `not_ready`=준비 안 됨(번들 미준비 또는 종료 드레인 중, `/health/ready`의 503에서만)."
          }
        },
        "required": [
          "status"
        ],
        "additionalProperties": false,
        "description": "헬스 프로브 응답.",
        "examples": [
          {
            "status": "ok"
          },
          {
            "status": "not_ready"
          }
        ]
      },
      "LunarDate": {
        "type": "object",
        "properties": {
          "lunar_year": {
            "type": "integer",
            "description": "음력 연도. 정월 초하루가 드는 그레고리력 연도다. 양력 1900-01-01은 음력 1899년 12월 1일이므로 연도가 다를 수 있다."
          },
          "month": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "음력 월 번호."
          },
          "is_leap": {
            "type": "boolean",
            "description": "윤달 여부. 윤달은 직전 달과 같은 번호를 쓴다."
          },
          "day": {
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "description": "음력 일."
          },
          "lunar_meridian": {
            "type": "string",
            "enum": [
              "kr_kasi",
              "kr_standard_utc9",
              "kr_historical_offsets"
            ],
            "description": "이 표기를 읽은 기준 자오선."
          },
          "assignment_quality": {
            "type": "string",
            "enum": [
              "stable",
              "sensitive",
              "unknown"
            ],
            "description": "월 경계가 얼마나 확실한가. `stable`은 모델 오차로 경계가 움직이지 않음, `sensitive`는 경계를 정하는 사건(삭·중기)이 자정에서 오차 상한 안에 있어 날짜가 바뀔 수 있음. 오차 상한은 삭 모델 오차 1초에 ΔT 상한을 더한 값이며, ΔT 상한은 1972년 이전 0.1초(HMNAO SMH2016 ε ±0.05초의 2배), 관측 EOP 기간 10ms, 그 이후는 HMNAO 외삽 ε와 Huber(2000) 예측 모델에서 정한다(2100년 약 39초). `unknown`은 상한이 없는 경우를 위해 남겨 두며 현재 번들에는 없다."
          }
        },
        "required": [
          "lunar_year",
          "month",
          "is_leap",
          "day",
          "lunar_meridian",
          "assignment_quality"
        ],
        "additionalProperties": false,
        "description": "민간 날짜의 한국 음력 표기."
      },
      "LunarMonth": {
        "type": "object",
        "properties": {
          "month": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "description": "음력 월 번호."
          },
          "is_leap": {
            "type": "boolean",
            "description": "윤달 여부."
          },
          "start_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "그 달 초하루의 양력 날짜."
          },
          "days": {
            "type": "integer",
            "enum": [
              29,
              30
            ],
            "description": "그 달의 일수."
          },
          "assignment_quality": {
            "type": "string",
            "enum": [
              "stable",
              "sensitive",
              "unknown"
            ],
            "description": "월 경계의 확실성."
          }
        },
        "required": [
          "month",
          "is_leap",
          "start_date",
          "days",
          "assignment_quality"
        ],
        "additionalProperties": false,
        "description": "한 음력 달."
      },
      "LunarMonthsResponse": {
        "type": "object",
        "properties": {
          "dataset_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 데이터 번들 ID."
          },
          "lunar_year": {
            "type": "integer",
            "description": "요청한 음력 연도."
          },
          "lunar_meridian": {
            "type": "string",
            "enum": [
              "kr_kasi",
              "kr_standard_utc9",
              "kr_historical_offsets"
            ],
            "description": "사용한 기준 자오선."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LunarMonth"
            },
            "minItems": 12,
            "maxItems": 13,
            "description": "그 음력 연도의 달 목록(12개, 윤달이 있으면 13개). 초하루 순이다."
          }
        },
        "required": [
          "dataset_id",
          "lunar_year",
          "lunar_meridian",
          "items"
        ],
        "additionalProperties": false,
        "description": "`GET /v1/lunar-months` 응답."
      },
      "CalendarDayGanzhi": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Pillar"
          }
        ],
        "description": "그 **양력 날짜의 일진(日辰)** — JDN으로만 정해지는 날짜의 간지. 경도, 시계 기준, 자시 처리와 무관하다. **사람 명식의 일주와 다를 수 있다**: `/v1/charts`의 일주는 기본값으로 진태양시와 자정 경계를 쓰므로, 자정 근처에 태어났거나 경도·균시차 보정으로 날짜가 넘어가면 달라진다."
      },
      "DayEdge": {
        "type": "object",
        "properties": {
          "local_time": {
            "type": "string",
            "description": "그 순간의 민간 벽시계 시각(마이크로초까지). 날짜의 00:00이 시각 변경 공백에 들면 공백이 끝난 시각이다(예: 1948-06-01은 `01:00:00`).",
            "example": "2024-02-04T00:00:00.000000"
          },
          "instant": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Instant"
              }
            ],
            "description": "같은 순간의 물리 시각(TT·UTC)."
          }
        },
        "required": [
          "local_time",
          "instant"
        ],
        "additionalProperties": false,
        "description": "날짜의 첫 순간(또는 다음 날짜의 첫 순간)."
      },
      "YearMonthPillars": {
        "type": "object",
        "properties": {
          "year": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Pillar"
              }
            ],
            "description": "연주(입춘 기준)."
          },
          "month": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Pillar"
              }
            ],
            "description": "월주(12절 기준)."
          }
        },
        "required": [
          "year",
          "month"
        ],
        "additionalProperties": false,
        "description": "한 순간의 연주·월주."
      },
      "GanzhiTransition": {
        "type": "object",
        "properties": {
          "local_time": {
            "type": "string",
            "description": "절이 들어오는 민간 벽시계 시각(마이크로초까지)."
          },
          "term": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Term"
              }
            ],
            "description": "들어오는 절(`GET /v1/solar-terms` 항목과 같은 형식, 항상 `is_jie=true`)."
          },
          "year": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Pillar"
              }
            ],
            "description": "이 절 이후의 연주. 입춘이면 바뀌고 그 밖에는 그대로다."
          },
          "month": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Pillar"
              }
            ],
            "description": "이 절 이후의 월주."
          }
        },
        "required": [
          "local_time",
          "term",
          "year",
          "month"
        ],
        "additionalProperties": false,
        "description": "그 날짜 안에서 연주 또는 월주를 바꾸는 절(12절) 하나. 절이 아닌 중기(中氣)는 기둥을 바꾸지 않으므로 나오지 않는다."
      },
      "ConvertResponse": {
        "type": "object",
        "properties": {
          "dataset_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 데이터 번들 ID."
          },
          "policy_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 정책 ID."
          },
          "lunar_meridian": {
            "type": "string",
            "enum": [
              "kr_kasi",
              "kr_standard_utc9",
              "kr_historical_offsets"
            ],
            "description": "음력 변환에 쓴 기준 자오선."
          },
          "date": {
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
            "description": "해석된 **양력** 날짜. 음력으로 요청해도 양력으로 돌려준다."
          },
          "korean_lunar": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LunarDate"
              }
            ],
            "description": "그 양력 날짜의 한국 음력 날짜(요청한 달력과 관계없이 항상)."
          },
          "calendar_day_ganzhi": {
            "$ref": "#/components/schemas/CalendarDayGanzhi"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "description": "경고 코드. 이 엔드포인트가 내는 것은 `LUNAR_BEYOND_OFFICIAL_RANGE`뿐이다 — 날짜가 KASI 공식 음양력 대조 구간의 마지막 날(2050-12-13) 이후라 음력 날짜가 공식 기준자료 없는 계산 확장이다. 음력 달 경계의 확정도는 `korean_lunar.assignment_quality`에 있다."
          }
        },
        "required": [
          "dataset_id",
          "policy_id",
          "lunar_meridian",
          "date",
          "korean_lunar",
          "calendar_day_ganzhi",
          "warnings"
        ],
        "additionalProperties": false,
        "description": "`GET /v1/convert` 응답. 양력과 음력 두 표기, 그 날짜의 일진."
      },
      "GanzhiResponse": {
        "type": "object",
        "properties": {
          "dataset_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 데이터 번들 ID."
          },
          "policy_id": {
            "type": "string",
            "minLength": 1,
            "description": "실제 사용한 정책 ID."
          },
          "lunar_meridian": {
            "type": "string",
            "enum": [
              "kr_kasi",
              "kr_standard_utc9",
              "kr_historical_offsets"
            ],
            "description": "`korean_lunar`에 쓴 기준 자오선."
          },
          "timezone": {
            "type": "string",
            "description": "날짜 경계를 정한 시간대."
          },
          "date": {
            "type": "string",
            "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
            "description": "해석된 양력 날짜."
          },
          "korean_lunar": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LunarDate"
              }
            ],
            "description": "그 날짜의 한국 음력 날짜."
          },
          "calendar_day_ganzhi": {
            "$ref": "#/components/schemas/CalendarDayGanzhi"
          },
          "day_start": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DayEdge"
              }
            ],
            "description": "그 날짜의 첫 순간."
          },
          "day_end": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DayEdge"
              }
            ],
            "description": "다음 날짜의 첫 순간(이 날짜에 속하지 않는다)."
          },
          "at_start": {
            "allOf": [
              {
                "$ref": "#/components/schemas/YearMonthPillars"
              }
            ],
            "description": "`day_start` 순간의 연주·월주. 절이 정확히 그 순간에 들어오면 새 기둥이다(같음 포함)."
          },
          "transitions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GanzhiTransition"
            },
            "maxItems": 2,
            "description": "`day_start` < 절 < `day_end`인 절, 시간순. 대부분의 날은 빈 배열이다. 절이 있는 날은 그 시각 전후로 연·월주가 다르므로, 시각이 정해진 사건은 이 시각과 비교해 고른다."
          },
          "boundary_status": {
            "type": "string",
            "enum": [
              "stable",
              "sensitive",
              "unknown"
            ],
            "description": "`/v1/charts`의 `uncertainty.boundary_status`와 같은 뜻. `stable`=절 시각 오차와 시각 변환 오차를 더해도 어떤 절도 날짜의 시작·끝을 넘지 못한다. `sensitive`=넘을 수 있다(또는 음력 입력의 달 경계가 오차 안에 있다). `unknown`=필요한 오차 상한이 없다 — 1972년 이전(민간시를 UT1 근사로 읽음)과 윤초표 만료 이후. `unknown`이어도 명목 결과는 그대로 정답 후보이며, 얼마나 가까운지는 `nearest_jie_seconds`로 판단한다."
          },
          "nearest_jie_seconds": {
            "type": "number",
            "minimum": 0,
            "description": "가장 가까운 절과 `day_start`·`day_end` 사이의 물리 시간 거리(초, 날짜 밖의 절 포함). 명목 거리이며 신뢰 보장이 아니다."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "description": "경고 코드. `HISTORICAL_TIME_APPROXIMATION`=1972년 이전 UT1 근사, `FUTURE_TIME_MODEL`=윤초 확정 범위 이후의 잠정 시간 모델, `MODEL_ERROR_UNKNOWN`=오차 상한 미상, `LUNAR_BOUNDARY_UNKNOWN`=음력 입력의 달 경계 오차 상한 없음, `LUNAR_BEYOND_OFFICIAL_RANGE`=날짜가 KASI 공식 음양력 대조 구간의 마지막 날(2050-12-13) 이후 — 음력 날짜가 공식 기준자료 없는 계산 확장이다."
          }
        },
        "required": [
          "dataset_id",
          "policy_id",
          "lunar_meridian",
          "timezone",
          "date",
          "korean_lunar",
          "calendar_day_ganzhi",
          "day_start",
          "day_end",
          "at_start",
          "transitions",
          "boundary_status",
          "nearest_jie_seconds",
          "warnings"
        ],
        "additionalProperties": false,
        "description": "`GET /v1/ganzhi` 응답."
      }
    }
  },
  "tags": [
    {
      "name": "Charts",
      "description": "사주팔자·대운·파생 정보 계산. 출생 정보는 POST 본문으로만 받는다."
    },
    {
      "name": "Reference",
      "description": "절기·정책·가용 데이터 번들 같은 참조 데이터 조회."
    },
    {
      "name": "Health",
      "description": "오케스트레이터(Kubernetes 등)용 생존·준비 상태 프로브. 인증과 레이트 리밋이 없고 로그에 기록하지 않는다."
    }
  ]
}
