{
  "openapi": "3.1.0",
  "info": {
    "title": "Snap API: FX Reference Rate",
    "description": "현물·포워드·커브와 통화쌍·결제일·영업일 정보를 제공한다.\n지원 범위와 필드 정의는 각 endpoint의 설명을 따른다.\n\n## 응답과 오류\n\n제품 API의 응답은 `{code, message, data}` 형식이다.\n요청은 유효하지만 필요한 시세가 없으면 `code: 0`이며 해당 값은 `null`이다.\n`null` 필드는 생략하지 않는다. `health`와 `availability`는 별도의 상태 응답 형식을 사용한다.\n\n| 상황 | 코드 |\n| -- | -- |\n| 지원하지 않는 통화쌍·만기, 잘못된 날짜, 필수 입력 누락 | `4000` |\n| 만기일이 결제일보다 빠르거나 같음 | `4220` |\n| 저장소 조회 실패 또는 필요한 계산 기준정보 없음 | `5000` |\n\n저장소 장애는 시세 데이터가 없는 정상 상태와 구분한다.\nMCP는 사용할 수 있는 시세가 없으면 `data_unavailable` 오류를 반환한다.\n\n## 시각과 날짜\n\n날짜는 `YYYY-MM-DD`다. 제품 API 응답의 시각(`referenceAt`, `lastAt`)은 밀리초까지인\n`YYYY-MM-DDTHH:mm:ss.SSSZ`이고, `timeZone`을 주면 `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm`처럼 그 지역의 오프셋으로\n끝난다. 날짜 값은 `timeZone`과 무관하다.\n응답 `meta`의 `requestAt`·`responseAt`은 요청 처리시각이며 항상 UTC 밀리초다.\n`availability`의 `as_of`는 가용성 판정시각이며 이 형식을 따르지 않는다. UTC ISO-8601이고 초 미만 자릿수는\n0이 아닐 때만 붙는다(예: `2026-08-12T09:20:00Z`).\n\n결제일·영업일 계산은 해당 통화쌍의 지원 관례와 발행된 휴일 기준정보를 사용한다.\n필요한 기준정보가 없으면 영업일을 추정하지 않고 오류를 반환한다.\n현물 조회 지원이 포워드·커브·결제일 계산 지원을 의미하지는 않는다.\n\n## 접근\n\n외부 접근의 인증·인가와 요청 제한은 게이트웨이 정책을 따른다.",
    "contact": {
      "name": "KustodyFi Server"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://github.com/KustodyFi/backend-snap-api"
    },
    "version": "0.1.0",
    "summary": "현물 환율, 커브 및 결제일 계산을 제공하는 참조용 API"
  },
  "servers": [
    {
      "url": "https://snap-api.kustodyfi.com"
    }
  ],
  "tags": [
    {
      "name": "quotes",
      "description": "\n        현물·포워드·스왑포인트·FxF 조회.\n        포워드·텀 스트럭처·스왑포인트는 USDKRW와 USDJPY를 지원한다.\n\n        유효한 요청에 필요한 데이터가 없으면 `code: 0`으로 응답하고 해당 값은 `null`이다.\n        MCP도 같은 빈 값 응답을 반환한다. 저장소 조회 실패는 `5000`이다.\n\n        현물 지원 범위는 현물 endpoint 설명을 따른다. 현물 조회 가능 여부만으로 포워드·커브·\n        결제일 계산의 지원을 판단하지 않는다. 계산용 통화쌍 목록은 `/v1/fx/currency-pairs`에서 확인한다.\n        임의 날짜의 포워드는 커브 구간 내 보간값이며, 구간 밖으로 외삽하지 않는다.\n    "
    },
    {
      "name": "calculate",
      "description": "\n        거래일과 tenor로 결제일·만기일을 영업일 기준으로 산출한다. 휴일 판정은\n        영업일 계산(`/v1/fx/business-days`)과 같은 Holiday Calendar를 쓴다.\n\n        휴일 데이터에는 규칙으로 산출한 잠정 구간이 있어 공식 휴일 공시 뒤 결과가 바뀔 수 있다.\n        캘린더가 덮지 않는 날짜는 추정하지 않고 오류로 실패한다. 오류 코드와 응답 필드는 각 operation을 따른다.\n    "
    },
    {
      "name": "reference",
      "description": "\n        지원 tenor·통화쌍·계산 규약. 식이 없고 스냅샷을 읽지 않으므로 응답이 항상 일정하다.\n        \"데이터 없음\" 상태가 없어 `referenceAt` 같은 신선도 신호도 없다.\n    "
    },
    {
      "name": "curve",
      "description": "\n        USD·KRW zero curve 조회.\n\n        `referenceAt`은 커브 입력의 원천 관측시각이며 신선도를 판단하는 기준이다.\n\n        ### 이 값은 우리가 계산한 것이다\n\n        관측된 금리로부터 산출한 값이다. 응답의 관측시각을 확인한다.\n\n        ### 날짜는 둘 중 하나로 묻는다\n\n        `date`(만기일)나 `tenor` 중 **정확히 하나**를 준다. 둘 다 주거나 둘 다 안 주면 `4000`이다.\n        `tenor`로 물으면 서버가 만기일을 산출해 응답의 `date`에 담는다.\n\n        **만기일을 세는 관례는 통화마다 다르다.** 서버가 산출한 만기일은 응답의 `maturityDate`로\n        확인한다.\n\n        ### 기준일은 요청이 정하지 않는다\n\n        항상 최신 스냅샷 하나를 쓴다. 응답의 `curveBaseDate`가 그 스냅샷에서 산출한 커브\n        기준일이고, **FX 결제일과 다를 수 있다.** USD 커브는 미국 전용 캘린더 T+2, FX 결제일은\n        미국·한국 공동 캘린더다.\n\n        ### 값이 없는 것은 오류가 아니다\n\n        스냅샷이 아직 없거나 물어본 날짜가 커브 범위 밖이면 `code: 0`으로 응답하고 값만\n        `null`이다. 커브 끝을 넘는 날짜를 **외삽하지 않는다.**\n\n        ### 지원 통화는 USD와 KRW다\n\n        JPY 등 그 밖의 통화는 `4000`이다. 지원하지 않는 것과 값이 없는 것을 구분하기 위해\n        오류로 답한다. 지원 통화의 표가 비어 있을 때는 `4000`이 아니라 `200`에 `null`이다.\n    "
    },
    {
      "name": "open-api reference",
      "description": "\n        파트너용 참조 데이터. 응답은 `reference` 태그의 같은 이름 endpoint 와 동일하며,\n        요구하는 자격증명이 opaque API key 라는 점만 다르다.\n    "
    },
    {
      "name": "spot",
      "description": "로그인 전 화면에서 사용하는 지시적 현물 가격과 호가"
    }
  ],
  "paths": {
    "/v1/fx/settlement-days": {
      "post": {
        "tags": [
          "calculate"
        ],
        "summary": "Settlement Dates",
        "description": "\n            Calculates the settlement dates for a trade date and tenor.\n            For standard tenors, `spotDate` is the spot settlement date from the currency pair's spot lag,\n            and `maturityDate` adds the tenor to it and adjusts to a business day.\n            Month and year tenors follow the end-of-month convention: when `spotDate` is the last business day\n            of its month, `maturityDate` is the last business day of the target month.\n            For `ON`, `TN` and `SN`, `spotDate` is the near leg and `maturityDate` is the far leg,\n            and the trade date must be a business day.\n            Business days follow the settlement calendars of the currency pair.\n            Dates outside the published calendar coverage return error code 4220 without estimating dates.\n            Holiday data for later years can come from rule-generated calendars, and calculation conventions\n            are provisional, so results may change when official holiday schedules are published.\n        ",
        "operationId": "settlementDays",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettlementDaysRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "정상 산출.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSettlementDaysResponse"
                },
                "examples": {
                  "spot-1m": {
                    "summary": "2026-06-26 거래, 1M: spot이 월말 영업일이라 만기도 월말 영업일",
                    "description": "spot-1m",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "tradeDate": "2026-06-26",
                        "spotDate": "2026-06-30",
                        "maturityDate": "2026-07-31",
                        "daysToMaturity": 31,
                        "businessDays": 22,
                        "tenor": "1M",
                        "spotDays": 2,
                        "isBusinessTradeDate": true
                      }
                    }
                  },
                  "overnight": {
                    "summary": "2026-06-26(금) 거래, ON: near는 거래일, far는 다음 영업일",
                    "description": "overnight",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "tradeDate": "2026-06-26",
                        "spotDate": "2026-06-26",
                        "maturityDate": "2026-06-29",
                        "daysToMaturity": 3,
                        "businessDays": 1,
                        "tenor": "ON",
                        "spotDays": 0,
                        "isBusinessTradeDate": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화쌍·만기, 날짜 형식 오류, 비영업일 거래일의 `ON`·`TN`·`SN` (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSettlementDaysResponse"
                }
              }
            }
          },
          "422": {
            "description": "캘린더가 덮지 않는 날짜 (`4220`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSettlementDaysResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/calculate-forward": {
      "post": {
        "tags": [
          "quotes"
        ],
        "summary": "Forward Exchange Rate",
        "description": "\n            Returns the two-way forward exchange rate for a currency pair at one forward value date.\n            Give the date either as `maturityDate` or as a standard `tenor` of the pair; send exactly one.\n            Supported tenors differ by pair; read them from `GET /v1/fx/tenors`.\n            The forward starts from the current spot settlement date, returned as `spotDate` with the\n            resolved `maturityDate` and the calendar-day count `days`.\n            `bid` and `ask` are in quote currency per USD 1. They are both null when a two-way forward is\n            unavailable, for example outside the quoted curve; no midpoint replaces them and nothing is extrapolated.\n            `referenceAt` is the observation time in timeZone (UTC when omitted or null); calendar dates are unchanged.\n        ",
        "operationId": "calculateForward",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalculateForwardRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "정상 산출. 두 방향 값을 낼 수 없으면 `bid`·`ask`가 함께 `null`이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalculateForwardResponse"
                },
                "examples": {
                  "tenor": {
                    "summary": "tenor로 조회",
                    "description": "tenor",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "tenor": "1M",
                        "maturityDate": "2026-09-24",
                        "spotDate": "2026-08-24",
                        "days": 31,
                        "bid": 1390.34,
                        "ask": 1390.76,
                        "referenceAt": "2026-08-25T06:00:00.000Z"
                      }
                    }
                  },
                  "beyond-curve": {
                    "summary": "커브보다 먼 만기: 외삽하지 않는다",
                    "description": "beyond-curve",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "tenor": null,
                        "maturityDate": "2028-01-01",
                        "spotDate": "2026-08-24",
                        "days": 495,
                        "bid": null,
                        "ask": null,
                        "referenceAt": "2026-08-25T06:00:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화쌍, 날짜 형식 오류, `maturityDate`와 `tenor`를 둘 다 주거나 둘 다 생략, 그 통화쌍에 없는 tenor (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalculateForwardResponse"
                }
              }
            }
          },
          "422": {
            "description": "`maturityDate`가 `spotDate` 이하 (`4220`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalculateForwardResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/calculate-dates": {
      "post": {
        "tags": [
          "calculate"
        ],
        "summary": "Settlement and Maturity Dates",
        "description": "\n            Calculates `spotDate` from `tradeDate` and the currency pair's spot lag, then adds the tenor and\n            adjusts to obtain `maturityDate`. Business days require both New York and Seoul to be open.\n            `isTradeDateBusinessDay`, `isSpotDateEom` and `eomRuleApplied` explain the result;\n            end of month means the last business day, not necessarily the last calendar day.\n            `spotDays` overrides the default lag. Disabling `eomRule` skips the end-of-month rule.\n            For `OVERNIGHT`, `TOM_NEXT` and `SPOT_NEXT` anchors, business-day offsets replace the tenor.\n            Calendar coverage is currently limited to 2026; unavailable coverage returns error code 5000 without estimating dates.\n            Calculation conventions and holiday data are provisional and results may change.\n        ",
        "operationId": "calculateDates",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CalculateDatesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "정상 산출. 12필드가 모두 채워진다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalculateDatesResponse"
                },
                "examples": {
                  "spot-1m": {
                    "summary": "2026-06-26 거래, 1M: 월말 규칙이 적용된 경우",
                    "description": "spot-1m",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "tradeDate": "2026-06-26",
                        "spotDate": "2026-06-30",
                        "maturityDate": "2026-07-31",
                        "daysToMaturity": 31,
                        "businessDays": 22,
                        "calendars": [
                          "usd_ny",
                          "krw_bank"
                        ],
                        "tenor": "1M",
                        "spotDays": 2,
                        "isTradeDateBusinessDay": true,
                        "isSpotDateEom": true,
                        "eomRuleApplied": true
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화쌍·만기, 날짜 형식 오류, `spotDays` 범위 밖 (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalculateDatesResponse"
                }
              }
            }
          },
          "500": {
            "description": "\n            적용 가능한 `PUBLISHED` holiday revision이 없다 (`5000`). 요청이 잘못된 것이\n            아니라 **운영 데이터가 그 날짜를 덮지 않는다**는 뜻이다.\n        ",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalculateDatesResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/zero-rate": {
      "get": {
        "tags": [
          "curve"
        ],
        "summary": "Zero Rate",
        "description": "\n            Returns a zero rate for USD or KRW, for exactly one of `maturityDate` or `tenor`. The deprecated `date` alias is accepted only without `maturityDate`.\n            A tenor resolves to a maturity date by that currency's convention; the response returns it as `maturityDate`.\n            Other currencies are unsupported and answer 4000, which is different from a supported currency with no value.\n            The rate is derived from the discount factor using simple interest: `DF = 1 / (1 + r * yf)`.\n            `curveBaseDate` is the curve's base date and may differ from the FX settlement date.\n            On the curve base date, `zeroRate` is null because the period is zero.\n            Missing data or dates outside the curve produce null values; there is no extrapolation.\n            `referenceAt` is the observation time of the curve inputs in timeZone (default UTC); calendar dates are unchanged.\n        ",
        "operationId": "zeroRate",
        "parameters": [
          {
            "name": "currency",
            "in": "query",
            "description": "통화. USD와 KRW",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "USD"
          },
          {
            "name": "date",
            "in": "query",
            "description": "Deprecated alias for maturityDate. Do not supply both date names. Format: `YYYY-MM-DD`.",
            "required": false,
            "deprecated": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2027-02-26"
          },
          {
            "name": "tenor",
            "in": "query",
            "description": "만기. `maturityDate`와 택일",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1D",
                "SN",
                "1W",
                "2W",
                "3W",
                "1M",
                "2M",
                "3M",
                "4M",
                "5M",
                "6M",
                "7M",
                "8M",
                "9M",
                "10M",
                "11M",
                "1Y",
                "12M"
              ]
            },
            "example": "6M"
          },
          {
            "name": "timeZone",
            "in": "query",
            "description": "IANA area/location time zone for response timestamps, for example Asia/Seoul. Omitted or null means UTC. Calendar dates and prices are unchanged. Timestamps then end in that zone's offset, for example `+09:00`, instead of `Z`.",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "default": "UTC"
            }
          },
          {
            "name": "maturityDate",
            "in": "query",
            "description": "Maturity calendar date. Use either maturityDate or tenor, not both. Format: `YYYY-MM-DD`.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2027-02-26"
          }
        ],
        "responses": {
          "200": {
            "description": "조회 성공. 스냅샷이 없거나 커브 범위 밖이면 `zeroRate`가 `null`이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseZeroRateResponse"
                },
                "examples": {
                  "tenor": {
                    "summary": "tenor로 물으면 만기일을 서버가 산출해 돌려준다",
                    "description": "tenor",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "currency": "USD",
                        "tenor": "6M",
                        "maturityDate": "2027-02-26",
                        "date": "2027-02-26",
                        "curveBaseDate": "2026-08-26",
                        "daycount": "ACT/360",
                        "yearFraction": 0.5083,
                        "zeroRate": 0.0405,
                        "referenceAt": "2026-08-24T06:00:00.000Z"
                      }
                    }
                  },
                  "outside-curve": {
                    "summary": "커브 마지막 만기보다 먼 날짜. 외삽하지 않는다",
                    "description": "outside-curve",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "currency": "USD",
                        "tenor": null,
                        "maturityDate": "2031-01-02",
                        "date": "2031-01-02",
                        "curveBaseDate": "2026-08-26",
                        "daycount": "ACT/360",
                        "yearFraction": 4.3778,
                        "zeroRate": null,
                        "referenceAt": "2026-08-24T06:00:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화, 또는 `date`·`tenor`를 둘 다 주거나 둘 다 안 줌 (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseZeroRateResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/term-structure": {
      "get": {
        "tags": [
          "quotes"
        ],
        "summary": "Swap Point Term Structure",
        "description": "\n            Returns swap points by tenor for the currency pair, measured from the current spot settlement date\n            returned as `spotDate`. Each point repeats that `spotDate`. Tenors without a quote are omitted.\n            `bid` and `ask` are in quote currency per USD 1 with the sign of the market quote; negative and zero\n            values are valid. They are null when a two-way quote is unavailable; no midpoint replaces them.\n            Before the market quotes ON and TN for the day, those two rows are estimated from the swap curve and marked\n            `interpolated: true`; when they cannot be estimated, their values are null. No tenor is extrapolated.\n            For ON and TN, `days` counts settlement business days from `spotDate` back to the near-leg date, usually -2\n            and -1; for other tenors it is the calendar days from `spotDate` to `maturityDate`.\n            With no available quotes, `points` is empty and `referenceAt` is null.\n            `referenceDate` is the UTC date of the source observation, independent of timeZone.\n            Timestamps use timeZone (default UTC); calendar dates are unchanged.\n        ",
        "operationId": "termStructure",
        "parameters": [
          {
            "name": "pair",
            "in": "query",
            "description": "통화쌍",
            "required": false,
            "schema": {
              "type": "string",
              "default": "USDKRW"
            },
            "example": "USDKRW"
          },
          {
            "name": "timeZone",
            "in": "query",
            "description": "IANA area/location time zone for response timestamps, for example Asia/Seoul. Omitted or null means UTC. Calendar dates and prices are unchanged. Timestamps then end in that zone's offset, for example `+09:00`, instead of `Z`.",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "default": "UTC"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "조회 성공. 호가가 없으면 `points`가 비고 `referenceAt`이 `null`이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseTermStructureResponse"
                },
                "examples": {
                  "intraday": {
                    "summary": "장중: ON·TN은 커브로 추정한 값",
                    "description": "intraday",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "spotDate": "2026-08-24",
                        "referenceDate": "2026-08-20",
                        "points": [
                          {
                            "tenor": "ON",
                            "spotDate": "2026-08-24",
                            "maturityDate": "2026-08-21",
                            "days": -2,
                            "bid": -0.04,
                            "ask": -0.02,
                            "interpolated": true
                          },
                          {
                            "tenor": "TN",
                            "spotDate": "2026-08-24",
                            "maturityDate": "2026-08-24",
                            "days": -1,
                            "bid": -0.05,
                            "ask": -0.03,
                            "interpolated": true
                          },
                          {
                            "tenor": "1W",
                            "spotDate": "2026-08-24",
                            "maturityDate": "2026-08-31",
                            "days": 7,
                            "bid": -0.22,
                            "ask": -0.2,
                            "interpolated": false
                          }
                        ],
                        "referenceAt": "2026-08-20T06:00:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화쌍 (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseTermStructureResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/tenors": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Supported Tenors",
        "description": "\n            Lists the standard FX forward tenors supported for the requested currency pair in `fxForward`.\n            `pair` is required and supports the published pairs USDKRW and USDJPY. Unsupported pairs return `4000`.\n            Overnight, tomorrow-next and spot-next are settlement anchors, so ON, TN and SN are not included here.\n        ",
        "operationId": "tenors",
        "parameters": [
          {
            "name": "pair",
            "in": "query",
            "description": "통화쌍",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "USDKRW"
          }
        ],
        "responses": {
          "200": {
            "description": "요청한 지원 통화쌍의 표준 FX forward 만기 목록.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseTenorsResponse"
                },
                "examples": {
                  "supported": {
                    "description": "supported",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "fxForward": [
                          "1W",
                          "1M",
                          "2M",
                          "3M",
                          "6M",
                          "9M",
                          "1Y"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "필수 `pair` 누락 또는 미지원 통화쌍 (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseTenorsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/swap-point": {
      "get": {
        "tags": [
          "quotes"
        ],
        "summary": "Forward Swap Points",
        "description": "\n            Returns two-way swap points for a swap from `nearDate` to `farDate` in quote currency per USD 1.\n            `farDate` must follow `nearDate`. Both legs are measured from the current spot settlement date, returned\n            as `spotDate`; a `nearDate` on `spotDate` is a spot-starting swap.\n            `swapPoint.bid` is the far-leg bid minus the near-leg ask and `swapPoint.ask` is the far-leg ask minus the\n            near-leg bid, so the spread includes both legs. Bid is rounded down and ask up to 2 decimal places.\n            `days` is the calendar-day count from `nearMaturityDate` to `farMaturityDate`.\n            `swapPoint` is null when a two-way price is unavailable, including a `nearDate` before `spotDate` or a date\n            outside the quoted curve; no midpoint replaces it and nothing is extrapolated.\n            `referenceAt` is the observation time in timeZone (UTC when omitted or null); calendar dates are unchanged.\n        ",
        "operationId": "swapPoint",
        "parameters": [
          {
            "name": "pair",
            "in": "query",
            "description": "통화쌍",
            "required": false,
            "schema": {
              "type": "string",
              "default": "USDKRW"
            },
            "example": "USDKRW"
          },
          {
            "name": "nearDate",
            "in": "query",
            "description": "near leg 결제일 Format: `YYYY-MM-DD`.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-09-24"
          },
          {
            "name": "farDate",
            "in": "query",
            "description": "far leg 결제일 Format: `YYYY-MM-DD`.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-11-24"
          },
          {
            "name": "timeZone",
            "in": "query",
            "description": "IANA area/location time zone for response timestamps, for example Asia/Seoul. Omitted or null means UTC. Calendar dates and prices are unchanged. Timestamps then end in that zone's offset, for example `+09:00`, instead of `Z`.",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "default": "UTC"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "정상 산출. 두 방향 값을 낼 수 없으면 `swapPoint`가 `null`이다. `nearDate`가 `spotDate`보다 이르면 오류가 아니라 `null`이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSwapPointResponse"
                },
                "examples": {
                  "1m-3m": {
                    "summary": "1M에서 3M까지",
                    "description": "1m-3m",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "spotDate": "2026-08-24",
                        "nearMaturityDate": "2026-09-24",
                        "farMaturityDate": "2026-11-24",
                        "days": 61,
                        "swapPoint": {
                          "bid": -2.1,
                          "ask": -1.76
                        },
                        "referenceAt": "2026-08-25T06:00:00.000Z"
                      }
                    }
                  },
                  "near-before-spot": {
                    "summary": "near가 결제일보다 이르다: 값 없음",
                    "description": "near-before-spot",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "spotDate": "2026-08-24",
                        "nearMaturityDate": "2026-08-21",
                        "farMaturityDate": "2026-09-24",
                        "days": 34,
                        "swapPoint": null,
                        "referenceAt": "2026-08-25T06:00:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화쌍, 날짜 형식 오류 (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSwapPointResponse"
                }
              }
            }
          },
          "422": {
            "description": "`farDate`가 `nearDate` 이하 (`4220`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSwapPointResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/spot": {
      "get": {
        "tags": [
          "quotes"
        ],
        "summary": "Spot Exchange Rate",
        "description": "\n            Returns the spot bid and ask, settlement date and last trade for a currency pair. USDKRW is supported;\n            USDJPY is available when its spot service is enabled. Spot availability does not imply support for other calculations.\n            `bid` and `ask` are quotes in quote currency per USD 1. When a midpoint is needed, compute it from both sides.\n            Missing or expired quotes produce null `bid` and `ask`; decide price availability from these two fields.\n            `referenceAt` is the observation time of the spot source data, not a retrieval time, and is null when no\n            observation exists. It can be present without quotes. It uses ISO-8601 with milliseconds in timeZone\n            (UTC when omitted or null).\n            `last` and `lastAt` describe the last available trade, independently of quote expiry.\n            Pairs without a trade source, currently USDJPY, return null for both.\n            `lastAt` uses the same millisecond format and timeZone, but the trade clock has second resolution, so its\n            fractional part is always .000. Its date is inferred from the observation date\n            and may be inaccurate for trades retransmitted after a market closure.\n            A last trade does not necessarily represent a recent trade.\n        ",
        "operationId": "spot",
        "parameters": [
          {
            "name": "pair",
            "in": "query",
            "description": "통화쌍",
            "required": false,
            "schema": {
              "type": "string",
              "default": "USDKRW"
            },
            "example": "USDKRW"
          },
          {
            "name": "timeZone",
            "in": "query",
            "description": "IANA area/location time zone for response timestamps, for example Asia/Seoul. Omitted or null means UTC. Calendar dates and prices are unchanged. Timestamps then end in that zone's offset, for example `+09:00`, instead of `Z`.",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "default": "UTC"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "조회 성공. 호가가 없거나 만료되면 `bid`·`ask`가 `null`이다. 가격 유무는 `referenceAt`이 아니라 `bid`·`ask`로 판단한다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSpotResponse"
                },
                "examples": {
                  "no-quote": {
                    "summary": "지원 쌍인데 호가가 없다. 오류가 아니다",
                    "description": "no-quote",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "pair": "USDKRW",
                        "spotDate": null,
                        "referenceAt": null,
                        "bid": null,
                        "ask": null,
                        "last": null,
                        "lastAt": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화쌍 (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseSpotResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/discount-factor": {
      "get": {
        "tags": [
          "curve"
        ],
        "summary": "Discount Factor",
        "description": "\n            Returns a discount factor and corresponding simple-interest zero rate for USD or KRW, for exactly one\n            of `maturityDate` or `tenor`. The deprecated `date` alias is accepted only without `maturityDate`.\n            A tenor resolves to a maturity date by that currency's convention; the response returns it as `maturityDate`.\n            Other currencies are unsupported and answer 4000, which is different from a supported currency with no value.\n            `curveBaseDate` is the curve's base date and may differ from the FX settlement date.\n            On the curve base date, the discount factor is 1.0 and `zeroRate` is null.\n            Missing data or dates outside the curve produce null values; there is no extrapolation.\n            `referenceAt` is the observation time of the curve inputs in timeZone (default UTC); calendar dates are unchanged.\n        ",
        "operationId": "discountFactor",
        "parameters": [
          {
            "name": "currency",
            "in": "query",
            "description": "통화. USD와 KRW",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "USD"
          },
          {
            "name": "date",
            "in": "query",
            "description": "Deprecated alias for maturityDate. Do not supply both date names. Format: `YYYY-MM-DD`.",
            "required": false,
            "deprecated": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2027-02-26"
          },
          {
            "name": "tenor",
            "in": "query",
            "description": "만기. `maturityDate`와 택일",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1D",
                "SN",
                "1W",
                "2W",
                "3W",
                "1M",
                "2M",
                "3M",
                "4M",
                "5M",
                "6M",
                "7M",
                "8M",
                "9M",
                "10M",
                "11M",
                "1Y",
                "12M"
              ]
            },
            "example": "6M"
          },
          {
            "name": "timeZone",
            "in": "query",
            "description": "IANA area/location time zone for response timestamps, for example Asia/Seoul. Omitted or null means UTC. Calendar dates and prices are unchanged. Timestamps then end in that zone's offset, for example `+09:00`, instead of `Z`.",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "default": "UTC"
            }
          },
          {
            "name": "maturityDate",
            "in": "query",
            "description": "Maturity calendar date. Use either maturityDate or tenor, not both. Format: `YYYY-MM-DD`.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2027-02-26"
          }
        ],
        "responses": {
          "200": {
            "description": "조회 성공. 스냅샷이 없거나 커브 범위 밖이면 `discountFactor`가 `null`이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseDiscountFactorResponse"
                },
                "examples": {
                  "date": {
                    "description": "date",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "currency": "USD",
                        "tenor": null,
                        "maturityDate": "2027-02-26",
                        "date": "2027-02-26",
                        "curveBaseDate": "2026-08-26",
                        "daycount": "ACT/360",
                        "yearFraction": 0.5083,
                        "discountFactor": 0.9798,
                        "zeroRate": 0.0405,
                        "referenceAt": "2026-08-24T06:00:00.000Z"
                      }
                    }
                  },
                  "curve-base-date": {
                    "summary": "커브 기준일 당일. 할인이 없고 금리는 정의되지 않는다",
                    "description": "curve-base-date",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "currency": "USD",
                        "tenor": null,
                        "maturityDate": "2026-08-26",
                        "date": "2026-08-26",
                        "curveBaseDate": "2026-08-26",
                        "daycount": "ACT/360",
                        "yearFraction": 0,
                        "discountFactor": 1,
                        "zeroRate": null,
                        "referenceAt": "2026-08-24T06:00:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "미지원 통화, 또는 `date`·`tenor`를 둘 다 주거나 둘 다 안 줌 (`4000`)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseDiscountFactorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/currency-pairs": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Supported Currency Pairs",
        "description": "\n            Lists currencies and currency pairs published by the calculation conventions.\n            The plural list keys are `currencies` and `pairs`. Returns USDKRW and USDJPY in `pairs`,\n            with JPY, KRW and USD in `currencies`.\n            Spot and forward quote availability is described by each quote operation.\n            Currency and pair conventions are available from `/v1/fx/config`.\n        ",
        "operationId": "currencyPairs",
        "responses": {
          "200": {
            "description": "항상 이 응답이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCurrencyPairsResponse"
                },
                "examples": {
                  "supported": {
                    "description": "supported",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "currencies": [
                          "JPY",
                          "KRW",
                          "USD"
                        ],
                        "pairs": [
                          "USDKRW",
                          "USDJPY"
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/config": {
      "get": {
        "tags": [
          "reference"
        ],
        "summary": "Calculation Conventions",
        "description": "\n            Returns currency day-count conventions and calendars in `currencies`, and base currency,\n            quote currency, spot lag and point factor in `pairs`. A currency listed here has published\n            conventions; it does not follow that every product accepts it. JPY conventions are listed\n            while `zero-rate` and `discount-factor` still serve USD and KRW only.\n\n            `currencies` carries one row per currency, identified by `currency`. `daycount` is the\n            convention used to report rates from that currency's curve.\n\n            `pointFactor` converts a quote-currency change to swap points:\n            `points = (outright - spot) * pointFactor`. Convert points back to quote-currency units with\n            `outright = spot + points / pointFactor`. Both USDKRW and USDJPY currently use 100.\n            These are reference conventions; quote responses provide their applicable settlement dates.\n        ",
        "operationId": "config",
        "responses": {
          "200": {
            "description": "항상 이 응답이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseConfigResponse"
                },
                "examples": {
                  "conventions": {
                    "description": "conventions",
                    "value": {
                      "code": 0,
                      "message": "",
                      "data": {
                        "currencies": [
                          {
                            "currency": "JPY",
                            "daycount": "ACT/360",
                            "calendar": "JPY_TOKYO"
                          },
                          {
                            "currency": "KRW",
                            "daycount": "ACT/365",
                            "calendar": "KRW_SEOUL"
                          },
                          {
                            "currency": "USD",
                            "daycount": "ACT/360",
                            "calendar": "USD_NEWYORK"
                          }
                        ],
                        "pairs": [
                          {
                            "pair": "USDKRW",
                            "base": "USD",
                            "quote": "KRW",
                            "spotLag": 2,
                            "pointFactor": 100
                          },
                          {
                            "pair": "USDJPY",
                            "base": "USD",
                            "quote": "JPY",
                            "spotLag": 2,
                            "pointFactor": 100
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/fx/business-days": {
      "get": {
        "tags": [
          "calendar-controller"
        ],
        "summary": "Business Days",
        "description": "\n            Returns the date `day` business days from `date` in the calendar selected by `calendarCode`.\n            For example, `day` 2 from a Monday with no holidays returns the Wednesday.\n            `day` 0 returns `date` itself when it is a business day, and the next business day otherwise.\n            Negative values move backward; the range is -30 to 30. When `date` is not a business day,\n            counting starts from `date`, so `day` 1 from a Saturday returns the following Monday.\n            `isBusinessDay` and `reason` describe the requested `date`; `resultDate` is the answer.\n            `certainty` describes `resultDate`: PREDICTED when any day the search passed through comes from a\n            rule-generated revision, so the answer can move once the real holiday schedule is published.\n            Dates outside the calendar's published coverage return an error.\n        ",
        "operationId": "businessDays",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "description": "Format: `YYYY-MM-DD`.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "calendarCode",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "USD_NEWYORK",
                "KRW_SEOUL",
                "JPY_TOKYO"
              ]
            }
          },
          {
            "name": "day",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalendarDayResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/calendars/business-day": {
      "get": {
        "tags": [
          "calendar-controller"
        ],
        "summary": "Business Day",
        "description": "\n            Returns the business day at `offset` business days from `date` in the selected `calendar`.\n            `offset` 0 returns `date` itself when it is a business day, and the next business day otherwise.\n            Positive values move forward and negative values move backward; the range is -30 to 30.\n            `isBusinessDay` and `reason` describe the requested `date`; `resultDate` is the answer.\n            `certainty` describes `resultDate`: PREDICTED when any day the search passed through comes from a\n            rule-generated revision, so the answer can move once the real holiday schedule is published.\n            Dates outside the calendar's published coverage return an error.\n        ",
        "operationId": "businessDay",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "description": "Format: `YYYY-MM-DD`.",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "calendar",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "USD_NEWYORK",
                "KRW_SEOUL",
                "JPY_TOKYO"
              ]
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponseCalendarDayResponse"
                }
              }
            }
          }
        }
      }
    },
    "/fx/spot": {
      "get": {
        "tags": [
          "spot"
        ],
        "summary": "현물 가격과 호가 조회",
        "description": "\n            로그인, 세션, Bearer token 또는 API key 없이 사용하는 조회 계약이다.\n            price는 유효시간 미만의 최근 체결가이며, 그 외에는 bid와 ask의 중간값이다.\n            체결가 유효시간은 서버 설정을 따르며 기본 300초다. 정확히 유효시간에 도달하면 중간값을 사용한다.\n            USDKRW 체결시각은 /v1/fx/spot의 lastAt과 같은 추정 날짜 기준이다.\n            체결시각이 없거나 미래이면 중간값을 사용한다. USDJPY는 체결가가 없어 중간값을 사용한다.\n            bid와 ask는 제공 가능한 표시 호가이며 price가 체결가여도 유지된다.\n            사용할 호가가 없으면 해당 필드는 null이다. 체결가와 중간값이 모두 없으면 price는 null이다.\n            referenceAt은 price의 기준시각이며 호가 시각을 보장하지 않는다. 체결가이면 추정 체결시각이다.\n            가격 유효시간 판정에 쓰는 서버 시각은 응답에 싣지 않는다.\n            referenceAt은 timeZone의 밀리초 형식이며 생략하면 UTC다.\n        ",
        "operationId": "spot_1",
        "parameters": [
          {
            "name": "pair",
            "in": "query",
            "description": "통화쌍",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "USDKRW"
          },
          {
            "name": "timeZone",
            "in": "query",
            "description": "IANA area/location time zone for response timestamps, for example Asia/Seoul. Omitted or null means UTC. Calendar dates and prices are unchanged. Timestamps then end in that zone's offset, for example `+09:00`, instead of `Z`.",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "default": "UTC"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "조회 성공. 사용할 가격이 없으면 price가 null이다.",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponsePublicSpotResponse"
                }
              }
            }
          },
          "400": {
            "description": "pair 누락, 형식 오류 또는 미지원 통화쌍 (4000)",
            "content": {
              "*/*": {
                "schema": {
                  "$ref": "#/components/schemas/ApiResponsePublicSpotResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SettlementDaysRequest": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string",
            "default": "USDKRW"
          },
          "tradeDate": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "tenor": {
            "type": "string",
            "enum": [
              "ON",
              "TN",
              "SN",
              "1D",
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y"
            ]
          }
        },
        "required": [
          "tenor",
          "tradeDate"
        ]
      },
      "ApiResponseSettlementDaysResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SettlementDaysResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "ResponseMetadata": {
        "type": "object",
        "description": "Request-response metadata shared by REST and MCP results.",
        "properties": {
          "requestAt": {
            "type": "string",
            "description": "UTC time when the server received the request. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ`, always UTC.",
            "example": "2026-09-17T00:00:00.000Z"
          },
          "requestId": {
            "type": "string",
            "description": "Server-generated UUID for response and log correlation.",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "responseAt": {
            "type": "string",
            "description": "UTC time when the response envelope was created. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ`, always UTC.",
            "example": "2026-09-17T00:00:00.012Z"
          },
          "clientRequestId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Client correlation value received in X-Request-ID; null when absent.",
            "example": "snap_550e8400-e29b-41d4-a716-446655440000"
          }
        },
        "required": [
          "clientRequestId",
          "requestAt",
          "requestId",
          "responseAt"
        ]
      },
      "SettlementDaysResponse": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string"
          },
          "tradeDate": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "spotDate": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "maturityDate": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "daysToMaturity": {
            "type": "integer",
            "format": "int32"
          },
          "businessDays": {
            "type": "integer",
            "format": "int32"
          },
          "tenor": {
            "type": "string",
            "enum": [
              "ON",
              "TN",
              "SN",
              "1D",
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y"
            ]
          },
          "spotDays": {
            "type": "integer",
            "format": "int32"
          },
          "isBusinessTradeDate": {
            "type": "boolean",
            "description": "Whether tradeDate is a business day in the pair's settlement calendars. Standard tenors still return dates for a holiday trade date; ON, TN and SN reject it."
          }
        },
        "required": [
          "businessDays",
          "daysToMaturity",
          "isBusinessTradeDate",
          "maturityDate",
          "pair",
          "spotDate",
          "spotDays",
          "tenor",
          "tradeDate"
        ]
      },
      "CalculateForwardRequest": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string",
            "default": "USDKRW"
          },
          "maturityDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Forward value date. Provide exactly one of maturityDate and tenor. Format: `YYYY-MM-DD`."
          },
          "tenor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Standard FX forward tenor of the pair, as listed by `GET /v1/fx/tenors`. Provide exactly one of maturityDate and tenor.",
            "enum": [
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y"
            ]
          },
          "timeZone": {
            "type": [
              "string",
              "null"
            ],
            "default": "UTC",
            "description": "IANA area/location time zone for response timestamps, for example Asia/Seoul. Omitted or null means UTC. Calendar dates and prices are unchanged."
          }
        }
      },
      "ApiResponseCalculateForwardResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/CalculateForwardResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "CalculateForwardResponse": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string"
          },
          "tenor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Requested tenor, echoed; null when the request used maturityDate.",
            "enum": [
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y"
            ]
          },
          "maturityDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Forward value date. For a tenor request, the date the tenor resolves to; null when no observation is available to resolve it. Format: `YYYY-MM-DD`."
          },
          "spotDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Spot settlement date the forward is measured from; null without an observation. Format: `YYYY-MM-DD`."
          },
          "days": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int32",
            "description": "Calendar days from spotDate to maturityDate; null when either date is null."
          },
          "bid": {
            "type": [
              "number",
              "null"
            ],
            "description": "Forward bid in quote currency per USD 1, rounded down to 2 decimal places; null with ask when a two-way forward is unavailable."
          },
          "ask": {
            "type": [
              "number",
              "null"
            ],
            "description": "Forward ask in quote currency per USD 1, rounded up to 2 decimal places; null with bid when a two-way forward is unavailable."
          },
          "referenceAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Observation time of the source values in timeZone (default UTC), with milliseconds; null when no observation backs the response. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`."
          }
        },
        "required": [
          "pair"
        ]
      },
      "CalculateDatesRequest": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string",
            "default": "USDKRW"
          },
          "tradeDate": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "tenor": {
            "type": "string",
            "enum": [
              "1D",
              "SN",
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y",
              "12M"
            ]
          },
          "spotDays": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int32"
          },
          "eomRule": {
            "type": "boolean",
            "default": true,
            "description": "Applies to month and year tenors only. Day and week tenors roll Following and are unaffected by this flag; `eomRuleApplied` reports what was used."
          },
          "anchor": {
            "type": "string",
            "default": "SPOT",
            "enum": [
              "SPOT",
              "OVERNIGHT",
              "TOM_NEXT",
              "SPOT_NEXT"
            ]
          }
        },
        "required": [
          "tenor",
          "tradeDate"
        ]
      },
      "ApiResponseCalculateDatesResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/CalculateDatesResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "CalculateDatesResponse": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string"
          },
          "tradeDate": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "spotDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "maturityDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "daysToMaturity": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int32"
          },
          "businessDays": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int32"
          },
          "calendars": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tenor": {
            "type": "string",
            "enum": [
              "1D",
              "SN",
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y",
              "12M"
            ]
          },
          "spotDays": {
            "type": "integer",
            "format": "int32"
          },
          "isTradeDateBusinessDay": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "isSpotDateEom": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "eomRuleApplied": {
            "type": "boolean"
          }
        },
        "required": [
          "calendars",
          "eomRuleApplied",
          "pair",
          "spotDays",
          "tenor",
          "tradeDate"
        ]
      },
      "ApiResponseZeroRateResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/ZeroRateResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "ZeroRateResponse": {
        "type": "object",
        "description": "지정 통화·날짜의 zero rate. 커브 범위 밖이면 `zeroRate`가 `null`이다.",
        "properties": {
          "currency": {
            "type": "string",
            "enum": [
              "JPY",
              "KRW",
              "USD"
            ]
          },
          "tenor": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "1D",
              "SN",
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y",
              "12M"
            ]
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "deprecated": true,
            "description": "Use maturityDate. Format: `YYYY-MM-DD`."
          },
          "curveBaseDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "daycount": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ACT/360",
              "ACT/365"
            ]
          },
          "yearFraction": {
            "type": [
              "number",
              "null"
            ],
            "format": "double"
          },
          "zeroRate": {
            "type": [
              "number",
              "null"
            ],
            "format": "double"
          },
          "referenceAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Observation time of the source values in timeZone (default UTC), with milliseconds; null when no observation backs the response. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`."
          },
          "maturityDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Resolved maturity calendar date; unaffected by timeZone. Format: `YYYY-MM-DD`."
          }
        },
        "required": [
          "currency"
        ]
      },
      "ApiResponseTermStructureResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/TermStructureResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "TermStructurePoint": {
        "type": "object",
        "properties": {
          "tenor": {
            "type": "string"
          },
          "spotDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Spot settlement date the point is measured from; the same value as the response's spotDate. Null without an observation. Format: `YYYY-MM-DD`."
          },
          "maturityDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Maturity (far-leg value) date; unaffected by timeZone. Format: `YYYY-MM-DD`."
          },
          "days": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int32",
            "description": "For ON and TN, settlement business days from spotDate back to the near-leg date, usually -2 and -1, but fewer when a holiday falls in between. For other tenors, calendar days from spotDate to maturityDate."
          },
          "bid": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bid in quote currency per USD 1; null when not provided."
          },
          "ask": {
            "type": [
              "number",
              "null"
            ],
            "description": "Ask in quote currency per USD 1; null when not provided."
          },
          "interpolated": {
            "type": "boolean",
            "description": "True when the value is estimated from the curve because the market has not quoted this tenor yet. Only ON and TN can be estimated; nothing is extrapolated."
          }
        },
        "required": [
          "interpolated",
          "tenor"
        ]
      },
      "TermStructureResponse": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string"
          },
          "spotDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Spot settlement date the points are measured from (business-day T+2 convention from the observation date); null without an observation. Format: `YYYY-MM-DD`."
          },
          "referenceDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "points": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TermStructurePoint"
            }
          },
          "referenceAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Observation time of the source values in timeZone (default UTC), with milliseconds; null when no observation backs the response. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`."
          }
        },
        "required": [
          "pair",
          "points"
        ]
      },
      "ApiResponseTenorsResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/TenorsResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "TenorsResponse": {
        "type": "object",
        "properties": {
          "fxForward": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "1W",
                "2W",
                "3W",
                "1M",
                "2M",
                "3M",
                "4M",
                "5M",
                "6M",
                "7M",
                "8M",
                "9M",
                "10M",
                "11M",
                "1Y"
              ]
            }
          }
        },
        "required": [
          "fxForward"
        ]
      },
      "ApiResponseSwapPointResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SwapPointResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "SwapPointQuote": {
        "type": "object",
        "properties": {
          "bid": {
            "type": "number",
            "description": "Far-leg bid minus near-leg ask, in quote currency per USD 1, rounded down to 2 decimal places."
          },
          "ask": {
            "type": "number",
            "description": "Far-leg ask minus near-leg bid, in quote currency per USD 1, rounded up to 2 decimal places."
          }
        },
        "required": [
          "ask",
          "bid"
        ]
      },
      "SwapPointResponse": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string"
          },
          "spotDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Spot settlement date of the observation; a nearDate before it has no price. Null without an observation. Format: `YYYY-MM-DD`."
          },
          "nearMaturityDate": {
            "type": "string",
            "format": "date",
            "description": "Near-leg value date, as requested; unaffected by timeZone. Format: `YYYY-MM-DD`."
          },
          "farMaturityDate": {
            "type": "string",
            "format": "date",
            "description": "Far-leg value date, as requested; unaffected by timeZone. Format: `YYYY-MM-DD`."
          },
          "days": {
            "type": "integer",
            "format": "int32",
            "description": "Calendar days from nearMaturityDate to farMaturityDate."
          },
          "swapPoint": {
            "description": "Two-way swap points from nearMaturityDate to farMaturityDate; null when a two-way price is unavailable. Never one-sided or a midpoint.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SwapPointQuote"
              },
              {
                "type": "null"
              }
            ]
          },
          "referenceAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Observation time of the source values in timeZone (default UTC), with milliseconds; null when no observation backs the response. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`."
          }
        },
        "required": [
          "days",
          "farMaturityDate",
          "nearMaturityDate",
          "pair"
        ]
      },
      "ApiResponseSpotResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/SpotResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "SpotResponse": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string"
          },
          "spotDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "referenceAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Observation time of the source values in timeZone (default UTC), with milliseconds; null when no observation backs the response. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`."
          },
          "bid": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bid in quote currency per USD 1; null when this side is not provided."
          },
          "ask": {
            "type": [
              "number",
              "null"
            ],
            "description": "Ask in quote currency per USD 1; null when this side is not provided."
          },
          "last": {
            "type": [
              "number",
              "null"
            ],
            "description": "Last traded price in quote currency per USD 1; null for pairs without a trade source and when no trade is available."
          },
          "lastAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Estimated time of the last trade in timeZone (default UTC), rendered with milliseconds; the trade clock has second resolution, so the fractional part is always .000. Null without a trade clock. Date is inferred from the observation and can be inaccurate after closures. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`.",
            "example": "2026-09-08T06:29:58.000Z"
          }
        },
        "required": [
          "pair"
        ]
      },
      "ApiResponseDiscountFactorResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/DiscountFactorResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "DiscountFactorResponse": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "enum": [
              "JPY",
              "KRW",
              "USD"
            ]
          },
          "tenor": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "1D",
              "SN",
              "1W",
              "2W",
              "3W",
              "1M",
              "2M",
              "3M",
              "4M",
              "5M",
              "6M",
              "7M",
              "8M",
              "9M",
              "10M",
              "11M",
              "1Y",
              "12M"
            ]
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "deprecated": true,
            "description": "Use maturityDate. Format: `YYYY-MM-DD`."
          },
          "curveBaseDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "daycount": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ACT/360",
              "ACT/365"
            ]
          },
          "yearFraction": {
            "type": [
              "number",
              "null"
            ],
            "format": "double"
          },
          "discountFactor": {
            "type": [
              "number",
              "null"
            ],
            "format": "double"
          },
          "zeroRate": {
            "type": [
              "number",
              "null"
            ],
            "format": "double"
          },
          "referenceAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Observation time of the source values in timeZone (default UTC), with milliseconds; null when no observation backs the response. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`."
          },
          "maturityDate": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Resolved maturity calendar date; unaffected by timeZone. Format: `YYYY-MM-DD`."
          }
        },
        "required": [
          "currency"
        ]
      },
      "ApiResponseCurrencyPairsResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/CurrencyPairsResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "CurrencyPairsResponse": {
        "type": "object",
        "properties": {
          "currencies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "JPY",
                "KRW",
                "USD"
              ]
            }
          },
          "pairs": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "USDKRW",
                "USDJPY",
                "JPYKRW"
              ]
            }
          }
        },
        "required": [
          "currencies",
          "pairs"
        ]
      },
      "ApiResponseConfigResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/ConfigResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "ConfigCurrency": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string",
            "enum": [
              "JPY",
              "KRW",
              "USD"
            ]
          },
          "daycount": {
            "type": "string",
            "description": "Actual-day convention used when reporting rates from this curve.",
            "enum": [
              "ACT/360",
              "ACT/365"
            ]
          },
          "calendar": {
            "type": "string",
            "enum": [
              "USD_NEWYORK",
              "KRW_SEOUL",
              "JPY_TOKYO"
            ]
          }
        },
        "required": [
          "calendar",
          "currency",
          "daycount"
        ]
      },
      "ConfigPair": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string",
            "enum": [
              "USDKRW",
              "USDJPY",
              "JPYKRW"
            ]
          },
          "base": {
            "type": "string",
            "enum": [
              "JPY",
              "KRW",
              "USD"
            ]
          },
          "quote": {
            "type": "string",
            "enum": [
              "JPY",
              "KRW",
              "USD"
            ]
          },
          "spotLag": {
            "type": "integer",
            "format": "int32"
          },
          "pointFactor": {
            "type": "number",
            "format": "double",
            "description": "Multiplier from the quote-currency change to swap points: points = (outright - spot) * pointFactor. Convert points back by dividing by this value.",
            "example": 100
          }
        },
        "required": [
          "base",
          "pair",
          "pointFactor",
          "quote",
          "spotLag"
        ]
      },
      "ConfigResponse": {
        "type": "object",
        "properties": {
          "currencies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigCurrency"
            }
          },
          "pairs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigPair"
            }
          }
        },
        "required": [
          "currencies",
          "pairs"
        ]
      },
      "ApiResponseCalendarDayResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/CalendarDayResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "CalendarDayResponse": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          },
          "calendar": {
            "type": "string",
            "enum": [
              "USD_NEWYORK",
              "KRW_SEOUL",
              "JPY_TOKYO"
            ]
          },
          "isBusinessDay": {
            "type": "boolean"
          },
          "certainty": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "resultDate": {
            "type": "string",
            "format": "date",
            "description": "Format: `YYYY-MM-DD`."
          }
        },
        "required": [
          "calendar",
          "certainty",
          "date",
          "isBusinessDay",
          "reason",
          "resultDate"
        ]
      },
      "ApiResponsePublicSpotResponse": {
        "type": "object",
        "description": "\n        SNAP 응답 래퍼. 성공은 `code: 0` · `message: \"\"`이고 payload는 `data`에 담긴다.\n        프런트엔드는 `code`로 분기한다.\n\n        **\"데이터 없음\"은 실패가 아니다.** 요청이 유효한데 스냅샷이 아직 안 들어왔거나 요청한\n        만기가 커브 범위 밖이면 `code: 0`으로 응답하고 `data` 안쪽 필드를 `null`로 둔다.\n    ",
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32",
            "description": "성공은 `0`. 실패는 에러 번호(`4000`·`4220`·`5000`).",
            "example": 0
          },
          "message": {
            "type": "string",
            "description": "성공은 빈 문자열. 실패는 유저에게 노출해도 되는 메시지."
          },
          "meta": {
            "$ref": "#/components/schemas/ResponseMetadata",
            "description": "REST와 MCP가 공유하는 요청·응답 처리 메타데이터."
          },
          "data": {
            "description": "payload. 실패 응답에서는 항상 `null`이다.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/PublicSpotResponse"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "code",
          "message",
          "meta"
        ]
      },
      "PublicSpotResponse": {
        "type": "object",
        "properties": {
          "pair": {
            "type": "string",
            "example": "USDKRW"
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "유효시간 미만의 최근 체결가, 그 외에는 호가 중간값. 둘 다 없으면 null.",
            "example": 1391.5
          },
          "bid": {
            "type": [
              "number",
              "null"
            ],
            "description": "매수 호가. 제공 가능한 호가가 없으면 null.",
            "example": 1391.4
          },
          "ask": {
            "type": [
              "number",
              "null"
            ],
            "description": "매도 호가. 제공 가능한 호가가 없으면 null.",
            "example": 1391.6
          },
          "referenceAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Observation time of the source values in timeZone (default UTC), with milliseconds; null when no observation backs the response. Format: `YYYY-MM-DDTHH:mm:ss.SSSZ` in UTC, or `YYYY-MM-DDTHH:mm:ss.SSS+hh:mm` in the requested timeZone, for example `2026-10-08T15:00:00.000+09:00`."
          }
        },
        "required": [
          "pair"
        ]
      },
      "AvailabilityResponse": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean"
          },
          "as_of": {
            "type": "string",
            "format": "date-time",
            "description": "Format: `YYYY-MM-DDTHH:mm:ssZ` in UTC, for example `2026-08-12T09:20:00Z`. Unlike product timestamps, fractional seconds appear only when non-zero and can have up to nine digits."
          },
          "degraded": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "as_of",
          "available",
          "degraded"
        ]
      },
      "DependencyCheck": {
        "type": "object",
        "properties": {
          "backend": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "missing": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "latency_ms": {
            "type": "number",
            "format": "double"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "backend",
          "status"
        ]
      },
      "ReadyChecks": {
        "type": "object",
        "properties": {
          "database": {
            "$ref": "#/components/schemas/DependencyCheck"
          },
          "calendars": {
            "$ref": "#/components/schemas/DependencyCheck"
          },
          "cache": {
            "$ref": "#/components/schemas/DependencyCheck"
          }
        },
        "required": [
          "cache",
          "calendars",
          "database"
        ]
      },
      "ReadyResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "checks": {
            "$ref": "#/components/schemas/ReadyChecks"
          }
        },
        "required": [
          "checks",
          "status"
        ]
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "status"
        ]
      }
    }
  }
}
