asset_openapi.yaml
Rama main · réplica de lectura del repositorio original.
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