openapi: "3.2.0" info: title: AssetPilot API version: "4.134.0" description: | 個人資產管理平台 AssetPilot 的完整 API Schema。 供 Cloudflare API Shield Schema Validation 使用。 認證方式:JWT 儲存於 httpOnly Cookie(token),除 /api/auth/* 與 /api/config 外均需認證。 記帳 API 可用 X-Ledger-Id header 或 ledgerId query 選擇帳本,未指定則使用個人帳本。 每次請求皆驗證登入者的帳本成員資格;viewer 僅可 GET/HEAD/OPTIONS。 個人偏好、投資、登入憑證及 MCP/API Token/LINE 整合不採用共享帳本選擇。 servers: - url: https://asset.shao.one description: Production # ───────────────────────────────────────── # 標籤定義(對應 Cloudflare API Shield Endpoint Labels) # ───────────────────────────────────────── tags: - name: Ledgers description: 共享帳本與成員授權 - name: Config description: 系統設定與版本資訊(cf-web-page) - name: Auth description: 使用者登入驗證(cf-log-in) - name: Account description: 帳號設定與個人資料(cf-account-update) - name: Categories description: 分類管理(cf-content) - name: ExchangeRates description: 匯率設定與查詢(cf-content) - name: Accounts description: 帳戶管理(cf-content) - name: Transactions description: 交易記錄(cf-content) - name: Budgets description: 預算管理(cf-content) - name: Recurring description: 固定收支(cf-content) - name: Dashboard description: 儀表板與報表(cf-content) - name: TWSE description: 台灣證交所查詢(cf-content) - name: Stocks description: 股票管理(cf-content) - name: StockTransactions description: 股票交易紀錄(cf-content) - name: StockDividends description: 股利紀錄(cf-content) - name: StockRealized description: 實現損益(cf-content) - name: StockSettings description: 股票參數設定(cf-content) - name: StockRecurring description: 股票定期定額(cf-content) - name: McpCredentials description: MCP 個人化存取權杖(PAT)管理(cf-account-update) - name: McpOAuthConnections description: 已連接 AI 工具(OAuth)清單與新增資料權限管理(cf-account-update) # ───────────────────────────────────────── # 安全性定義 # ───────────────────────────────────────── components: securitySchemes: cookieAuth: type: apiKey in: cookie name: token parameters: LedgerId: in: path name: ledgerId required: true schema: type: string LedgerSelection: in: header name: X-Ledger-Id description: 目前帳本,省略時使用個人帳本;不可用此值冒用非成員帳本。 schema: type: string schemas: Ledger: type: object properties: id: { type: string } name: { type: string } isShared: { type: boolean } isPersonal: { type: boolean } ownerUserId: { type: string } role: { type: string, enum: [owner, editor, viewer] } memberCount: { type: integer } createdAt: { type: integer } LedgerMemberRole: type: object required: [userId, role] properties: userId: { type: string } role: { type: string, enum: [editor, viewer] } LedgerMemberTarget: type: object required: [userId] properties: userId: { type: string } # ── 通用 ── IdObject: type: object properties: id: type: string ErrorResponse: type: object properties: error: type: string # ── 分類 ── Category: type: object properties: id: type: string name: type: string type: type: string enum: [income, expense] color: type: string parentId: type: string isHidden: type: boolean sortOrder: type: integer CategoryInput: type: object required: [name, type, color] properties: name: type: string type: type: string enum: [income, expense] color: type: string parentId: type: string sortOrder: type: integer # ── 帳戶 ── Account: type: object properties: id: type: string name: type: string icon: type: string currency: type: string accountType: type: string excludeFromTotal: type: boolean balance: type: number initialBalance: type: number linkedBankId: type: string AccountInput: type: object required: [name] properties: name: type: string initialBalance: type: number icon: type: string currency: type: string accountType: type: string excludeFromTotal: type: boolean linkedBankId: type: string # ── 交易 ── Transaction: type: object properties: id: type: string type: type: string enum: [income, expense, transfer_in, transfer_out] amount: type: number originalAmount: type: number currency: type: string fxRate: type: number date: type: string format: date categoryId: type: string accountId: type: string note: type: string excludeFromStats: type: boolean TransactionInput: type: object required: [type, amount, date, categoryId, accountId] properties: type: type: string enum: [income, expense] amount: type: number originalAmount: type: number currency: type: string fxRate: type: number date: type: string categoryId: type: string accountId: type: string note: type: string excludeFromStats: type: boolean fxFee: type: number description: 海外刷卡手續費(TWD) clientRef: type: string pattern: "^[a-f0-9]{32}$" description: | 離線記帳的 idempotency key(007-pwa-offline-entry)。離線新增的交易由 client 產生此值;伺服器以 (user_id, client_ref) 唯一索引去重,同一 clientRef 重送不會產生重複交易,並回傳與新建相同的結果。線上直接新增時省略; 格式不符時視為未提供。 # ── 預算 ── BudgetInput: type: object required: [amount, yearMonth] properties: categoryId: type: string amount: type: number yearMonth: type: string # ── 目標儲蓄與還款計畫(issue #260)── SavingsGoalInput: type: object required: [name, targetAmount, targetDate] properties: name: type: string maxLength: 60 targetAmount: type: integer minimum: 1 maximum: 1000000000 description: 目標金額(TWD) targetDate: type: string pattern: "^\\d{4}-\\d{2}-\\d{2}$" accountId: type: [string, "null"] description: 綁定帳戶(以帳戶餘額為已存);與 categoryId 擇一 categoryId: type: [string, "null"] description: 綁定分類(以建立日起該分類支出累計為已存);與 accountId 擇一 RepaymentPlanInput: type: object required: [name, principal, periods, startDate] properties: name: type: string maxLength: 60 principal: type: integer minimum: 1 maximum: 1000000000 annualRatePercent: type: number minimum: 0 maximum: 100 description: 年利率(百分比,預設 0) periods: type: integer minimum: 1 maximum: 600 description: 期數(月) startDate: type: string pattern: "^\\d{4}-\\d{2}-\\d{2}$" description: 首次應繳日 accountId: type: [string, "null"] description: 還款來源帳戶標記(不影響攤還計算) # ── 固定收支 ── RecurringInput: type: object required: [type, amount, categoryId, accountId, frequency, startDate] properties: type: type: string enum: [income, expense] amount: type: number currency: type: string fxRate: type: number categoryId: type: string accountId: type: string frequency: type: string enum: [daily, weekly, monthly, yearly] startDate: type: string note: type: string # ── 匯率 ── ExchangeRateItem: type: object required: [currency, rateToTwd] properties: currency: type: string minLength: 3 maxLength: 3 rateToTwd: type: number # ── 股票 ── StockInput: type: object required: [symbol, name] properties: market: type: string enum: [TW, US] default: TW symbol: type: string name: type: string stockType: type: string enum: [stock, etf, warrant] StockTransactionInput: type: object required: [stockId, date, type, shares, price] properties: stockId: type: string date: type: string type: type: string enum: [buy, sell] shares: type: number price: type: number fee: type: number tax: type: number dayTrade: type: boolean description: 現股當沖(僅 2017-04-28 至 2027-12-31 的台股一般股票賣出適用);需有同證券帳戶、同日、同標的且股數足夠的現款買進,證交稅依證券交易稅條例第 2 條之 2 以 0.15% 計算。 accountId: type: string note: type: string StockDividendInput: type: object required: [stockId, date] properties: stockId: type: string date: type: string cashDividend: type: number stockDividendShares: type: number reinvest: type: boolean description: 股利再投資(DRIP):以現金股利再買入同一標的,自動產生買入紀錄並計入 FIFO 成本基礎。 reinvestShares: type: number description: 再投資股數(reinvest = true 時必填且為正數)。 reinvestPrice: type: number description: 再投資每股價格(reinvest = true 時必填且為正數);再投資股數 × 價格不得超過現金股利。 accountId: type: string note: type: string # ── 管理員 ── AdminUserInput: type: object required: [email, displayName] properties: email: type: string format: email displayName: type: string isAdmin: type: boolean BatchIdsInput: type: object required: [ids] properties: ids: type: array items: type: string # ── MCP 新增資料權限(003-mcp-write-no-delete) ── McpCredential: type: object properties: id: type: string name: type: string status: type: string enum: [active, expired, revoked] allowCreate: type: boolean description: 是否允許此憑證透過 MCP 新增交易;預設 false。 allowUpdateNote: type: boolean description: | 是否允許此憑證透過 MCP 更新既有交易的備註;預設 false。 與 allowCreate 彼此獨立,開啟其中一項不代表取得另一項的能力。 本功能上線當下所有既有憑證(含 allowCreate 為 true 者)皆為 false。 createdAt: type: string format: date-time lastUsedAt: # OpenAPI 3.1 起對齊 JSON Schema 2020-12,3.0 的 `nullable: true` 已移除, # 可空型別一律寫成型別陣列。 type: [string, "null"] format: date-time expiresAt: type: [string, "null"] format: date-time required: [id, name, status, allowCreate, allowUpdateNote, createdAt] # 既有 McpCredentialAllowCreateUpdate 更名而來:本 body 現可攜帶兩種權限, # 原名稱已不再貼切(見 research.md 第 11 節)。屬性名稱與端點路徑皆不變。 McpCredentialPermissionUpdate: type: object description: | 兩個屬性皆為選填,但至少必須提供其中一個布林值;兩者皆未提供或型別非布林時回傳 400。 僅提供其中一項時,另一項維持原狀不變。 properties: allowCreate: type: boolean description: 新的「允許新增資料」狀態。 allowUpdateNote: type: boolean description: 新的「允許更新備註」狀態。 McpOAuthConnection: type: object properties: clientId: type: string description: 已授權的 OAuth client id(一個 AI 工具對應一個 client_id)。 clientName: type: string description: 授權當下記錄的 client 顯示名稱快照。 allowCreate: type: boolean description: 是否允許此連線透過 MCP 新增交易;預設 false。 allowUpdateNote: type: boolean description: | 是否允許此連線透過 MCP 更新既有交易的備註;預設 false。 與 allowCreate 彼此獨立。此 AI 工具的授權全數失效後重新完成授權時, 本欄位會與 allowCreate 一同重置為 false,須使用者再次主動開啟。 firstConnectedAt: type: string format: date-time lastUsedAt: type: string format: date-time required: - clientId - clientName - allowCreate - allowUpdateNote - firstConnectedAt - lastUsedAt # 既有 McpOAuthConnectionAllowCreateUpdate 更名而來,理由同上。 McpOAuthConnectionPermissionUpdate: type: object description: | 兩個屬性皆為選填,但至少必須提供其中一個布林值;兩者皆未提供或型別非布林時回傳 400。 僅提供其中一項時,另一項維持原狀不變。 properties: allowCreate: type: boolean allowUpdateNote: type: boolean # ── 006 信用卡總金額還款 ── RepaymentRequest: type: object description: | totalAmount 與 repayments 至少擇一;兩者同時出現時以 totalAmount 為準(FR-019)。 properties: fromAccountId: type: string description: 付款帳戶 id,不可為信用卡帳戶(FR-010) date: type: [string, "null"] description: 還款日期 YYYY-MM-DD;省略或無法解析時以使用者時區的今日為準(FR-014) totalAmount: type: integer minimum: 1 description: 本次還款總金額(付款帳戶幣別),須為大於 0 的整數且不得小於納入卡片張數(FR-008、FR-008a) repayments: type: array description: 舊版行動 App 逐張卡片金額(過渡相容路徑); 系統加總為一筆總金額後套用自動納入與等比例分配重算(FR-019a) items: type: object properties: cardId: type: string amount: type: number required: [fromAccountId] RepaymentAllocation: type: object properties: cardId: type: string cardName: type: string cardCurrency: type: string amount: type: integer minimum: 1 amountInCardCurrency: type: number balanceAfter: type: number description: 還款後該卡餘額;大於 0 代表預繳(FR-004、FR-020) required: - cardId - cardName - cardCurrency - amount - amountInCardCurrency - balanceAfter RepaymentResponse: type: object properties: ok: type: boolean summaryId: type: string date: type: string fromAccountId: type: string currency: type: string totalAmount: type: integer allocations: type: array items: $ref: "#/components/schemas/RepaymentAllocation" required: - ok - summaryId - date - fromAccountId - currency - totalAmount - allocations PayableCard: type: object description: 本次納入還款的信用卡;陣列順序即標準順序,前端不得重排。 properties: id: type: string name: type: string currency: type: string debt: type: integer minimum: 1 debtInCardCurrency: type: number required: [id, name, currency, debt, debtInCardCurrency] RepaymentCardsResponse: type: object properties: fromAccount: type: object properties: id: type: string name: type: string currency: type: string required: [id, name, currency] cards: type: array items: $ref: "#/components/schemas/PayableCard" totalDebt: type: integer minTotalAmount: type: integer snapshotAt: type: string format: date-time required: [fromAccount, cards, totalDebt, minTotalAmount, snapshotAt] SummaryAllocation: type: object properties: cardId: type: string cardName: type: string cardCurrency: type: string amount: type: integer amountInCardCurrency: type: number status: type: string enum: [intact, modified, deleted] required: - cardId - cardName - cardCurrency - amount - amountInCardCurrency - status RepaymentSummaryResponse: type: object properties: id: type: string date: type: string fromAccount: type: object properties: id: type: string name: type: string currency: type: string required: [id, name, currency] totalAmount: type: integer inputMode: type: string enum: [total, legacy_items] createdAt: type: string format: date-time stale: type: boolean allocations: type: array items: $ref: "#/components/schemas/SummaryAllocation" required: - id - date - fromAccount - totalAmount - inputMode - createdAt - stale - allocations # ───────────────────────────────────────── # 預設安全性(全域) # ───────────────────────────────────────── security: - cookieAuth: [] # ───────────────────────────────────────── # 路由定義 # ───────────────────────────────────────── paths: /api/ledgers: get: tags: [Ledgers] summary: 列出目前登入者加入的帳本 responses: "200": description: 個人與共享帳本清單(不包含其他使用者的帳本) content: application/json: schema: type: array items: { $ref: "#/components/schemas/Ledger" } post: tags: [Ledgers] summary: 建立空白共享帳本,不複製個人資料 requestBody: required: true content: application/json: schema: type: object required: [name] properties: name: { type: string, minLength: 1, maxLength: 80 } responses: "201": { description: 已建立帳本,建立者為 owner } "400": { description: 帳本名稱無效 } /api/ledgers/{ledgerId}/members: parameters: - { $ref: "#/components/parameters/LedgerId" } get: tags: [Ledgers] summary: 成員可讀取此帳本成員與角色 responses: "200": { description: 帳本資訊與成員清單 } "404": { description: 找不到帳本或非成員 } post: tags: [Ledgers] summary: owner 以 Email 邀請 editor 或 viewer requestBody: required: true content: application/json: schema: type: object required: [email, role] properties: email: { type: string, format: email } role: { type: string, enum: [editor, viewer] } responses: "201": { description: 已寄送 7 天有效的單次邀請,不回傳原始 token } "403": { description: 非 owner } "409": { description: 已是成員 } "503": { description: 寄信服務未設定或寄送失敗,邀請已撤銷 } patch: tags: [Ledgers] summary: owner 修改其他成員的 editor/viewer 角色 requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/LedgerMemberRole" } responses: "200": { description: 已更新角色 } "403": { description: 非 owner } "409": { description: owner 必須使用移交功能 } delete: tags: [Ledgers] summary: owner 移除非 owner 成員,資料仍歸帳本所有 requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/LedgerMemberTarget" } responses: "200": { description: 已移除,新請求立即失去存取權 } "403": { description: 非 owner } "409": { description: 不可移除 owner } /api/ledgers/{ledgerId}/invitations: parameters: - { $ref: "#/components/parameters/LedgerId" } get: tags: [Ledgers] summary: owner 列出尚未接受且未到期的邀請(不含 token 雜湊) responses: "200": { description: 待處理邀請清單 } "403": { description: 非 owner } delete: tags: [Ledgers] summary: owner 撤銷邀請 requestBody: required: true content: application/json: schema: type: object required: [invitationId] properties: invitationId: { type: string } responses: "200": { description: 已撤銷 } "404": { description: 找不到待處理邀請 } /api/ledgers/invitations/accept: post: tags: [Ledgers] summary: 使用受邀電子郵件帳號登入後接受邀請 requestBody: required: true content: application/json: schema: type: object required: [token] properties: token: { type: string, pattern: "^[a-fA-F0-9]{64}$" } responses: "200": { description: 已加入帳本,回傳 ledgerId 與角色 } "403": { description: 登入信箱不是受邀信箱 } "404": { description: 邀請無效、到期、撤銷或已使用 } "409": { description: 已是帳本成員 } /api/ledgers/{ledgerId}/transfer: parameters: - { $ref: "#/components/parameters/LedgerId" } post: tags: [Ledgers] summary: owner 移交給另一位有效成員,原 owner 變為 editor requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/LedgerMemberTarget" } responses: "200": { description: 已原子移交擁有權,財務列的歸屬不變 } "403": { description: 非 owner } "404": { description: 目標不是有效帳本成員 } /api/ledgers/{ledgerId}/leave: parameters: - { $ref: "#/components/parameters/LedgerId" } post: tags: [Ledgers] summary: editor 或 viewer 離開帳本,資料仍屬帳本 responses: "200": { description: 已離開 } "409": { description: owner 必須先移交擁有權 } /api/ledgers/{ledgerId}/audit: parameters: - { $ref: "#/components/parameters/LedgerId" } get: tags: [Ledgers] summary: owner 查閱帳本操作稽核(含操作者 ID、Email、角色與結果) parameters: - in: query name: limit schema: { type: integer, minimum: 1, maximum: 200, default: 100 } responses: "200": { description: 最新稽核清單 } "403": { description: 非 owner } # ── 公開頁面 ────────────────────────── /api/config: get: operationId: getConfig tags: [Config] summary: 取得前端設定(Google Client ID 等) security: [] responses: "200": description: 設定物件 /api/changelog: get: operationId: getChangelog tags: [Config] summary: 取得版本更新紀錄 security: [] responses: "200": description: Changelog JSON # ── 認證 ────────────────────────────── /api/auth/logout: post: operationId: authLogout tags: [Auth] summary: 登出(清除 Cookie) security: [] responses: "200": description: 登出成功 /api/auth/me: get: operationId: authMe tags: [Auth] summary: 取得目前登入的使用者資訊 responses: "200": description: 使用者資訊 "401": description: 未認證 /api/auth/google/state: get: operationId: authGoogleState tags: [Auth] summary: 取得 Google OAuth state nonce security: [] responses: "200": description: State token /api/auth/google: post: operationId: authGoogle tags: [Auth] summary: Google SSO 登入 / 註冊 security: [] requestBody: required: true content: application/json: schema: type: object required: [credential] properties: credential: type: string state: type: string responses: "200": description: SSO 登入成功 "401": description: Token 驗證失敗 # ── Passkey (WebAuthn) 認證 ───────────── /api/auth/passkey/challenge: get: operationId: getPasskeyChallenge tags: [Auth] summary: 取得 Passkey 登入用 challenge security: [] responses: "200": description: 回傳 challenge 與 key content: application/json: schema: type: object properties: key: type: string challenge: type: string /api/auth/passkey/login: post: operationId: passkeyLogin tags: [Auth] summary: 使用 Passkey 進行無密碼登入 security: [] requestBody: required: true content: application/json: schema: type: object required: [authentication, challengeKey] properties: authentication: type: object description: WebAuthn authentication response challengeKey: type: string responses: "200": description: 登入成功 "400": description: Challenge 過期或缺少資料 "401": description: Passkey 驗證失敗 # ── 帳號設定 ────────────────────────── /api/account/login-logs: get: operationId: getLoginLogs tags: [Account] summary: 取得目前使用者的登入紀錄 responses: "200": description: 登入紀錄清單 /api/account/theme: put: operationId: updateTheme tags: [Account] summary: 更新使用者佈景主題 requestBody: required: true content: application/json: schema: type: object properties: theme: type: string enum: [light, dark, auto] responses: "200": description: 更新成功 /api/account/display-name: put: operationId: updateDisplayName tags: [Account] summary: 更新顯示名稱 requestBody: required: true content: application/json: schema: type: object properties: displayName: type: string responses: "200": description: 更新成功 /api/account/link-google: post: operationId: linkGoogle tags: [Account] summary: 綁定 Google 帳號 requestBody: required: true content: application/json: schema: type: object required: [credential] properties: credential: type: string responses: "200": description: 綁定成功 /api/account/unlink-google: post: operationId: unlinkGoogle tags: [Account] summary: 解除綁定 Google 帳號 responses: "200": description: 解除成功 /api/account/delete: post: operationId: deleteAccount tags: [Account] summary: 永久刪除帳號及所有資料 requestBody: required: true content: application/json: schema: type: object required: [confirmEmail] properties: confirmEmail: type: string format: email responses: "200": description: 帳號已刪除 /api/account/passkey/challenge: get: operationId: getPasskeyRegisterChallenge tags: [Account] summary: 取得 Passkey 註冊用 challenge(需登入) responses: "200": description: 回傳 challenge 與 key /api/account/passkeys: get: operationId: listPasskeys tags: [Account] summary: 列出已註冊的 Passkey 清單 responses: "200": description: Passkey 列表 content: application/json: schema: type: object properties: passkeys: type: array items: type: object properties: id: type: string deviceName: type: string createdAt: type: string /api/account/passkey/register: post: operationId: registerPasskey tags: [Account] summary: 註冊新的 Passkey 憑證 requestBody: required: true content: application/json: schema: type: object required: [registration, challengeKey] properties: registration: type: object description: WebAuthn registration response challengeKey: type: string deviceName: type: string responses: "200": description: 註冊成功 "400": description: Challenge 過期或驗證失敗 /api/account/recovery-codes: get: operationId: getRecoveryCodeStatus tags: [Account] summary: 取得帳號復原碼狀態(不含明文) responses: "200": description: 是否已產生與剩餘可用數量 post: operationId: generateRecoveryCodes tags: [Account] summary: 產生或重新產生一次性復原碼(舊碼立即失效,明文僅回傳一次) requestBody: content: application/json: schema: type: object properties: authentication: type: object description: 近期登入逾時時,以 Passkey(UV)重新驗證 challengeKey: type: string responses: "200": description: 回傳 codes(僅此一次) "403": description: 需要重新驗證(reauthRequired) /api/auth/recovery/verify: post: operationId: verifyRecoveryCode tags: [Auth] summary: 驗證復原碼並建立受限復原工作階段(不消耗復原碼,不可存取財務資料) requestBody: required: true content: application/json: schema: type: object required: [email, code] properties: email: type: string code: type: string responses: "200": description: 設定 httpOnly recoveryToken Cookie(10 分鐘) "401": description: 帳號或復原碼不正確(不洩漏帳號是否存在) "429": description: 嘗試次數過多 /api/auth/recovery/challenge: get: operationId: getRecoveryChallenge tags: [Auth] summary: 取得復原流程註冊新 Passkey 的 challenge responses: "200": description: key、challenge 與 user "401": description: 復原工作階段無效或過期 /api/auth/recovery/complete: post: operationId: completeRecovery tags: [Auth] summary: 註冊新 Passkey 後消耗復原碼並登入(可選擇移除其他 Passkey,並登出所有既有工作階段) requestBody: required: true content: application/json: schema: type: object required: [registration, challengeKey] properties: registration: type: object challengeKey: type: string deviceName: type: string removeOtherPasskeys: type: boolean responses: "200": description: 完成並登入 "409": description: 復原碼已被使用 /api/account/passkey/{id}: delete: operationId: deletePasskey tags: [Account] summary: 刪除已註冊的 Passkey parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 "404": description: 找不到此 Passkey put: operationId: renamePasskey tags: [Account] summary: 重新命名 Passkey parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object required: [deviceName] properties: deviceName: type: string responses: "200": description: 更新成功 "404": description: 找不到此 Passkey # ── 分類 ────────────────────────────── /api/user/settings/smart-assist: get: operationId: getSmartAssistSettings tags: [Account] summary: 取得智慧輔助開關(分類建議/固定收支偵測,#252) responses: "200": description: 目前設定 content: application/json: schema: type: object properties: enabled: type: boolean put: operationId: updateSmartAssistSettings tags: [Account] summary: 更新智慧輔助開關(關閉後兩個建議端點皆回傳空結果) requestBody: required: true content: application/json: schema: type: object required: [enabled] properties: enabled: type: boolean responses: "200": description: 更新成功 /api/categories: get: operationId: getCategories tags: [Categories] summary: 取得分類清單 responses: "200": description: 分類陣列 post: operationId: createCategory tags: [Categories] summary: 新增分類 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CategoryInput" responses: "200": description: 新增成功 /api/categories/{id}: put: operationId: updateCategory tags: [Categories] summary: 更新分類 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: name: type: string color: type: string isHidden: type: boolean sortOrder: type: integer responses: "200": description: 更新成功 "404": description: 分類不存在 delete: operationId: deleteCategory tags: [Categories] summary: 刪除分類 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 # ── 匯率 ────────────────────────────── /api/exchange-rates: get: operationId: getExchangeRates tags: [ExchangeRates] summary: 取得使用者匯率設定 responses: "200": description: 匯率清單與設定 put: operationId: setExchangeRates tags: [ExchangeRates] summary: 手動儲存匯率(僅寫入本使用者) requestBody: required: true content: application/json: schema: type: object required: [rates] properties: rates: type: array items: $ref: "#/components/schemas/ExchangeRateItem" responses: "200": description: 儲存成功 /api/exchange-rates/settings: put: operationId: updateExchangeRateSettings tags: [ExchangeRates] summary: 更新匯率自動更新設定 requestBody: required: true content: application/json: schema: type: object required: [autoUpdate] properties: autoUpdate: type: boolean responses: "200": description: 設定已更新 /api/exchange-rates/refresh: post: operationId: refreshExchangeRates tags: [ExchangeRates] summary: 從外部 API 自動取得最新匯率 requestBody: required: false content: application/json: schema: type: object properties: currencies: type: array items: type: string responses: "200": description: 更新成功 # ── 帳戶管理 ────────────────────────── /api/accounts: get: operationId: getAccounts tags: [Accounts] summary: 取得帳戶清單(含餘額) responses: "200": description: 帳戶陣列 post: operationId: createAccount tags: [Accounts] summary: 新增帳戶 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AccountInput" responses: "200": description: 新增成功 /api/accounts/{id}: put: operationId: updateAccount tags: [Accounts] summary: 更新帳戶 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: name: type: string initialBalance: type: number icon: type: string currency: type: string accountType: type: string excludeFromTotal: type: boolean linkedBankId: type: string responses: "200": description: 更新成功 delete: operationId: deleteAccountEntry tags: [Accounts] summary: 刪除帳戶 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 /api/accounts/credit-card-repayment: post: operationId: creditCardRepayment tags: [Accounts] summary: 信用卡還款(輸入單一總金額,系統依欠款等比例分配到各張卡) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RepaymentRequest" responses: "200": description: 還款成功,回傳本次實際寫入的分配結果 content: application/json: schema: $ref: "#/components/schemas/RepaymentResponse" "400": description: 參數錯誤(InvalidTotalAmount/TotalAmountTooSmall/ NoPayableCards) "500": description: 寫入失敗,本次全部未寫入(已回滾) /api/accounts/{id}/repayment-cards: get: operationId: getRepaymentCards tags: [Accounts] summary: 取得以此帳戶為付款來源時本次納入還款的信用卡欠款快照 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 快照取得成功;cards 為空陣列代表本次無可還款卡片 content: application/json: schema: $ref: "#/components/schemas/RepaymentCardsResponse" "400": description: 付款帳戶為信用卡(NotCreditCardPayer) "404": description: 帳戶不存在或不屬於本人 /api/credit-card-repayment-summaries/{id}: get: operationId: getCreditCardRepaymentSummary tags: [Accounts] summary: 取得某次信用卡還款的分配摘要(含「已與現況不符」判定) parameters: - name: id in: path required: true schema: type: string responses: "200": description: 摘要取得成功 content: application/json: schema: $ref: "#/components/schemas/RepaymentSummaryResponse" "404": description: 摘要不存在或不屬於本人 # ── 交易記錄 ────────────────────────── /api/transactions: get: operationId: getTransactions tags: [Transactions] summary: 取得交易記錄(支援篩選分頁) parameters: - name: dateFrom in: query schema: type: string - name: dateTo in: query schema: type: string - name: type in: query schema: type: string - name: categoryId in: query schema: type: string - name: accountId in: query schema: type: string - name: keyword in: query schema: type: string - name: page in: query schema: type: integer - name: limit in: query schema: type: integer responses: "200": description: | 交易清單(含分頁資訊)。每筆交易含唯讀衍生欄位 repaymentSummaryId (非還款交易為空字串;非空代表該筆屬於某次信用卡還款摘要,可作為開啟摘要的入口)。 post: operationId: createTransaction tags: [Transactions] summary: 新增交易 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TransactionInput" responses: "200": description: 新增成功 /api/transactions/{id}: put: operationId: updateTransaction tags: [Transactions] summary: 更新交易 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: type: type: string enum: [income, expense] amount: type: number originalAmount: type: number currency: type: string fxRate: type: number date: type: string categoryId: type: string accountId: type: string note: type: string excludeFromStats: type: boolean responses: "200": description: 更新成功 delete: operationId: deleteTransaction tags: [Transactions] summary: 刪除單筆交易 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 /api/transactions/{id}/restore-ai-created: post: operationId: restoreAiCreatedTransaction tags: [Transactions] summary: 還原(移除)一筆由 AI 透過 MCP 建立的交易 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 已還原(交易與其連動對象皆已移除) "404": description: 找不到此交易,或不屬於目前使用者 "409": description: 此交易並非 AI/MCP 建立(NotRestorable),未變更任何資料 /api/transactions/{id}/restore-ai-note: get: operationId: previewAiNoteRestore tags: [Transactions] summary: 預覽「備註經 AI 修改」還原後的內容(唯讀) parameters: - name: id in: path required: true schema: type: string responses: "200": description: 預覽內容 "404": description: 找不到此交易,或不屬於目前使用者 post: operationId: restoreAiNote tags: [Transactions] summary: 確認執行備註還原 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object required: [expectedUpdatedAt] properties: expectedUpdatedAt: type: integer responses: "200": description: 已還原 "400": description: 缺少或格式錯誤的 expectedUpdatedAt "404": description: 找不到此交易,或不屬於目前使用者 "409": description: NoteChanged(樂觀鎖衝突)或 NotRestorable(無可還原快照),皆不變更任何資料 /api/transactions/transfer: post: operationId: createTransfer tags: [Transactions] summary: 帳戶間轉帳 requestBody: required: true content: application/json: schema: type: object required: [fromId, toId, amount, date] properties: fromId: type: string toId: type: string amount: type: number date: type: string note: type: string clientRef: type: string pattern: "^[a-f0-9]{32}$" description: | 離線記帳的 idempotency key(007-pwa-offline-entry);同一 clientRef 重送 不產生重複的轉帳配對。線上直接新增時省略。 responses: "200": description: 轉帳成功 /api/transactions/suggestions: get: operationId: getTransactionCategorySuggestions tags: [Transactions] summary: 依摘要與歷史紀錄取得分類建議(Top-N 附信心度;僅提示,需使用者確認才寫入) parameters: - name: type in: query schema: type: string enum: [income, expense] - name: note in: query schema: type: string maxLength: 200 responses: "200": description: 分類建議(enabled=false 代表使用者已關閉智慧輔助) content: application/json: schema: type: object properties: enabled: type: boolean suggestions: type: array items: type: object properties: categoryId: type: string categoryName: type: string parentName: type: string nullable: true confidence: type: number minimum: 0 maximum: 1 matchedCount: type: integer /api/transactions/batch-delete: post: operationId: batchDeleteTransactions tags: [Transactions] summary: 批次刪除交易 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BatchIdsInput" responses: "200": description: 刪除成功 /api/transactions/batch-update: post: operationId: batchUpdateTransactions tags: [Transactions] summary: 批次更新交易(分類 / 帳戶 / 日期) requestBody: required: true content: application/json: schema: type: object required: [ids, fields] properties: ids: type: array items: type: string fields: type: object properties: categoryId: type: string accountId: type: string date: type: string responses: "200": description: 更新成功 /api/transactions/import: post: operationId: importTransactions tags: [Transactions] summary: CSV 匯入交易記錄 requestBody: required: true content: application/json: schema: type: object required: [rows] properties: rows: type: array items: type: object properties: date: type: string type: type: string amount: type: string category: type: string account: type: string note: type: string responses: "200": description: 匯入成功 # ── 預算 ────────────────────────────── /api/budgets: get: operationId: getBudgets tags: [Budgets] summary: 取得預算清單(含月份使用量) parameters: - name: yearMonth in: query schema: type: string responses: "200": description: 預算清單 post: operationId: createBudget tags: [Budgets] summary: 新增 / 更新預算 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BudgetInput" responses: "200": description: 儲存成功 /api/budgets/{id}: delete: operationId: deleteBudget tags: [Budgets] summary: 刪除預算 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 # ── 目標儲蓄與還款計畫(issue #260)────────── /api/goals: get: operationId: getSavingsGoals tags: [Goals] summary: 取得儲蓄目標清單(含進度與預估達成日) responses: "200": description: 儲蓄目標陣列 post: operationId: createSavingsGoal tags: [Goals] summary: 新增儲蓄目標 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SavingsGoalInput" responses: "201": description: 建立成功,回傳含進度的目標 "400": description: 驗證失敗 /api/goals/{id}: get: operationId: getSavingsGoal tags: [Goals] summary: 取得單一儲蓄目標(含進度) parameters: - name: id in: path required: true schema: type: string responses: "200": description: 目標詳細資料 "404": description: 目標不存在或無權限 put: operationId: updateSavingsGoal tags: [Goals] summary: 更新儲蓄目標(不變更起算日) parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SavingsGoalInput" responses: "200": description: 更新成功 "400": description: 驗證失敗 "404": description: 目標不存在或無權限 delete: operationId: deleteSavingsGoal tags: [Goals] summary: 刪除儲蓄目標 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 "404": description: 目標不存在或無權限 /api/repayment-plans: get: operationId: getRepaymentPlans tags: [Goals] summary: 取得還款計畫清單(摘要,不含完整攤還表) responses: "200": description: 還款計畫陣列 post: operationId: createRepaymentPlan tags: [Goals] summary: 新增還款計畫 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RepaymentPlanInput" responses: "201": description: 建立成功 "400": description: 驗證失敗 /api/repayment-plans/{id}: get: operationId: getRepaymentPlan tags: [Goals] summary: 取得單一還款計畫(含完整攤還表) parameters: - name: id in: path required: true schema: type: string responses: "200": description: 還款計畫與攤還表 "404": description: 計畫不存在或無權限 put: operationId: updateRepaymentPlan tags: [Goals] summary: 更新還款計畫並重算攤還表 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RepaymentPlanInput" responses: "200": description: 更新成功 "400": description: 驗證失敗 "404": description: 計畫不存在或無權限 delete: operationId: deleteRepaymentPlan tags: [Goals] summary: 刪除還款計畫 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 "404": description: 計畫不存在或無權限 # ── 固定收支 ────────────────────────── /api/recurring: get: operationId: getRecurring tags: [Recurring] summary: 取得固定收支清單 responses: "200": description: 固定收支陣列 post: operationId: createRecurring tags: [Recurring] summary: 新增固定收支 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RecurringInput" responses: "200": description: 新增成功 /api/recurring/{id}: put: operationId: updateRecurring tags: [Recurring] summary: 更新固定收支 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: type: type: string enum: [income, expense] amount: type: number currency: type: string fxRate: type: number categoryId: type: string accountId: type: string frequency: type: string enum: [daily, weekly, monthly, yearly] startDate: type: string note: type: string responses: "200": description: 更新成功 delete: operationId: deleteRecurring tags: [Recurring] summary: 刪除固定收支 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 /api/recurring/{id}/toggle: patch: operationId: toggleRecurring tags: [Recurring] summary: 啟用 / 停用固定收支 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 狀態已切換 /api/recurring/process: post: operationId: processRecurring tags: [Recurring] summary: 手動執行固定收支(產生到期交易) responses: "200": description: 執行結果 /api/recurring/suggestions: get: operationId: getRecurringSuggestions tags: [Recurring] summary: 掃描交易找出疑似週期性群組(僅提示,需使用者確認才建立固定收支) responses: "200": description: 週期性建議(enabled=false 代表使用者已關閉智慧輔助) content: application/json: schema: type: object properties: enabled: type: boolean suggestions: type: array items: type: object properties: signature: type: string type: type: string enum: [income, expense] amount: type: number description: 最新交易的 TWD 金額(供顯示與群組參考) suggestedAmount: type: number description: 最近一筆交易的原幣金額(供新增表單預填) currency: type: string fxRate: type: string frequency: type: string enum: [daily, weekly, monthly, yearly] confidence: type: number minimum: 0 maximum: 1 occurrences: type: integer categoryId: type: string nullable: true accountId: type: string nullable: true firstDate: type: string lastDate: type: string suggestedStartDate: type: string /api/recurring/suggestions/dismiss: post: operationId: dismissRecurringSuggestion tags: [Recurring] summary: 忽略某組週期性建議(僅影響提示,不改動任何交易或固定收支) requestBody: required: true content: application/json: schema: type: object required: [signature] properties: signature: type: string responses: "200": description: 已記錄忽略 # ── 儀表板 / 報表 ───────────────────── /api/dashboard: get: operationId: getDashboard tags: [Dashboard] summary: 取得儀表板資料 responses: "200": description: 儀表板統計資料 /api/reports: get: operationId: getReports tags: [Dashboard] summary: 取得統計報表 parameters: - name: type in: query schema: type: string enum: [income, expense] - name: from in: query schema: type: string format: date - name: to in: query schema: type: string format: date - name: baseCurrency in: query description: ISO 4217 基準幣別;未提供時沿用既有 TWD 等值行為 schema: type: string pattern: '^[A-Za-z]{3}$' default: TWD responses: "200": description: 報表資料;指定非 TWD 基準時另含匯率來源、ISO 8601 UTC 時間戳與可用幣別 "400": description: 日期區間無效、基準幣別不在 ISO 4217 白名單或匯率不可用 /api/reports/export: get: operationId: exportReportsCsv tags: [Dashboard] summary: 匯出統計報表 CSV parameters: - name: type in: query schema: type: string enum: [income, expense] default: expense - name: from in: query schema: type: string format: date - name: to in: query schema: type: string format: date - name: baseCurrency in: query description: ISO 4217 基準幣別;未提供時沿用既有 TWD 等值行為 schema: type: string pattern: '^[A-Za-z]{3}$' default: TWD responses: "200": description: UTF-8 BOM CSV,含所選基準幣別、匯率來源與時間戳 content: text/csv: schema: type: string "400": description: 日期區間無效、基準幣別不在 ISO 4217 白名單或匯率不可用 # ── TWSE 台股 API ───────────────────── /api/twse/stock/{symbol}: get: operationId: getTwseStock tags: [TWSE] summary: 查詢台股股票即時 / 收盤價與基本資料 parameters: - name: symbol in: path required: true schema: type: string responses: "200": description: 股票資料 "404": description: 股票不存在 /api/twse/search: get: operationId: searchTwseStock tags: [TWSE] summary: 搜尋台股股票代號或名稱 parameters: - name: q in: query required: true schema: type: string responses: "200": description: 搜尋結果清單 # ── 股票清單 ────────────────────────── /api/stocks: get: operationId: getStocks tags: [Stocks] summary: 取得持股清單(含損益) responses: "200": description: 股票陣列 post: operationId: createStock tags: [Stocks] summary: 新增股票 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/StockInput" responses: "200": description: 新增成功 /api/stocks/{id}/history: get: operationId: getStockPriceHistory tags: [Stocks] summary: 取得台股歷史日 K 線與目前使用者的交易點位 parameters: - name: id in: path required: true schema: type: string - name: from in: query description: 起始日期(預設為一年前) schema: type: string format: date - name: to in: query description: 結束日期(預設為今天) schema: type: string format: date responses: "200": description: 歷史股價與個人交易點位 content: application/json: schema: type: object properties: symbol: type: string name: type: string market: type: string enum: [TW] from: type: string format: date to: type: string format: date candles: type: array items: type: object properties: date: type: string format: date open: type: number high: type: number low: type: number close: type: number volume: type: number description: 成交股數(櫃買成交張數已換算為股) transactions: type: array items: type: object properties: id: type: string date: type: string format: date type: type: string enum: [buy, sell] shares: type: number price: type: number "400": description: 日期無效、查詢期間過長或不支援的市場 "404": description: 股票不存在或不屬於目前使用者 "503": description: 歷史股價服務暫時無法回應 /api/stocks/{id}: put: operationId: updateStock tags: [Stocks] summary: 更新股票基本資料 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: symbol: type: string name: type: string stockType: type: string enum: [stock, etf, warrant] responses: "200": description: 更新成功 delete: operationId: deleteStock tags: [Stocks] summary: 刪除股票 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 /api/stocks/batch-price: post: operationId: batchUpdateStockPrice tags: [Stocks] summary: 批次更新股票現價 requestBody: required: true content: application/json: schema: type: object required: [symbols] properties: symbols: type: array items: type: string responses: "200": description: 更新結果 /api/stocks/cleanup: post: operationId: cleanupStocks tags: [Stocks] summary: 清除無持倉且無交易紀錄的股票 responses: "200": description: 清除結果 # ── 股票交易紀錄 ────────────────────── /api/stock-transactions: get: operationId: getStockTransactions tags: [StockTransactions] summary: 取得股票交易紀錄(支援篩選分頁) parameters: - name: stockId in: query schema: type: string - name: dateFrom in: query schema: type: string - name: dateTo in: query schema: type: string - name: page in: query schema: type: integer - name: pageSize in: query schema: type: integer - name: sortBy in: query schema: type: string - name: sortDir in: query schema: type: string enum: [asc, desc] responses: "200": description: 交易紀錄清單 post: operationId: createStockTransaction tags: [StockTransactions] summary: 新增股票買賣交易 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/StockTransactionInput" responses: "200": description: 新增成功 /api/stock-transactions/{id}: put: operationId: updateStockTransaction tags: [StockTransactions] summary: 更新股票交易 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: stockId: type: string date: type: string type: type: string enum: [buy, sell] shares: type: number price: type: number fee: type: number tax: type: number dayTrade: type: boolean description: 現股當沖(僅 2017-04-28 至 2027-12-31 的台股一般股票賣出適用);需有同證券帳戶、同日、同標的且股數足夠的現款買進,證交稅依證券交易稅條例第 2 條之 2 以 0.15% 計算。 accountId: type: string note: type: string responses: "200": description: 更新成功 delete: operationId: deleteStockTransaction tags: [StockTransactions] summary: 刪除股票交易 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 /api/stock-transactions/batch-delete: post: operationId: batchDeleteStockTransactions tags: [StockTransactions] summary: 批次刪除股票交易 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BatchIdsInput" responses: "200": description: 刪除成功 /api/stock-transactions/import: post: operationId: importStockTransactions tags: [StockTransactions] summary: CSV 匯入股票交易紀錄 requestBody: required: true content: application/json: schema: type: object required: [rows] properties: rows: type: array items: type: object properties: date: type: string symbol: type: string name: type: string type: type: string shares: type: string price: type: string fee: type: string tax: type: string account: type: string note: type: string responses: "200": description: 匯入成功 # ── 股利紀錄 ────────────────────────── /api/stock-dividends: get: operationId: getStockDividends tags: [StockDividends] summary: 取得股利紀錄 parameters: - name: stockId in: query schema: type: string - name: page in: query schema: type: integer - name: pageSize in: query schema: type: integer responses: "200": description: 股利清單 post: operationId: createStockDividend tags: [StockDividends] summary: 新增股利紀錄 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/StockDividendInput" responses: "200": description: 新增成功 /api/stock-dividends/{id}: put: operationId: updateStockDividend tags: [StockDividends] summary: 更新股利紀錄 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: stockId: type: string date: type: string cashDividend: type: number stockDividendShares: type: number reinvest: type: boolean description: 股利再投資(DRIP),以現金股利再買入同一標的並計入 FIFO 成本基礎。 reinvestShares: type: number description: 再投資股數(reinvest = true 時需為正數;台股必須為整數)。 reinvestPrice: type: number description: 再投資每股價格(reinvest = true 時需為正數)。 accountId: type: string note: type: string responses: "200": description: 更新成功 delete: operationId: deleteStockDividend tags: [StockDividends] summary: 刪除股利紀錄 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 /api/stock-dividends/batch-delete: post: operationId: batchDeleteStockDividends tags: [StockDividends] summary: 批次刪除股利紀錄 requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/BatchIdsInput" responses: "200": description: 刪除成功 /api/stock-dividends/import: post: operationId: importStockDividends tags: [StockDividends] summary: CSV 匯入股利紀錄 requestBody: required: true content: application/json: schema: type: object required: [rows] properties: rows: type: array items: type: object properties: date: type: string symbol: type: string name: type: string cashDividend: type: string stockDividendShares: type: string note: type: string responses: "200": description: 匯入成功 /api/stock-dividends/sync: post: operationId: syncStockDividends tags: [StockDividends] summary: 從 TWSE 自動同步除權息股利紀錄 responses: "200": description: 同步結果 # ── 實現損益 ────────────────────────── /api/stock-realized: get: operationId: getStockRealized tags: [StockRealized] summary: 取得已實現損益(FIFO 計算) parameters: - name: stockId in: query schema: type: string - name: dateFrom in: query schema: type: string - name: dateTo in: query schema: type: string responses: "200": description: 損益清單與彙總 # ── 股票設定 ────────────────────────── /api/stock-settings: get: operationId: getStockSettings tags: [StockSettings] summary: 取得股票手續費 / 稅率設定 responses: "200": description: 設定物件 put: operationId: updateStockSettings tags: [StockSettings] summary: 更新股票手續費 / 稅率設定 requestBody: required: true content: application/json: schema: type: object properties: feeRate: type: number feeDiscount: type: number feeMinLot: type: integer feeMinOdd: type: integer sellTaxRateStock: type: number sellTaxRateEtf: type: number sellTaxRateWarrant: type: number sellTaxMin: type: integer responses: "200": description: 設定已更新 # ── 股票固定扣款 ────────────────────── /api/stock-recurring: get: operationId: getStockRecurring tags: [StockRecurring] summary: 取得定期定額清單 responses: "200": description: 定期定額清單 post: operationId: createStockRecurring tags: [StockRecurring] summary: 新增定期定額 requestBody: required: true content: application/json: schema: type: object required: [stockId, amount, frequency, startDate] properties: stockId: type: string amount: type: number frequency: type: string enum: [weekly, monthly] startDate: type: string accountId: type: string note: type: string responses: "200": description: 新增成功 /api/stock-recurring/{id}: put: operationId: updateStockRecurring tags: [StockRecurring] summary: 更新定期定額 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: amount: type: number frequency: type: string startDate: type: string accountId: type: string note: type: string responses: "200": description: 更新成功 delete: operationId: deleteStockRecurring tags: [StockRecurring] summary: 刪除定期定額 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 刪除成功 /api/stock-recurring/{id}/toggle: patch: operationId: toggleStockRecurring tags: [StockRecurring] summary: 啟用 / 停用定期定額 parameters: - name: id in: path required: true schema: type: string responses: "200": description: 狀態已切換 /api/stock-recurring/process: post: operationId: processStockRecurring tags: [StockRecurring] summary: 手動執行定期定額 responses: "200": description: 執行結果 # ── MCP 寫入權限(新增資料/更新備註,003-mcp-write-no-delete + # 004-mcp-update-notes-only) ───────────────────────────────────────────── /api/user/mcp-credentials/{id}: patch: operationId: setMcpCredentialPermissions tags: [McpCredentials] summary: 開啟或關閉指定 PAT 憑證的 MCP 寫入權限(新增資料/更新備註) description: | 變更即時生效於後續 MCP 連線,不影響此憑證先前已成功建立的交易或已更新的備註。 兩種權限彼此獨立,可分別開關;未於 body 提供的權限維持原狀。 僅能操作使用者本人名下的憑證,找不到時回傳 404。 parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/McpCredentialPermissionUpdate" responses: "200": description: 已更新 content: application/json: schema: type: object properties: credential: $ref: "#/components/schemas/McpCredential" "400": description: 請求格式錯誤(未提供任何權限欄位,或欄位非布林值) content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: 找不到此憑證 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /api/user/mcp-oauth-connections: get: operationId: listMcpOAuthConnections tags: [McpOAuthConnections] summary: 列出使用者目前已連接的 AI 工具(依 OAuth client 分組) description: | 每一列代表使用者目前仍持有有效授權的 AI 工具(以 client_id 為單位)——判定為「存在至少 一項未撤銷且未過期的 token」。授權被撤銷或全數過期者不會出現在此清單;其權限設定仍保留在 伺服器端,但日後重新授權時會重置為關閉。清單本身獨立於 access/refresh token 的核發與輪替週期。 responses: "200": description: 已連接的 AI 工具清單 content: application/json: schema: type: object properties: connections: type: array items: $ref: "#/components/schemas/McpOAuthConnection" /api/user/mcp-oauth-connections/{clientId}: patch: operationId: setMcpOAuthConnectionPermissions tags: [McpOAuthConnections] summary: 開啟或關閉指定已連接 AI 工具的 MCP 寫入權限(新增資料/更新備註) description: | 變更立即生效,不需使用者重新走一次 OAuth 授權流程。兩種權限彼此獨立,可分別開關; 未於 body 提供的權限維持原狀。僅能操作使用者本人名下的連線,找不到時回傳 404。 parameters: - name: clientId in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/McpOAuthConnectionPermissionUpdate" responses: "200": description: 已更新 content: application/json: schema: type: object properties: connection: $ref: "#/components/schemas/McpOAuthConnection" "400": description: 請求格式錯誤(未提供任何權限欄位,或欄位非布林值) content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: 找不到此連線 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /api/transactions/receipt-ocr: post: operationId: recognizeReceipt tags: [Transactions] summary: 收據影像辨識(草稿預填,不寫入資料庫) description: | 以設定的 OCR 供應商辨識收據影像,回傳金額/日期/店家草稿。 結果僅供前端預填,使用者確認後才由 POST /api/transactions 儲存。 未設定供應商或辨識失敗時回傳 `status: unavailable` / `failed`,前端降級為手動輸入。 讀取既有附件時沿用 photoCrypto 解密與 S3 存取控制,直接上傳的影像不落地儲存。 requestBody: required: true content: application/json: schema: type: object description: | 兩種來源擇一:(a) 同時提供 imageBase64 與 mimeType; (b) 同時提供 transactionId 與 attachmentId 以讀取既有附件(MIME type 從附件資料取得)。 oneOf: - required: [imageBase64, mimeType] - required: [transactionId, attachmentId] properties: imageBase64: type: string description: "收據影像的 base64(不含 data URI 前綴)" mimeType: type: string description: "影像 MIME type(搭配 imageBase64 使用)" transactionId: type: string description: "既有交易 id(改讀已加密存放的附件)" attachmentId: type: string description: "既有附件 id(需與 transactionId 同時提供)" responses: "200": description: 辨識結果(草稿;即使失敗亦回 200 並帶 status) content: application/json: schema: type: object required: [status, provider, draft, warnings] properties: status: type: string enum: [ok, unavailable, failed] provider: type: string enum: [none, http] draft: type: object nullable: true properties: amount: type: number nullable: true currency: type: string nullable: true date: type: string nullable: true description: 嚴格 ISO `YYYY-MM-DD` merchant: type: string nullable: true warnings: type: array items: type: string message: type: string "400": description: 請求內容格式無效或未提供影像 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: 找不到該附件(或非本人所有) content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "413": description: 影像超過 OCR 大小上限 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" # ── 管理員 / 系統 / 資料庫備份(不經過 Cloudflare,不納入 API Shield) ── # /api/admin/* — 管理員設定、使用者管理、登入紀錄、憑證管理 # /api/system/* — 系統更新 # /api/database/* — 資料庫匯出匯入備份 # # 以上端點僅供內部管理使用,詳見 server.js。 # (EOF) 管理員端點不納入此 Schema