{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "HaoRate 匯率 API",
    "version": "2.1.0",
    "description": "臺灣銀行牌告匯率靜態 JSON API，由 GitHub Actions 排程更新（約每 5 分鐘檢查更新）。\n\n**匯率類型說明：**\n- `cash_buy`（現金買入）：銀行以此價收購外幣現鈔（你拿外幣換台幣）\n- `cash_sell`（現金賣出）：銀行以此價賣出外幣現鈔（你拿台幣換外幣現金）\n- `spot_buy`（即期買入）：電匯/帳戶轉入匯率（你匯款回台灣）\n- `spot_sell`（即期賣出）：電匯/帳戶轉出匯率（你從台灣匯款出去）\n\n**重要提示：** 賣出（sell）= 銀行賣給你外幣的價格 = 你拿台幣換外幣看此價；\n買入（buy）= 銀行收你外幣的價格 = 你拿外幣換台幣看此價。\n\n**App 匯率模式對應：**\n- `auto`：來源外幣用 `{rateType}.buy`，目標外幣用 `{rateType}.sell`；TWD 視為 1。\n- `sell`：來源與目標皆用 `{rateType}.sell`。\n- `mid`：來源與目標皆用 `({rateType}.buy + {rateType}.sell) / 2`。\n\n匯率僅供參考，實際交易請以金融機構公告為準。",
    "contact": {
      "name": "haotool",
      "email": "haotool.org@gmail.com",
      "url": "https://haotool.org"
    },
    "license": {
      "name": "GPL-3.0",
      "url": "https://www.gnu.org/licenses/gpl-3.0.html"
    },
    "x-source": "臺灣銀行牌告匯率",
    "x-source-url": "https://rate.bot.com.tw/xrt",
    "x-update-frequency": "every 5 minutes",
    "x-base-currency": "TWD",
    "x-rate-modes": [
      "auto",
      "sell",
      "mid"
    ],
    "x-rate-mode-strategies": {
      "auto": {
        "label": "自動方向",
        "description": "與 App 預設匯率模式一致：來源外幣使用買入價、目標外幣使用賣出價，依換算方向貼近實際成本。",
        "fromCurrencyField": "{rateType}.buy",
        "toCurrencyField": "{rateType}.sell",
        "twdToForeign": "amount / details.{TO}.{rateType}.sell",
        "foreignToTwd": "amount * details.{FROM}.{rateType}.buy",
        "foreignToForeign": "amount * details.{FROM}.{rateType}.buy / details.{TO}.{rateType}.sell"
      },
      "sell": {
        "label": "賣出價",
        "description": "全程使用銀行賣出價；即期/現金由使用者選擇的 rateType 決定。",
        "fromCurrencyField": "{rateType}.sell",
        "toCurrencyField": "{rateType}.sell",
        "twdToForeign": "amount / details.{TO}.{rateType}.sell",
        "foreignToTwd": "amount * details.{FROM}.{rateType}.sell",
        "foreignToForeign": "amount * details.{FROM}.{rateType}.sell / details.{TO}.{rateType}.sell"
      },
      "mid": {
        "label": "參考價",
        "description": "使用買入價與賣出價的中間價；適合市場參考，不代表實際可成交價格。",
        "fromCurrencyField": "({rateType}.buy + {rateType}.sell) / 2",
        "toCurrencyField": "({rateType}.buy + {rateType}.sell) / 2",
        "twdToForeign": "amount / mid(details.{TO}.{rateType})",
        "foreignToTwd": "amount * mid(details.{FROM}.{rateType})",
        "foreignToForeign": "amount * mid(details.{FROM}.{rateType}) / mid(details.{TO}.{rateType})"
      }
    },
    "x-rate-providers": {
      "providerSelection": {
        "bankProviderChoiceEnabled": false,
        "activationRule": "Enable bank provider choice when bank provider count is greater than 1",
        "currentBankProviderId": "bot",
        "currentExchangeShopProviderId": "moneybox",
        "supportedSelectionModes": [
          "manual",
          "best"
        ],
        "currentSelectionMode": "manual"
      },
      "providers": [
        {
          "providerId": "bot",
          "sourceKind": "bank",
          "name": "台灣銀行",
          "shortName": "台銀",
          "supportedCurrencies": [
            "TWD",
            "JPY",
            "KRW",
            "CNY",
            "VND",
            "THB",
            "HKD",
            "PHP",
            "MYR",
            "SGD",
            "USD",
            "EUR",
            "GBP",
            "CHF",
            "AUD",
            "CAD",
            "NZD",
            "IDR"
          ],
          "supportedRateTypes": [
            "spot",
            "cash"
          ],
          "currentEndpoint": "/public/rates/latest.json",
          "historyEndpoint": "/public/rates/history/{date}.json"
        },
        {
          "providerId": "moneybox",
          "sourceKind": "exchange-shop",
          "name": "明洞換匯所",
          "shortName": "MoneyBox",
          "supportedCurrencies": [
            "KRW"
          ],
          "supportedRateTypes": [
            "cash"
          ],
          "currentEndpoint": "/public/rates/providers/moneybox/latest.json",
          "historyEndpoint": "/public/rates/providers/moneybox/history/{date}.json"
        }
      ]
    },
    "x-supported-currencies": [
      "TWD",
      "JPY",
      "KRW",
      "CNY",
      "VND",
      "THB",
      "HKD",
      "PHP",
      "MYR",
      "SGD",
      "USD",
      "EUR",
      "GBP",
      "CHF",
      "AUD",
      "CAD",
      "NZD",
      "IDR"
    ],
    "x-webapp": "https://app.haotool.org/ratewise/",
    "x-documentation": "https://app.haotool.org/ratewise/open-data/",
    "x-app-version": "2.27.1",
    "x-schema-version": "2.0",
    "x-semantics-doc": "https://app.haotool.org/ratewise/open-data/"
  },
  "x-changelog": {
    "2.1.0": {
      "date": "2026-06-27",
      "summary": "換錢所 provider JSON 新增 schemaVersion、quoteUnit、semanticFieldMapping 與 ExchangeShopRateV2 語意欄位。",
      "app-version": "2.27.1"
    },
    "2.0.0": {
      "date": "2026-06-27",
      "summary": "新增 customer-centric v2 語意欄位（customerBuyForeignRate 等）與 schemaVersion；legacy buy/sell 保留。",
      "app-version": "2.27.1"
    },
    "1.2.0": {
      "date": "2026-05-09",
      "summary": "新增 MoneyBox 換錢所 current/history 端點與 provider metadata 合約。",
      "app-version": "2.27.1"
    },
    "1.1.0": {
      "date": "2026-05-08",
      "summary": "新增 App 匯率模式欄位對照與幣對 rateModes 規格，並對齊資料端時間欄位格式。",
      "app-version": "2.27.1"
    },
    "1.0.0": {
      "date": "2026-03-21",
      "summary": "初始 API 版本。包含最新匯率、歷史匯率、幣對資訊三個端點。",
      "app-version": "2.27.1"
    }
  },
  "servers": [
    {
      "url": "https://cdn.jsdelivr.net/gh/haotool/app@data",
      "description": "CDN（jsDelivr）— 建議端點，全球 PoP 節點加速，支援 ETag 條件式請求（If-None-Match）省流量。GitHub Actions 每次推送後自動呼叫 jsDelivr Purge API，CDN 快取立即失效，實際新鮮度約 5 分鐘。"
    },
    {
      "url": "https://raw.githubusercontent.com/haotool/app/data",
      "description": "GitHub Raw — 無快取端點，每次請求直接取得 data 分支最新版本（由 GitHub Actions 排程更新，約每 5 分鐘檢查更新）。適合需要確保即時性的場景，但注意 GitHub 對未認證 IP 有速率限制（60 req/hr/IP）。"
    }
  ],
  "paths": {
    "/public/rates/providers/{providerId}/latest.json": {
      "get": {
        "summary": "取得指定 provider 最新換錢所匯率",
        "description": "取得指定 provider 的最新換錢所匯率資料。 目前 App 只在 KRW 相關換算啟用 MoneyBox，且換錢所來源固定視為現金匯率。 CDN 端點支援 ETag 條件式請求（If-None-Match）。",
        "operationId": "getExchangeShopProviderRates",
        "tags": [
          "匯率資料",
          "匯率來源"
        ],
        "parameters": [
          {
            "name": "providerId",
            "in": "path",
            "required": true,
            "description": "匯率來源 providerId。目前換錢所 provider 為 moneybox。",
            "schema": {
              "type": "string",
              "enum": [
                "moneybox"
              ],
              "example": "moneybox"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "ETag 條件式請求標頭；帶入前次回應的 ETag 值，資料未變時可節省傳輸。",
            "schema": {
              "type": "string",
              "example": "\"a1b2c3d4e5f6\""
            }
          }
        ],
        "responses": {
          "200": {
            "description": "成功取得指定 provider 最新換錢所匯率",
            "headers": {
              "ETag": {
                "description": "資源版本標識符，用於後續 If-None-Match 條件式請求（可節省約 5 KB／次）",
                "schema": {
                  "type": "string",
                  "example": "\"a1b2c3d4e5f6\""
                }
              },
              "Cache-Control": {
                "description": "CDN 快取控制；資料更新後 jsDelivr 自動 Purge，實際新鮮度約 5 分鐘",
                "schema": {
                  "type": "string",
                  "example": "max-age=300, public"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExchangeShopRatesResponse"
                }
              }
            }
          },
          "304": {
            "description": "資料未變更（ETag 條件式請求命中，零 body，僅 CDN 端點支援）。請求時帶入 If-None-Match: <ETag 值> 即可觸發。",
            "headers": {
              "ETag": {
                "description": "與前次回應相同的 ETag 值",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "請求超過速率限制（僅 GitHub Raw 端點；未認證 IP 限 60 req/hr）。建議改用 CDN 端點（jsDelivr）以避免觸發此限制。",
            "headers": {
              "Retry-After": {
                "description": "建議等待秒數後重試",
                "schema": {
                  "type": "integer",
                  "example": 3600
                }
              }
            }
          }
        }
      }
    },
    "/public/rates/providers/{providerId}/history/{date}.json": {
      "get": {
        "summary": "取得指定 provider 指定日期換錢所歷史匯率",
        "description": "取得指定 provider 與指定日期的換錢所歷史匯率快照。 資料路徑與台銀 history 分離，避免銀行與換錢所歷史資料語意混用。",
        "operationId": "getExchangeShopProviderHistoryRates",
        "tags": [
          "匯率資料",
          "匯率來源"
        ],
        "parameters": [
          {
            "name": "providerId",
            "in": "path",
            "required": true,
            "description": "匯率來源 providerId。目前換錢所 provider 為 moneybox。",
            "schema": {
              "type": "string",
              "enum": [
                "moneybox"
              ],
              "example": "moneybox"
            }
          },
          {
            "name": "date",
            "in": "path",
            "required": true,
            "description": "查詢日期，格式 YYYY-MM-DD。",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "example": "2026-05-09"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "ETag 條件式請求標頭；帶入前次回應的 ETag 值，資料未變時可節省傳輸。",
            "schema": {
              "type": "string",
              "example": "\"a1b2c3d4e5f6\""
            }
          }
        ],
        "responses": {
          "200": {
            "description": "成功取得指定 provider 歷史換錢所匯率",
            "headers": {
              "ETag": {
                "description": "資源版本標識符，用於後續 If-None-Match 條件式請求（可節省約 5 KB／次）",
                "schema": {
                  "type": "string",
                  "example": "\"a1b2c3d4e5f6\""
                }
              },
              "Cache-Control": {
                "description": "CDN 快取控制；資料更新後 jsDelivr 自動 Purge，實際新鮮度約 5 分鐘",
                "schema": {
                  "type": "string",
                  "example": "max-age=300, public"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExchangeShopRatesResponse"
                }
              }
            }
          },
          "304": {
            "description": "資料未變更（ETag 條件式請求命中，零 body，僅 CDN 端點支援）。請求時帶入 If-None-Match: <ETag 值> 即可觸發。",
            "headers": {
              "ETag": {
                "description": "與前次回應相同的 ETag 值",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "指定日期無歷史資料。常見原因：日期為國定假日（台銀未開市）、早於資料收集起始日，或格式不符 YYYY-MM-DD 正則。"
          },
          "429": {
            "description": "請求超過速率限制（僅 GitHub Raw 端點；未認證 IP 限 60 req/hr）。建議改用 CDN 端點（jsDelivr）以避免觸發此限制。",
            "headers": {
              "Retry-After": {
                "description": "建議等待秒數後重試",
                "schema": {
                  "type": "integer",
                  "example": 3600
                }
              }
            }
          }
        }
      }
    },
    "/ratewise/api/pairs/{pair}.json": {
      "get": {
        "summary": "取得指定幣對資訊",
        "description": "取得指定幣對（如 usd-twd）的靜態資訊，包含幣對代碼、即時匯率 CDN 端點、匯率欄位路徑與對應落地頁 URL。適合搜尋系統與 AI agent 查詢特定幣對。",
        "operationId": "getPairInfo",
        "tags": [
          "幣對資訊"
        ],
        "parameters": [
          {
            "name": "pair",
            "in": "path",
            "required": true,
            "description": "幣對代碼，格式 {from}-twd（例如 usd-twd、jpy-twd）",
            "schema": {
              "type": "string",
              "enum": [
                "usd-twd",
                "jpy-twd",
                "eur-twd",
                "gbp-twd",
                "cny-twd",
                "krw-twd",
                "hkd-twd",
                "aud-twd",
                "cad-twd",
                "sgd-twd",
                "thb-twd",
                "nzd-twd",
                "chf-twd",
                "vnd-twd",
                "php-twd",
                "idr-twd",
                "myr-twd"
              ],
              "example": "usd-twd"
            }
          }
        ],
        "servers": [
          {
            "url": "https://app.haotool.org",
            "description": "HaoRate 應用程式伺服器"
          }
        ],
        "responses": {
          "200": {
            "description": "成功取得幣對資訊",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PairInfo"
                }
              }
            }
          }
        }
      }
    },
    "/public/rates/latest.json": {
      "get": {
        "summary": "取得最新匯率",
        "description": "取得臺灣銀行最新牌告匯率資料。 資料由 GitHub Actions 排程更新（約每 5 分鐘檢查更新）。 包含各幣別的現金買入、現金賣出、即期買入、即期賣出四種報價。 CDN 端點支援 ETag 條件式請求（If-None-Match），資料未變時回傳 304（零 body）。",
        "operationId": "getLatestRates",
        "tags": [
          "匯率資料"
        ],
        "parameters": [
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "ETag 條件式請求標頭；帶入前次回應的 ETag 值，資料未變時可節省傳輸。",
            "schema": {
              "type": "string",
              "example": "\"a1b2c3d4e5f6\""
            }
          }
        ],
        "responses": {
          "200": {
            "description": "成功取得最新匯率資料",
            "headers": {
              "ETag": {
                "description": "資源版本標識符，用於後續 If-None-Match 條件式請求（可節省約 5 KB／次）",
                "schema": {
                  "type": "string",
                  "example": "\"a1b2c3d4e5f6\""
                }
              },
              "Cache-Control": {
                "description": "CDN 快取控制；資料更新後 jsDelivr 自動 Purge，實際新鮮度約 5 分鐘",
                "schema": {
                  "type": "string",
                  "example": "max-age=300, public"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RatesResponse"
                }
              }
            }
          },
          "304": {
            "description": "資料未變更（ETag 條件式請求命中，零 body，僅 CDN 端點支援）。請求時帶入 If-None-Match: <ETag 值> 即可觸發。",
            "headers": {
              "ETag": {
                "description": "與前次回應相同的 ETag 值",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "description": "請求超過速率限制（僅 GitHub Raw 端點；未認證 IP 限 60 req/hr）。建議改用 CDN 端點（jsDelivr）以避免觸發此限制。",
            "headers": {
              "Retry-After": {
                "description": "建議等待秒數後重試",
                "schema": {
                  "type": "integer",
                  "example": 3600
                }
              }
            }
          }
        }
      }
    },
    "/public/rates/history/{date}.json": {
      "get": {
        "summary": "取得指定日期歷史匯率",
        "description": "取得指定日期的臺灣銀行牌告匯率歷史資料。可查詢日期自資料收集起始日起（約 2025-02 至今）；國定假日（台銀未開市）或收集前日期均無資料，回傳 404。CDN 端點支援 ETag 條件式請求（If-None-Match），資料未變時回傳 304（零 body）。",
        "operationId": "getHistoryRates",
        "tags": [
          "匯率資料"
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "description": "查詢日期，格式 YYYY-MM-DD。可查詢範圍自 2025-02 起；假日或格式不符回傳 404。",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "example": "2025-02-20"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "ETag 條件式請求標頭；帶入前次回應的 ETag 值，資料未變時可節省傳輸。",
            "schema": {
              "type": "string",
              "example": "\"a1b2c3d4e5f6\""
            }
          }
        ],
        "responses": {
          "200": {
            "description": "成功取得歷史匯率資料",
            "headers": {
              "ETag": {
                "description": "資源版本標識符，用於後續 If-None-Match 條件式請求（可節省約 5 KB／次）",
                "schema": {
                  "type": "string",
                  "example": "\"a1b2c3d4e5f6\""
                }
              },
              "Cache-Control": {
                "description": "CDN 快取控制；資料更新後 jsDelivr 自動 Purge，實際新鮮度約 5 分鐘",
                "schema": {
                  "type": "string",
                  "example": "max-age=300, public"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RatesResponse"
                }
              }
            }
          },
          "304": {
            "description": "資料未變更（ETag 條件式請求命中，零 body，僅 CDN 端點支援）。請求時帶入 If-None-Match: <ETag 值> 即可觸發。",
            "headers": {
              "ETag": {
                "description": "與前次回應相同的 ETag 值",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "指定日期無歷史資料。常見原因：日期為國定假日（台銀未開市）、早於資料收集起始日，或格式不符 YYYY-MM-DD 正則。"
          },
          "429": {
            "description": "請求超過速率限制（僅 GitHub Raw 端點；未認證 IP 限 60 req/hr）。建議改用 CDN 端點（jsDelivr）以避免觸發此限制。",
            "headers": {
              "Retry-After": {
                "description": "建議等待秒數後重試",
                "schema": {
                  "type": "integer",
                  "example": 3600
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CurrencyRateDetail": {
        "type": "object",
        "description": "單一幣別的完整四種報價（即期與現金的買入/賣出）",
        "properties": {
          "spot": {
            "type": "object",
            "description": "即期匯率（電匯/帳戶轉帳適用）",
            "properties": {
              "buy": {
                "type": "number",
                "description": "@deprecated 2026-12-31 — 即期買入匯率（銀行視角）；請改用 customerSellForeignRate",
                "example": 32.015
              },
              "sell": {
                "type": "number",
                "description": "@deprecated 2026-12-31 — 即期賣出匯率（銀行視角）；請改用 customerBuyForeignRate",
                "example": 32.085
              },
              "customerBuyForeignRate": {
                "type": "number",
                "description": "客戶用 TWD 買外幣（即期）；對應 legacy sell",
                "example": 32.085
              },
              "customerSellForeignRate": {
                "type": "number",
                "description": "客戶用外幣賣回 TWD（即期）；對應 legacy buy",
                "example": 32.015
              },
              "midMarketRate": {
                "type": "number",
                "example": 32.05
              },
              "bankSellTwdPerUnit": {
                "type": "number",
                "example": 0.03117
              },
              "rateType": {
                "type": "string",
                "enum": [
                  "spot"
                ],
                "example": "spot"
              },
              "currency": {
                "type": "string",
                "example": "USD"
              }
            },
            "required": [
              "buy",
              "sell"
            ]
          },
          "cash": {
            "type": "object",
            "description": "現金匯率（臨櫃換鈔適用）",
            "properties": {
              "buy": {
                "type": "number",
                "description": "@deprecated 2026-12-31 — 現金買入匯率（銀行視角）；請改用 customerSellForeignRate",
                "example": 31.73
              },
              "sell": {
                "type": "number",
                "description": "@deprecated 2026-12-31 — 現金賣出匯率（銀行視角）；請改用 customerBuyForeignRate",
                "example": 32.385
              },
              "customerBuyForeignRate": {
                "type": "number",
                "description": "客戶用 TWD 買外幣（現金）；對應 legacy sell",
                "example": 32.385
              },
              "customerSellForeignRate": {
                "type": "number",
                "description": "客戶用外幣賣回 TWD（現金）；對應 legacy buy",
                "example": 31.73
              },
              "midMarketRate": {
                "type": "number",
                "example": 32.0575
              },
              "bankSellTwdPerUnit": {
                "type": "number",
                "example": 0.03087
              },
              "rateType": {
                "type": "string",
                "enum": [
                  "cash"
                ],
                "example": "cash"
              },
              "currency": {
                "type": "string",
                "example": "USD"
              }
            },
            "required": [
              "buy",
              "sell"
            ]
          }
        },
        "required": [
          "spot",
          "cash"
        ]
      },
      "CurrencyRateV2": {
        "type": "object",
        "description": "v2 語意化匯率物件（customer-centric additive 欄位；spec §二十一）。legacy buy/sell 仍保留至 2026-12-31 文件 sunset。",
        "properties": {
          "name": {
            "type": "string",
            "example": "美金"
          },
          "spot": {
            "type": "object",
            "description": "v2 語意化匯率區塊（additive；legacy buy/sell 仍保留）",
            "properties": {
              "buy": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "@deprecated 2026-12-31 — 銀行視角買入價；請改用 customerSellForeignRate（客戶賣外幣回 TWD）",
                "example": 32.015
              },
              "sell": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "@deprecated 2026-12-31 — 銀行視角賣出價；請改用 customerBuyForeignRate（客戶用 TWD 買外幣）",
                "example": 32.085
              },
              "customerBuyForeignRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "客戶用 TWD 買外幣（銀行賣出）；對應 legacy sell",
                "example": 32.085
              },
              "customerSellForeignRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "客戶用外幣賣回 TWD（銀行買入）；對應 legacy buy",
                "example": 32.015
              },
              "midMarketRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "參考中間價：(buy + sell) / 2",
                "example": 32.05
              },
              "bankSellTwdPerUnit": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "每 1 單位外幣的 TWD 賣價（1 / sell）",
                "example": 0.03117
              },
              "rateType": {
                "type": "string",
                "enum": [
                  "cash",
                  "spot"
                ],
                "example": "cash"
              },
              "currency": {
                "type": "string",
                "description": "ISO 4217 幣別代碼",
                "example": "USD"
              }
            },
            "required": [
              "buy",
              "sell",
              "customerBuyForeignRate",
              "customerSellForeignRate",
              "midMarketRate",
              "rateType",
              "currency"
            ]
          },
          "cash": {
            "type": "object",
            "description": "v2 語意化匯率區塊（additive；legacy buy/sell 仍保留）",
            "properties": {
              "buy": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "@deprecated 2026-12-31 — 銀行視角買入價；請改用 customerSellForeignRate（客戶賣外幣回 TWD）",
                "example": 32.015
              },
              "sell": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "@deprecated 2026-12-31 — 銀行視角賣出價；請改用 customerBuyForeignRate（客戶用 TWD 買外幣）",
                "example": 32.085
              },
              "customerBuyForeignRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "客戶用 TWD 買外幣（銀行賣出）；對應 legacy sell",
                "example": 32.085
              },
              "customerSellForeignRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "客戶用外幣賣回 TWD（銀行買入）；對應 legacy buy",
                "example": 32.015
              },
              "midMarketRate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "參考中間價：(buy + sell) / 2",
                "example": 32.05
              },
              "bankSellTwdPerUnit": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "每 1 單位外幣的 TWD 賣價（1 / sell）",
                "example": 0.03117
              },
              "rateType": {
                "type": "string",
                "enum": [
                  "cash",
                  "spot"
                ],
                "example": "cash"
              },
              "currency": {
                "type": "string",
                "description": "ISO 4217 幣別代碼",
                "example": "USD"
              }
            },
            "required": [
              "buy",
              "sell",
              "customerBuyForeignRate",
              "customerSellForeignRate",
              "midMarketRate",
              "rateType",
              "currency"
            ]
          }
        },
        "required": [
          "spot",
          "cash"
        ]
      },
      "SemanticRateTypeBlock": {
        "type": "object",
        "description": "v2 語意化匯率區塊（additive；legacy buy/sell 仍保留）",
        "properties": {
          "buy": {
            "type": [
              "number",
              "null"
            ],
            "description": "@deprecated 2026-12-31 — 銀行視角買入價；請改用 customerSellForeignRate（客戶賣外幣回 TWD）",
            "example": 32.015
          },
          "sell": {
            "type": [
              "number",
              "null"
            ],
            "description": "@deprecated 2026-12-31 — 銀行視角賣出價；請改用 customerBuyForeignRate（客戶用 TWD 買外幣）",
            "example": 32.085
          },
          "customerBuyForeignRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "客戶用 TWD 買外幣（銀行賣出）；對應 legacy sell",
            "example": 32.085
          },
          "customerSellForeignRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "客戶用外幣賣回 TWD（銀行買入）；對應 legacy buy",
            "example": 32.015
          },
          "midMarketRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "參考中間價：(buy + sell) / 2",
            "example": 32.05
          },
          "bankSellTwdPerUnit": {
            "type": [
              "number",
              "null"
            ],
            "description": "每 1 單位外幣的 TWD 賣價（1 / sell）",
            "example": 0.03117
          },
          "rateType": {
            "type": "string",
            "enum": [
              "cash",
              "spot"
            ],
            "example": "cash"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 幣別代碼",
            "example": "USD"
          }
        },
        "required": [
          "buy",
          "sell",
          "customerBuyForeignRate",
          "customerSellForeignRate",
          "midMarketRate",
          "rateType",
          "currency"
        ]
      },
      "ExchangeShopRateV2": {
        "type": "object",
        "description": "換錢所單一幣別報價（legacy + v2 additive）",
        "properties": {
          "currency": {
            "type": "string",
            "example": "TWD"
          },
          "base": {
            "type": [
              "number",
              "null"
            ],
            "example": 44.9
          },
          "buy": {
            "type": [
              "number",
              "null"
            ],
            "description": "@deprecated 2026-12-31 — 換錢所買入價；請改用 customerSellForeignRate",
            "example": 45.1
          },
          "sell": {
            "type": [
              "number",
              "null"
            ],
            "description": "@deprecated 2026-12-31 — 換錢所賣出價；請改用 customerBuyForeignRate",
            "example": 44.9
          },
          "spbuy": {
            "type": [
              "number",
              "null"
            ],
            "example": 45.2
          },
          "spsell": {
            "type": [
              "number",
              "null"
            ],
            "example": 44.8
          },
          "customerBuyForeignRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "客戶用 TWD 買外幣（換錢所賣出）；MoneyBox TWD 對應 rates.TWD.sell（KRW/TWD）",
            "example": 46.5
          },
          "customerSellForeignRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "客戶用外幣賣回 TWD（換錢所買入）；對應 legacy buy",
            "example": 46
          },
          "midMarketRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "參考中間價：(buy + sell) / 2",
            "example": 46.25
          },
          "quoteUnit": {
            "type": "string",
            "enum": [
              "TWD_PER_FOREIGN",
              "KRW_PER_TWD"
            ],
            "description": "報價單位；MoneyBox TWD 為 KRW_PER_TWD（與台銀 TWD_PER_FOREIGN 相反）",
            "example": "KRW_PER_TWD"
          },
          "quotePerBaseUnit": {
            "type": [
              "number",
              "null"
            ],
            "description": "每 1 TWD 可換 KRW（KRW_PER_TWD 時等同 sell；台銀語意為 1/sell）",
            "example": 46.5
          }
        },
        "required": [
          "currency",
          "base",
          "buy",
          "sell",
          "spbuy",
          "spsell"
        ]
      },
      "ExchangeShopRatesResponse": {
        "type": "object",
        "description": "換錢所匯率資料回應。MoneyBox 以 KRW 為基準；TWD rate 表示 1 TWD 可換多少 KRW（sell 直接乘算）。",
        "properties": {
          "schemaVersion": {
            "type": "string",
            "description": "API 語意版本（有別於 App semver）",
            "example": "2.0"
          },
          "asOf": {
            "type": "string",
            "format": "date-time",
            "description": "資料生效時間（通常對應 timestamp）",
            "example": "2026-06-27T04:00:00.000Z"
          },
          "semanticFieldMapping": {
            "type": "object",
            "description": "v2 語意欄位對照（見 open-data / latest.json metadata）"
          },
          "quoteUnit": {
            "type": "string",
            "enum": [
              "KRW_PER_TWD"
            ],
            "description": "MoneyBox TWD 報價單位（與台銀 details 語意不同）",
            "example": "KRW_PER_TWD"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "資料抓取時間（ISO 8601 字串，UTC）",
            "example": "2026-05-07T17:25:48.209Z"
          },
          "updateTime": {
            "type": "string",
            "description": "MoneyBox 資料更新時間（首爾時間 UTC+9）",
            "example": "2026/05/08 02:25:48"
          },
          "source": {
            "type": "string",
            "example": "MoneyBox (明洞換匯所聯盟)"
          },
          "sourceUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://moneybox-exchange.com/zh-CHT/exchange"
          },
          "base": {
            "type": "string",
            "example": "KRW"
          },
          "rates": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ExchangeShopRateV2"
            }
          }
        },
        "required": [
          "timestamp",
          "updateTime",
          "source",
          "sourceUrl",
          "base",
          "rates"
        ]
      },
      "RatesResponse": {
        "type": "object",
        "description": "匯率資料回應（由 GitHub Actions 排程更新，約每 5 分鐘檢查更新）",
        "properties": {
          "schemaVersion": {
            "type": "string",
            "description": "API 語意版本（有別於 App semver）",
            "example": "2.0"
          },
          "asOf": {
            "type": "string",
            "format": "date-time",
            "description": "牌告生效時間（通常對應 timestamp 或 updateTime）",
            "example": "2026-06-27T04:00:00.000Z"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "資料抓取時間（ISO 8601 字串，UTC）",
            "example": "2026-05-07T17:25:48.209Z"
          },
          "updateTime": {
            "type": "string",
            "description": "台灣銀行牌告顯示時間（台灣時間 UTC+8）",
            "example": "2026/05/08 01:25:48"
          },
          "source": {
            "type": "string",
            "description": "匯率資料來源名稱",
            "example": "臺灣銀行牌告匯率"
          },
          "rates": {
            "type": "object",
            "description": "各幣別簡化參考匯率（相容舊版欄位）。若要對齊 App 的 auto / sell / mid 匯率模式，請使用 details 搭配 x-rate-mode-strategies。",
            "additionalProperties": {
              "type": "number",
              "description": "該幣別對 TWD 的簡化參考匯率"
            },
            "example": {
              "USD": 32.085,
              "JPY": 0.2088,
              "EUR": 35.12
            }
          },
          "details": {
            "type": "object",
            "description": "各幣別完整四種報價資料（以幣別代碼為 key）；v2 欄位見 CurrencyRateV2 / CurrencyRateDetail",
            "additionalProperties": {
              "$ref": "#/components/schemas/CurrencyRateDetail"
            },
            "example": {
              "USD": {
                "spot": {
                  "buy": 32.015,
                  "sell": 32.085
                },
                "cash": {
                  "buy": 31.73,
                  "sell": 32.385
                }
              },
              "JPY": {
                "spot": {
                  "buy": 0.2078,
                  "sell": 0.2088
                },
                "cash": {
                  "buy": 0.2041,
                  "sell": 0.2119
                }
              }
            }
          }
        },
        "required": [
          "timestamp",
          "updateTime",
          "source",
          "rates",
          "details"
        ]
      },
      "PairInfo": {
        "type": "object",
        "description": "幣對靜態資訊（指向即時匯率 CDN 的入口）",
        "properties": {
          "pair": {
            "type": "string",
            "example": "USD/TWD"
          },
          "from": {
            "type": "string",
            "example": "USD"
          },
          "to": {
            "type": "string",
            "example": "TWD"
          },
          "slug": {
            "type": "string",
            "example": "usd-twd"
          },
          "pageUrl": {
            "type": "string",
            "format": "uri",
            "example": "https://app.haotool.org/ratewise/usd-twd/"
          },
          "liveRateUrl": {
            "type": "string",
            "format": "uri",
            "description": "即時匯率 JSON 來源（CDN）",
            "example": "https://cdn.jsdelivr.net/gh/haotool/app@data/public/rates/latest.json"
          },
          "rateFieldPath": {
            "type": "string",
            "description": "在 liveRateUrl 回應中定位此幣別資料的路徑",
            "example": "details.USD"
          },
          "rateModes": {
            "type": "object",
            "description": "App 匯率模式對應的欄位選擇策略；用於依使用者模式取正確 buy/sell/mid 欄位。",
            "example": {
              "auto": {
                "label": "自動方向",
                "description": "與 App 預設匯率模式一致：來源外幣使用買入價、目標外幣使用賣出價，依換算方向貼近實際成本。",
                "fromCurrencyField": "{rateType}.buy",
                "toCurrencyField": "{rateType}.sell",
                "twdToForeign": "amount / details.{TO}.{rateType}.sell",
                "foreignToTwd": "amount * details.{FROM}.{rateType}.buy",
                "foreignToForeign": "amount * details.{FROM}.{rateType}.buy / details.{TO}.{rateType}.sell"
              },
              "sell": {
                "label": "賣出價",
                "description": "全程使用銀行賣出價；即期/現金由使用者選擇的 rateType 決定。",
                "fromCurrencyField": "{rateType}.sell",
                "toCurrencyField": "{rateType}.sell",
                "twdToForeign": "amount / details.{TO}.{rateType}.sell",
                "foreignToTwd": "amount * details.{FROM}.{rateType}.sell",
                "foreignToForeign": "amount * details.{FROM}.{rateType}.sell / details.{TO}.{rateType}.sell"
              },
              "mid": {
                "label": "參考價",
                "description": "使用買入價與賣出價的中間價；適合市場參考，不代表實際可成交價格。",
                "fromCurrencyField": "({rateType}.buy + {rateType}.sell) / 2",
                "toCurrencyField": "({rateType}.buy + {rateType}.sell) / 2",
                "twdToForeign": "amount / mid(details.{TO}.{rateType})",
                "foreignToTwd": "amount * mid(details.{FROM}.{rateType})",
                "foreignToForeign": "amount * mid(details.{FROM}.{rateType}) / mid(details.{TO}.{rateType})"
              }
            }
          },
          "source": {
            "type": "string",
            "example": "臺灣銀行牌告匯率"
          }
        },
        "required": [
          "pair",
          "from",
          "to",
          "slug",
          "pageUrl",
          "liveRateUrl",
          "rateFieldPath",
          "rateModes",
          "source"
        ]
      },
      "RateProvider": {
        "type": "object",
        "description": "匯率來源 provider metadata。分類使用 sourceKind，實際來源使用 providerId。",
        "properties": {
          "providerId": {
            "type": "string",
            "examples": [
              "bot",
              "moneybox"
            ]
          },
          "sourceKind": {
            "type": "string",
            "enum": [
              "bank",
              "exchange-shop"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "臺灣銀行",
              "MoneyBox (明洞換匯所聯盟)"
            ]
          },
          "supportedCurrencies": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "supportedRateTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "cash",
                "spot"
              ]
            }
          },
          "currentEndpoint": {
            "type": "string"
          },
          "historyEndpoint": {
            "type": "string"
          }
        },
        "required": [
          "providerId",
          "sourceKind",
          "name",
          "supportedCurrencies",
          "supportedRateTypes",
          "currentEndpoint",
          "historyEndpoint"
        ]
      }
    }
  },
  "tags": [
    {
      "name": "匯率資料",
      "description": "臺灣銀行牌告匯率相關端點（最新 / 歷史）",
      "x-displayName": "匯率資料"
    },
    {
      "name": "幣對資訊",
      "description": "幣對靜態資訊端點（幣對代碼、CDN 端點、落地頁 URL）",
      "x-displayName": "幣對資訊"
    },
    {
      "name": "匯率來源",
      "description": "provider metadata 與換錢所資料端點",
      "x-displayName": "匯率來源"
    }
  ],
  "x-rate-providers": {
    "providerSelection": {
      "bankProviderChoiceEnabled": false,
      "activationRule": "Enable bank provider choice when bank provider count is greater than 1",
      "currentBankProviderId": "bot",
      "currentExchangeShopProviderId": "moneybox",
      "supportedSelectionModes": [
        "manual",
        "best"
      ],
      "currentSelectionMode": "manual"
    },
    "providers": [
      {
        "providerId": "bot",
        "sourceKind": "bank",
        "name": "台灣銀行",
        "shortName": "台銀",
        "supportedCurrencies": [
          "TWD",
          "JPY",
          "KRW",
          "CNY",
          "VND",
          "THB",
          "HKD",
          "PHP",
          "MYR",
          "SGD",
          "USD",
          "EUR",
          "GBP",
          "CHF",
          "AUD",
          "CAD",
          "NZD",
          "IDR"
        ],
        "supportedRateTypes": [
          "spot",
          "cash"
        ],
        "currentEndpoint": "/public/rates/latest.json",
        "historyEndpoint": "/public/rates/history/{date}.json"
      },
      {
        "providerId": "moneybox",
        "sourceKind": "exchange-shop",
        "name": "明洞換匯所",
        "shortName": "MoneyBox",
        "supportedCurrencies": [
          "KRW"
        ],
        "supportedRateTypes": [
          "cash"
        ],
        "currentEndpoint": "/public/rates/providers/moneybox/latest.json",
        "historyEndpoint": "/public/rates/providers/moneybox/history/{date}.json"
      }
    ]
  },
  "x-pair-endpoints": {
    "description": "各幣對靜態 JSON 端點（供搜尋系統與 AI agent 查詢特定幣對匯率資訊）",
    "template": "https://app.haotool.org/ratewise/api/pairs/{PAIR}.json",
    "example": "https://app.haotool.org/ratewise/api/pairs/usd-twd.json"
  },
  "x-deep-link": {
    "description": "公開可索引的幣對金額頁與首頁互動 deep-link 模板。",
    "preferredLandingPageTemplate": "https://app.haotool.org/ratewise/{pair}/{amount}/",
    "preferredLandingPageDescription": "優先提供公開可索引的 path-based landing page，適合搜尋引擎與 AI agent 引用。",
    "interactiveDeepLinkTemplate": "https://app.haotool.org/ratewise/?amount={AMOUNT}&from={FROM}&to={TO}",
    "interactiveDeepLinkDescription": "僅在需要同時指定三個自由變數時使用，作為首頁換算器的互動狀態 URL。"
  }
}
