記帳系統後端 API 完整文件,包含所有端點的詳細說明、請求/回應範例。
端點: POST /api/auth/register
描述: 註冊新用戶。是否開放、是否需要邀請碼由後端環境變數 REGISTRATION_MODE 決定(見下方「註冊模式」)。
請求標頭: 無需認證
請求體:
{
"email": "user@example.com",
"password": "MyS3cur3P@ssw0rd!XyZ",
"name": "張三",
"invite_code": "xxxxxxxxxxxx"
}invite_code 只在 REGISTRATION_MODE=invite 時需要(一次性,使用後失效);open 模式會忽略它。
邀請制/關閉時的錯誤回應 (403):
{ "error": "邀請碼無效、已使用或已過期" }{ "error": "目前未開放註冊" }回應 (200):
{
"message": "註冊成功",
"user": {
"email": "user@example.com",
"name": "張三"
}
}錯誤回應 (400):
{
"error": "Email 已被使用"
}密碼要求:
- 至少 12 個字元
- 包含大寫字母、小寫字母、數字、特殊符號
- 不能有超過 2 個重複字元
- 不能有超過 3 個連續字元
- 不能包含常見密碼
- 不能包含 Email 或姓名
- 熵值需 >= 3.0
端點: GET /api/auth/registration-config(無需認證)
回應 (200):
{ "mode": "invite" }| mode | 行為 |
|---|---|
open |
開放註冊(預設,REGISTRATION_MODE 未設定時) |
invite |
註冊需帶有效的一次性 invite_code |
closed |
一律拒絕註冊(設了無法辨識的值也視為 closed) |
邀請碼用 python backend/manage_invites.py create|list|revoke 管理,資料庫只存雜湊,明文只在建立時顯示一次。
端點: POST /api/auth/login
請求體:
{
"email": "user@example.com",
"password": "MyS3cur3P@ssw0rd!XyZ"
}回應 (200):
{
"message": "登入成功",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"email": "user@example.com",
"name": "張三"
}
}錯誤回應 (401)(Email 不存在與密碼錯誤使用同一則訊息,不洩漏帳號是否存在):
{
"error": "Email 或密碼錯誤"
}登入失敗鎖定 (429): 同一個來源 IP 對同一個 Email 連續失敗 5 次,鎖定 15 分鐘:
{
"error": "登入失敗次數過多,請 15 分鐘後再試"
}鎖定以「來源 IP + Email」計算,別人從其他 IP 亂試密碼不會鎖到你。鎖定記錄存在後端記憶體,重啟後清空。IP 取法見 TRUSTED_PROXY_COUNT。
端點: POST /api/auth/forgot-password
請求體:
{
"email": "user@example.com"
}回應 (200):
{
"message": "重設密碼連結已發送至您的 Email"
}端點: POST /api/auth/reset-password
請求體:
{
"token": "abc123...",
"new_password": "N3wP@ssw0rd!Xyz"
}回應 (200):
{
"message": "密碼已重設,請重新登入"
}端點: POST /api/user/change-password
認證: 需要 JWT Token
請求標頭:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
請求體:
{
"old_password": "OldP@ssw0rd!123",
"new_password": "N3wP@ssw0rd!Xyz"
}回應 (200):
{
"message": "密碼已更新"
}端點: POST /api/auth/update-profile
認證: 需要 JWT Token
請求體:
{
"name": "新名字"
}回應 (200):
{
"message": "個人資料已更新"
}端點: POST /api/auth/validate-password
描述: 即時驗證密碼強度(用於前端即時顯示)
請求體:
{
"password": "Test123!",
"email": "user@example.com",
"name": "張三"
}回應 (200):
{
"valid": false,
"checks": {
"length": true,
"uppercase": true,
"lowercase": true,
"digit": true,
"special": true,
"repeating": true,
"sequential": false,
"keyboard_pattern": true,
"common_password": true,
"personal_info": true,
"pinyin": true,
"math_pattern": true,
"entropy": false
},
"errors": [
"連續字元不能超過 3 個(例如:abc, 123)",
"熵值過低,密碼太容易猜測"
]
}端點: GET /api/auth/password-config
回應 (200):
{
"min_length": 12,
"require_uppercase": true,
"require_lowercase": true,
"require_digit": true,
"require_special": true,
"max_repeating": 2,
"max_sequential": 3
}端點: POST /admin/api/accounting/records
認證: 需要 JWT Token
請求體:
{
"type": "expense",
"amount": 1250.50,
"category": "餐飲",
"date": "2024-02-24",
"description": "午餐",
"expense_type": "variable"
}欄位說明:
type:income(收入) 或expense(支出)。transfer(內部轉移)與restricted(受限資金)無法透過本端點直接建立,請分別見下方「新增內部轉移」端點,以及本節下方的restricted_amount拆分欄位amount: 金額(必須 > 0,最大 10,000,000)category: 分類(最多 50 字元)date: 日期 (YYYY-MM-DD 格式)description: 描述(最多 500 字元,可選)expense_type: 支出類型(可選)fixed: 固定支出variable: 變動支出onetime: 一次性支出
進階欄位(可選):
wallet_id: 指定要記入哪個資金錢包(見資金錢包章節);省略、null或空字串代表「未分類」location:bank(銀行)或cash(現金)type=income時必填,由使用者指定資金進帳的位置type=expense時由後端自動判斷:現金餘額足夠優先扣現金,不足則需從銀行提領補齊(見下方「現金不足」情境),前端傳入的location會被忽略
client_id: 離線同步冪等用的字串(最長 64 字元)。帶相同client_id重送會直接回傳既有記錄(deduped: true),不會重複建立;一般線上操作可不帶restricted_amount/restricted_description: 僅type=income適用,用於「收入拆分受限資金」——從這筆收入中拆出部分金額鎖定為受限資金(例如代收代付款項)。restricted_amount需通過金額驗證;此時amount可為0(代表全額皆為受限資金,不產生一般收入記錄,回應也不會有id欄位)confirm_withdrawal:type=expense且現金餘額不足時,第一次請求會回傳409(見下方錯誤回應);前端需帶confirm_withdrawal: true重新送出,才會先建立一筆「自動提領」轉帳記錄(type=transfer、auto_generated: true),再建立支出本身
回應 (201):
{
"message": "記帳記錄已新增",
"id": "65d9f8a7b4c3d2e1a0b9c8d7"
}若使用
restricted_amount拆分受限資金,回應會多一個restricted_id欄位;若amount為0(全額受限),則不會有id欄位(因為沒有建立一般收入記錄)。
錯誤回應 (400):
{
"error": "金額必須大於 0"
}錯誤回應 (409) — 支出時現金不足,需確認自動提領:
{
"error": "cash_insufficient",
"message": "現金不足,需從銀行提領 NT$ 5,000 補齊,是否確認?",
"cash_balance": 1200.0,
"deficit": 3800.0,
"withdrawal_amount": 5000.0
}收到此回應後,若使用者確認提領,重新送出同一請求並加上
"confirm_withdrawal": true即可完成支出。
端點: GET /admin/api/accounting/records
認證: 需要 JWT Token
查詢參數 (可選):
start_date: 開始日期 (YYYY-MM-DD)end_date: 結束日期 (YYYY-MM-DD)type: 記錄類型 (income或expense)category: 分類名稱search: 關鍵字搜尋(比對description欄位,不分大小寫)sort_by: 排序欄位(date(預設)或amount)sort_order: 排序方向(desc(預設)或asc)page: 頁碼(預設1)limit: 每頁筆數(預設20)
範例請求:
GET /admin/api/accounting/records?type=expense&search=午餐&sort_by=amount&sort_order=desc&page=1
回應 (200):
{
"records": [
{
"_id": {"$oid": "65d9f8a7b4c3d2e1a0b9c8d7"},
"type": "expense",
"amount": 1250.5,
"category": "午餐",
"date": "2024-02-24",
"description": "午餐",
"expense_type": "variable",
"created_at": {"$date": "2024-02-24T10:30:00.000Z"},
"user_id": {"$oid": "65d9f8a7b4c3d2e1a0b9c8d6"}
}
],
"total": 42,
"page": 1,
"total_pages": 3
}端點: PUT /admin/api/accounting/records/{record_id}
認證: 需要 JWT Token
請求體:
{
"type": "expense",
"amount": 1500.0,
"category": "交通",
"date": "2024-02-24",
"description": "計程車"
}回應 (200):
{
"message": "記帳記錄已更新"
}端點: DELETE /admin/api/accounting/records/{record_id}
認證: 需要 JWT Token
回應 (200):
{
"message": "記帳記錄已刪除"
}端點: POST /admin/api/accounting/records/transfer
認證: 需要 JWT Token
描述: 記錄同一資金錢包內、銀行與現金之間的資金搬移(例如提款、存款),不計入收入/支出統計,只影響對應位置的餘額計算。
請求體:
{
"wallet_id": "65d9f8a7b4c3d2e1a0b9c8d7",
"from_location": "bank",
"to_location": "cash",
"amount": 3000,
"date": "2024-02-24",
"description": "提款"
}欄位說明:
wallet_id:必填,轉移必須指定所屬帳戶(不可為「未分類」)from_location/to_location:必填,皆為bank或cash,且兩者不可相同amount:必須 > 0,最大 10,000,000date:日期 (YYYY-MM-DD)description:描述(最多 500 字元,可選)
回應 (201):
{
"message": "轉移已記錄",
"id": "65d9f8a7b4c3d2e1a0b9c8d8"
}錯誤回應 (400):
{ "error": "轉出與轉入位置不可相同" }支出時因現金不足而自動產生的提領轉帳(見「新增記帳記錄」的
confirm_withdrawal)也是同一種記錄型別(type: "transfer"),差異在於auto_generated為true,且由系統直接寫入、不經過本端點。
端點: POST /admin/api/accounting/records/{record_id}/unlock
認證: 需要 JWT Token
描述: 將一筆受限資金(type: "restricted")整筆轉為一般收入,並新增一筆對應支出記錄代表這筆錢已經交出去。不支援部分解鎖,一次只能整筆處理。
請求體:
{ "date": "2024-03-01" }欄位說明:
date:必填,代表實際交出去的日期(YYYY-MM-DD);原受限資金記錄沿用當初記錄的收到日期,不會被覆蓋
回應 (201):
{
"message": "已解鎖",
"expense_id": "65d9f8a7b4c3d2e1a0b9c8d9"
}錯誤回應 (400):
{ "error": "只有受限資金記錄可以解鎖" }{ "error": "這筆受限資金已經解鎖過了" }錯誤回應 (404):
{ "error": "找不到該記錄或無權限操作" }端點: GET /admin/api/accounting/data-version
認證: 需要 JWT Token
描述: 輕量更新檢查端點。回傳一個不透明的版本簽章字串(由 records/budget/recurring/wallets 四個集合的筆數與最後更新時間組合計算),供前端(尤其是多裝置或 PWA 離線情境)比對是否需要重新抓取完整資料,避免每次都重新整理造成不必要的流量與畫面閃爍。
回應 (200):
{ "version": "3f2504e04f8964..." }備註:
version純粹是不透明字串,非安全用途(內部以 MD5 產生),前端只需比對是否與上次快取的值相同即可- records/budget/recurring/wallets 任一集合的新增、修改、刪除都會改變此值
- 速率限制:200 次/分鐘
資金錢包(wallet)代表同一登入帳號底下的不同資金來源(例如「日常帳戶」「旅遊基金」),與登入帳號(user_id)是不同概念——一個帳號底下可以有多個錢包。記帳記錄的 wallet_id 為可選欄位,null 代表「未分類」,沿用既有記錄相容,不強制搬遷舊資料。
以下端點皆需要 JWT Token 認證。
端點: GET /admin/api/wallets
查詢參數 (可選):
show_archived:true時包含已封存的錢包(預設false,只回傳未封存)
回應 (200):
[
{
"id": "65d9f8a7b4c3d2e1a0b9c8d7",
"name": "日常帳戶",
"icon": "💰",
"color": "#2563EB",
"is_default": true,
"archived": false,
"created_at": "2024-02-24T10:30:00.000000"
}
]備註: 速率限制 100 次/分鐘。
端點: POST /admin/api/wallets
請求體:
{
"name": "旅遊基金",
"icon": "✈️",
"color": "#10B981",
"is_default": false
}欄位說明:
name:必填,1-30 字元icon/color:可選,最長 40 字元,空字串會被正規化為nullis_default:可選,預設false;設為true時,同一使用者底下其他錢包會自動被取消預設狀態(同時只能有一個預設錢包)
回應 (201):
{ "id": "65d9f8a7b4c3d2e1a0b9c8d8", "message": "錢包已新增" }錯誤回應 (400):
{ "error": "錢包名稱不可為空" }備註: 速率限制 30 次/分鐘。
端點: PUT /admin/api/wallets/{wallet_id}
描述: 可改名、換圖示/顏色、設為預設、封存或還原。所有欄位皆為可選,只需傳送要修改的欄位。
請求體範例:
{ "name": "旅遊基金 2024", "is_default": true }欄位說明:
name:1-30 字元icon/color:最長 40 字元archived:true/false;設為true時會一併取消該錢包的預設狀態is_default:true/false;與archived同時出現時以archived邏輯優先(archived: true一律清除is_default)
回應 (200):
{ "message": "錢包已更新" }錯誤回應 (400):
{ "error": "沒有可更新的欄位" }錯誤回應 (404):
{ "error": "找不到該錢包或無權限修改" }備註: 速率限制 30 次/分鐘。
端點: DELETE /admin/api/wallets/{wallet_id}
描述: 封存而非刪除——歷史關聯記錄不受影響,只是該錢包不再出現在新增記錄的選單中。封存同時會取消該錢包的預設狀態。
回應 (200):
{ "message": "錢包已封存" }錯誤回應 (404):
{ "error": "找不到該錢包或無權限操作" }備註: 速率限制 30 次/分鐘。
端點: GET /admin/api/wallets/balances
描述: 回傳每個(未封存)錢包的收入、支出、餘額,並額外附加一筆 wallet_id: null 的「未分類」桶(涵蓋舊資料或尚未指定錢包的記錄),方便使用者核對總額。
回應 (200):
[
{
"wallet_id": "65d9f8a7b4c3d2e1a0b9c8d7",
"name": "日常帳戶",
"icon": "💰",
"color": "#2563EB",
"is_default": true,
"income": 50000.0,
"expense": 32500.0,
"balance": 17500.0
},
{
"wallet_id": null,
"name": "未分類",
"icon": null,
"color": null,
"is_default": false,
"income": 0.0,
"expense": 0.0,
"balance": 0.0
}
]備註: 速率限制 100 次/分鐘。
端點: GET /admin/api/wallets/location-summary
描述: 同時回傳每個帳戶在「銀行」「現金」兩個位置的餘額(收入 − 支出 + 轉入 − 轉出),以及跨帳戶的位置總計,供統計頁面顯示銀行/現金維度使用。
回應 (200):
{
"wallets": [
{
"wallet_id": "65d9f8a7b4c3d2e1a0b9c8d7",
"name": "日常帳戶",
"icon": "💰",
"color": "#2563EB",
"is_default": true,
"locations": {
"bank": { "balance": 42000.0 },
"cash": { "balance": 3500.0 }
},
"unclassified_balance": 0.0,
"total_balance": 45500.0
},
{
"wallet_id": null,
"name": "未分類",
"icon": null,
"color": null,
"is_default": false,
"locations": {
"bank": { "balance": 0.0 },
"cash": { "balance": 0.0 }
},
"unclassified_balance": 0.0,
"total_balance": 0.0
}
],
"location_totals": {
"bank": 42000.0,
"cash": 3500.0
}
}備註:
unclassified_balance為該帳戶內location欄位缺失的舊資料餘額(此功能上線前的歷史記錄)- 速率限制 100 次/分鐘
端點: GET /admin/api/wallets/{wallet_id}/balance-history
描述: 回傳指定錢包近 N 個月的月度增減(net_changes)與月底累計餘額(balances)。累計餘額會從該錢包最早有記錄的月份開始逐月往下算,確保回傳區間的起始餘額正確,不會因為只看最近 N 個月而從 0 起算。
查詢參數 (可選):
months:顯示月份數量(預設 12,範圍 1-24)
回應 (200):
{
"wallet_id": "65d9f8a7b4c3d2e1a0b9c8d7",
"months": ["2023-09", "2023-10", "2023-11", "2023-12", "2024-01", "2024-02"],
"net_changes": [5000, -2000, 3000, -1500, 4000, -2500],
"balances": [5000, 3000, 6000, 4500, 8500, 6000]
}錯誤回應 (400):
{ "error": "months 必須為整數" }錯誤回應 (404):
{ "error": "找不到該錢包或無權限存取" }備註: 速率限制 100 次/分鐘。
端點: GET /admin/api/wallets/restricted-funds
描述: 列出目前所有還鎖著(尚未解鎖)的受限資金記錄(type: "restricted"),供錢包管理面板顯示總額與明細。已解鎖的記錄不會出現在此列表(解鎖後 type 會變成 income)。
回應 (200):
{
"total": 8000.0,
"items": [
{
"id": "65d9f8a7b4c3d2e1a0b9c8da",
"amount": 8000.0,
"description": "代收班費",
"date": "2024-02-24",
"wallet_id": "65d9f8a7b4c3d2e1a0b9c8d7",
"wallet_name": "日常帳戶",
"location": "bank",
"linked_income_id": "65d9f8a7b4c3d2e1a0b9c8db"
}
]
}備註:
wallet_name即使該錢包已被封存也會正確顯示(受限資金查詢會含已封存錢包的名稱對照)linked_income_id指向拆分出這筆受限資金的原始收入記錄- 速率限制 100 次/分鐘
端點: GET /admin/api/wallets/{wallet_id}/flow-tree
描述: 回傳單一錢包的銀行/現金淨額、目前鎖定中的受限資金總額,以及三種「已明確記錄的資金流向」的全時間累計次數與金額:
auto_withdrawal:銀行 → 現金的自動提領(支出時現金不足,系統自動產生的轉帳,auto_generated: true)restricted_split:收入 → 受限資金的拆分(不分是否已解鎖,累計曾經拆出去的總額)restricted_unlock:受限資金 → 原位置的解鎖(source_restricted_id不為空的支出,位置沿用原受限記錄的location)
不做一般收入/支出配對追蹤,只加總這三種欄位本身已經記錄的關聯。
回應 (200):
{
"wallet_id": "65d9f8a7b4c3d2e1a0b9c8d7",
"wallet_name": "日常帳戶",
"locations": {
"bank": { "balance": 42000.0 },
"cash": { "balance": 3500.0 }
},
"restricted_locked_total": 8000.0,
"edges": {
"auto_withdrawal": { "count": 3, "amount": 15000.0 },
"restricted_split": { "count": 2, "amount": 12000.0 },
"restricted_unlock": { "count": 1, "amount": 4000.0 }
}
}錯誤回應 (404):
{ "error": "找不到該錢包或無權限存取" }備註: 速率限制 100 次/分鐘。
記帳記錄可附加照片憑證。照片 metadata 內嵌在 records.photos 陣列(不開獨立集合),實際檔案存於伺服器端 PHOTO_STORAGE_PATH 目錄下,依 <user_id>/<record_id>/<photo_id>.<副檔名> 存放。不做伺服器端縮圖或壓縮(前端上傳前已先行壓縮),伺服器端只用「大小上限」把關。
以下端點皆需要 JWT Token 認證。
端點: POST /admin/api/accounting/records/{record_id}/photos
描述: 上傳一或多張照片到指定記帳記錄,multipart/form-data 格式,欄位名固定為 photos(可重複帶多個檔案達成多檔上傳)。
請求範例:
curl -X POST http://localhost:5001/admin/api/accounting/records/65d9f8a7b4c3d2e1a0b9c8d7/photos \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "photos=@receipt1.jpg" \
-F "photos=@receipt2.png"驗證規則:
- 單筆記錄最多 20 張照片(含已存在的)
- 單檔大小上限 10MB
- 僅接受
image/jpeg、image/png、image/webp,且會實際比對檔案開頭的簽章 magic bytes(不信任請求帶的Content-Type),偽裝成圖片的任意檔案會被拒絕 - 全部檔案驗證通過才會寫入磁碟;只要有一張未通過驗證,整批都不會寫入(避免部分寫入卻資料庫沒同步的不一致狀態)
回應 (201):
{
"photos": [
{
"id": "65d9f8a7b4c3d2e1a0b9c8dc",
"filename": "65d9f8a7b4c3d2e1a0b9c8dc.jpg",
"original_filename": "receipt1.jpg",
"content_type": "image/jpeg",
"size_bytes": 245680,
"uploaded_at": "2024-02-24T10:30:00.000000"
}
]
}錯誤回應 (400):
{ "error": "單筆記錄最多 20 張照片" }{ "error": "照片檔案過大(上限 10MB)" }{ "error": "不支援的圖片格式" }錯誤回應 (404):
{ "error": "找不到該記錄或無權限操作" }備註: 速率限制 30 次/分鐘。
端點: DELETE /admin/api/accounting/records/{record_id}/photos/{photo_id}
描述: 先移除資料庫內的照片參照,再刪除磁碟檔案。若磁碟刪除失敗,只會留下無害的孤兒檔案(可事後清理),不會出現「畫面顯示一張其實刪不掉的照片」的狀況。
回應 (200):
{ "message": "已刪除" }錯誤回應 (404):
{ "error": "找不到該照片" }備註: 速率限制 30 次/分鐘。
端點: GET /admin/api/accounting/records/{record_id}/photos/{photo_id}
描述: 回傳照片檔案本體(二進位)。需以 Authorization: Bearer header 認證——因為 <img src> 無法帶自訂 header,前端須改用 fetch() 取得 blob 後自行 createObjectURL() 給 <img> 使用。
請求範例:
curl -X GET http://localhost:5001/admin/api/accounting/records/65d9f8a7b4c3d2e1a0b9c8d7/photos/65d9f8a7b4c3d2e1a0b9c8dc \
-H "Authorization: Bearer YOUR_TOKEN" \
-o receipt1.jpg回應 (200): 對應 content_type 的二進位圖片內容。
錯誤回應 (404):
{ "error": "照片檔案遺失" }資料庫內有參照但磁碟檔案不存在時回傳此錯誤(例如儲存卷未正確掛載)。
備註: 速率限制 300 次/分鐘。
端點: GET /admin/api/accounting/photos
描述: 依上傳時間新到舊排序、分頁回傳使用者所有記帳記錄的照片清單,供「照片牆」瀏覽介面使用;點縮圖可依回傳的 record_id 跳轉到對應記錄。
查詢參數 (可選):
page:頁碼(預設 1)limit:每頁筆數(預設 30,最大 100)
回應 (200):
{
"items": [
{
"record_id": "65d9f8a7b4c3d2e1a0b9c8d7",
"record_type": "expense",
"record_category": "餐飲",
"record_date": "2024-02-24",
"photo_id": "65d9f8a7b4c3d2e1a0b9c8dc",
"content_type": "image/jpeg",
"uploaded_at": "2024-02-24T10:30:00.000000"
}
],
"total": 42,
"page": 1,
"limit": 30,
"total_pages": 2
}錯誤回應 (400):
{ "error": "page 和 limit 必須為正整數" }備註: 速率限制 100 次/分鐘。
端點: GET /admin/api/accounting/stats
認證: 需要 JWT Token
查詢參數 (可選):
start_date: 開始日期 (YYYY-MM-DD)end_date: 結束日期 (YYYY-MM-DD)
範例請求:
GET /admin/api/accounting/stats?start_date=2024-02-01&end_date=2024-02-28
回應 (200):
{
"total_income": 50000,
"total_expense": 32500,
"balance": 17500,
"category_stats": [
{
"_id": "餐飲",
"total": 8500
},
{
"_id": "交通",
"total": 3200
}
]
}端點: GET /admin/api/accounting/comparison
認證: 需要 JWT Token
查詢參數 (可選):
period:month(預設)/week/quarter/year
範例請求:
GET /admin/api/accounting/comparison?period=week
回應 (200):
{
"current": {
"income": 5000,
"expense": 3200,
"balance": 1800,
"label": "2026/03/09 週"
},
"previous": {
"income": 4800,
"expense": 3000,
"balance": 1800,
"label": "2026/03/02 週"
},
"changes": {
"income_pct": 4.2,
"expense_pct": 6.7,
"balance_pct": 0.0
}
}
label格式依 period 不同:week為"YYYY/MM/DD 週",month為"YYYY-MM",quarter為"YYYY-QN",year為"YYYY"。
錯誤 (400):
{ "error": "period 必須為 week、month、quarter 或 year" }端點: GET /admin/api/accounting/budget
認證: 需要 JWT Token
回應 (200):
{
"month": "2024-02",
"budget": {
"餐飲": 10000,
"交通": 5000,
"娛樂": 3000
}
}端點: POST /admin/api/accounting/budget
認證: 需要 JWT Token
請求體:
{
"budget": {
"餐飲": 10000,
"交通": 5000,
"娛樂": 3000,
"購物": 8000
}
}回應 (200):
{
"message": "預算已儲存"
}所有端點均需要 JWT Token,速率限制 30 次/分鐘。
端點: GET /admin/api/recurring
回應 (200):
[
{
"_id": "abc123",
"name": "房租",
"amount": 15000,
"type": "expense",
"category": "居住",
"day_of_month": 5,
"description": "每月房租",
"created_at": "2026-03-01T00:00:00"
}
]端點: POST /admin/api/recurring
請求體:
{
"name": "房租",
"amount": 15000,
"type": "expense",
"category": "居住",
"day_of_month": 5,
"description": "每月房租"
}欄位驗證:
name:必填,1-50 字元amount:必填,正數type:income或expenseday_of_month:1-31category:最多 30 字元(預設「其他」)description:最多 200 字元
回應 (201):
{ "id": "abc123", "message": "新增成功" }端點: PUT /admin/api/recurring/<id>
請求體格式同新增,所有欄位均需提供。
回應 (200):
{ "message": "更新成功" }端點: DELETE /admin/api/recurring/<id>
回應 (200):
{ "message": "刪除成功" }端點: POST /admin/api/recurring/<id>/apply
說明: 依定期項目設定的日期,在當月建立一筆實際記帳記錄。若 day_of_month 超出當月天數(如 31 日在二月),自動調整至月底。
回應 (201):
{ "id": "新建記錄ID", "message": "記帳成功" }欠款追蹤記錄「借出」「借入」款項,支援單一對象與多人分帳(members)兩種模式。還款時會自動同步寫入一筆記帳記錄(debt_id 指向該筆欠款、auto_generated: true),讓還款金額同時反映在收支統計中。
以下端點皆需要 JWT Token 認證。
debt_type:lent(借出,別人欠我)或borrowed(借入,我欠別人)。註:歷史資料可能存在已淘汰的debt_type: "group"(群組分帳),後端提供一次性遷移函式migrate_group_debts()自動轉為lent+members格式,新資料一律使用lent/borrowedperson:對象姓名或標題,最多 50 字元amount:總金額reason:原因說明,最多 200 字元(可選)paid_amount:已還款總額(單一對象模式下由/repay累加;多人分帳模式下由各成員paid_amount加總得出)is_settled:是否已結清repayments:單一對象模式的還款歷史陣列({amount, date, note})members:多人分帳陣列,每筆為{name, share, paid_amount, is_settled};空陣列代表單一對象模式- 動態附加欄位(僅
members非空時附加,由 API 即時計算,不落地儲存):total_members、paid_members、pending_receivable(尚未還清的成員應收總額)
端點: GET /admin/api/debts
查詢參數 (可選):
type:篩選debt_type(lent、borrowed,或已淘汰但仍相容的group)show_settled:true時包含已結清的欠款(預設false,只回傳未結清)
回應 (200):
[
{
"_id": { "$oid": "65d9f8a7b4c3d2e1a0b9c8dd" },
"debt_type": "lent",
"person": "小明",
"amount": 3000,
"reason": "代墊聚餐費用",
"date": "2024-02-24",
"paid_amount": 1000,
"is_settled": false,
"repayments": [
{ "amount": 1000, "date": "2024-02-25", "note": "先還一部分" }
],
"members": [],
"created_at": { "$date": "2024-02-24T10:30:00.000Z" },
"user_id": { "$oid": "65d9f8a7b4c3d2e1a0b9c8d6" }
}
]備註: 速率限制 100 次/分鐘。
端點: POST /admin/api/debts
請求體(單一對象):
{
"debt_type": "lent",
"person": "小明",
"amount": 3000,
"reason": "代墊聚餐費用",
"date": "2024-02-24"
}請求體(多人分帳):
{
"debt_type": "lent",
"person": "聚餐分帳",
"amount": 3000,
"date": "2024-02-24",
"members": [
{ "name": "小明", "share": 1000 },
{ "name": "小華", "share": 2000 }
]
}欄位說明:
debt_type:必填,lent或borrowedperson:必填,最多 50 字元amount:必填,須 > 0date:可選,預設今天reason:可選,最多 200 字元members:可選,提供時每筆需有name(會 trim 並截斷至 50 字元,空白名稱會被忽略)與share;提供members即進入多人分帳模式
回應 (201):
{ "id": "65d9f8a7b4c3d2e1a0b9c8dd", "message": "欠款記錄已新增" }錯誤回應 (400):
{ "error": "debt_type 必須為 lent 或 borrowed" }{ "error": "請輸入對象姓名或標題" }{ "error": "請輸入有效金額" }備註: 速率限制 50 次/分鐘。
端點: GET /admin/api/debts/{debt_id}
回應 (200): 同列表中單筆的格式(含動態附加欄位)。
錯誤回應 (404):
{ "error": "找不到該記錄或無權限存取" }備註: 速率限制 100 次/分鐘。
端點: PUT /admin/api/debts/{debt_id}
描述: 可修改 person、amount、reason、date、members,皆為可選欄位,只需傳送要修改的欄位。修改 members 會整組覆蓋。
請求體範例:
{ "amount": 3500, "reason": "追加消夜費用" }回應 (200):
{ "message": "欠款記錄已更新" }錯誤回應 (400):
{ "error": "沒有可更新的欄位" }備註: 速率限制 50 次/分鐘。
端點: DELETE /admin/api/debts/{debt_id}
描述: 刪除欠款記錄本身;曾同步產生的記帳記錄不會被刪除,但會標記 debt_deleted: true(debt_id 對應且 auto_generated: true 的記帳記錄)。
回應 (200):
{ "message": "欠款記錄已刪除" }錯誤回應 (404):
{ "error": "找不到該記錄或無權限刪除" }備註: 速率限制 50 次/分鐘。
端點: POST /admin/api/debts/{debt_id}/repay
描述: 僅適用於單一對象模式(members 為空陣列);多人分帳請改用「分帳成員還款」端點。還款會同步寫入一筆記帳記錄:debt_type=lent 寫入 income(分類「債務收回」),debt_type=borrowed 寫入 expense(分類「債務償還」)。
請求體:
{ "amount": 1000, "date": "2024-02-25", "note": "先還一部分" }欄位說明:
amount:必填,須 > 0date:可選,預設今天note:可選,最多 100 字元
回應 (200):
{ "message": "還款記錄已新增", "is_settled": false }回應 (200) — 記帳同步失敗時(還款本身仍會成功寫入,只是對應的記帳記錄沒有一併產生):
{
"message": "還款記錄已新增,但記帳同步失敗,請手動補記",
"is_settled": false,
"sync_failed": true
}錯誤回應 (400):
{ "error": "請輸入有效還款金額" }{ "error": "多人分帳請使用分帳成員還款功能" }備註: 速率限制 50 次/分鐘。
端點: POST /admin/api/debts/{debt_id}/members/{member_idx}/repay
描述: member_idx 為 members 陣列的索引(從 0 起算)。還款邏輯與單一對象相同(同步寫入對應記帳記錄),但更新的是該成員的 paid_amount/is_settled,並重新計算整筆欠款的 paid_amount(所有成員加總)與 is_settled(是否全員結清)。
請求體:
{ "amount": 1000, "date": "2024-02-25" }回應 (200):
{ "message": "還款已記錄", "is_settled": false }錯誤回應 (400):
{ "error": "無效的成員索引" }備註: 速率限制 50 次/分鐘。
端點: POST /admin/api/debts/{debt_id}/settle
描述: 手動切換整筆欠款的 is_settled(true ↔ false),不受還款金額是否足額限制,供使用者手動標記「已結清」或「取消結清」。
回應 (200):
{ "message": "狀態已更新", "is_settled": true }備註: 速率限制 50 次/分鐘。
端點: PUT /admin/api/debts/{debt_id}/members/{member_idx}/pay
描述: 僅適用於已淘汰但仍相容的 debt_type: "group" 資料格式,切換指定成員的 paid(布林)狀態;所有成員皆 paid 時整筆欠款自動標記為 is_settled。新資料建議一律使用 lent/borrowed + members 格式與「分帳成員還款」端點。
回應 (200):
{ "message": "成員付款狀態已更新", "is_settled": false }錯誤回應 (400):
{ "error": "此功能僅適用於群組分帳" }備註: 速率限制 50 次/分鐘。
端點: GET /admin/api/accounting/export
認證: 需要 JWT Token
查詢參數 (可選):
start_date: 開始日期 (YYYY-MM-DD)(format=json時會被忽略,JSON 備份一律匯出全部記錄)end_date: 結束日期 (YYYY-MM-DD)(同上)type: 記錄類型 (income或expense)(format=json時同樣被忽略)format: 匯出格式,csv(預設)、xlsx、json三選一;其他值一律視為csv
範例請求:
GET /admin/api/accounting/export?start_date=2024-02-01&end_date=2024-02-28&type=expense&format=csv
GET /admin/api/accounting/export?format=xlsx
GET /admin/api/accounting/export?format=json
回應 (200) — CSV:
- Content-Type:
text/csv; charset=utf-8-sig - Content-Disposition:
attachment; filename=記帳記錄_2024-02-01_至_2024-02-28.csv
日期,類型,分類,金額,描述,支出類型
2024-02-24,支出,餐飲,1250.5,午餐,變動支出
2024-02-23,收入,薪資,50000,月薪,回應 (200) — Excel (xlsx):
- Content-Type:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - Content-Disposition:
attachment; filename=記帳記錄_20240224.xlsx - 內容欄位與 CSV 相同(日期/類型/分類/金額/描述/支出類型),加上標題列樣式(藍底白字置中)與凍結首列
回應 (200) — JSON 備份:
- Content-Type:
application/json - Content-Disposition:
attachment; filename=記帳備份_20240224.json
{
"version": "1.0",
"exported_at": "2024-02-24T10:30:00",
"count": 2,
"records": [
{
"type": "expense",
"amount": 1250.5,
"category": "餐飲",
"date": "2024-02-24",
"description": "午餐",
"expense_type": "variable"
}
]
}功能特點:
- CSV/Excel:UTF-8 BOM 支援 Excel 正確顯示中文;
type欄位轉為中文顯示(含transfer「轉帳」、restricted「受限資金」) - JSON 備份格式:一律匯出全部記錄(不套用日期/類型篩選),僅保留
type/amount/category/date/description/expense_type六個欄位(不含wallet_id、location、photos等其他欄位),供搭配「匯入記帳記錄」端點還原使用 - 自動生成檔名包含日期範圍(CSV/Excel)或匯出日期(JSON)
- 速率限制:10 次/小時
端點: POST /admin/api/accounting/import
認證: 需要 JWT Token
描述: 從「匯出記帳記錄」的 JSON 備份格式匯入記帳記錄,逐筆驗證並寫入,不會整批失敗——單筆驗證失敗只會被計入 invalid,不影響其他筆的匯入。
請求體:
{
"records": [
{
"type": "expense",
"amount": 1250.5,
"category": "餐飲",
"date": "2024-02-24",
"description": "午餐",
"expense_type": "variable"
}
]
}欄位說明(records 陣列內每筆):
type:須通過與新增記帳記錄相同的類型驗證(income/expense/transfer/restricted)amount:須通過金額驗證(> 0,最大 10,000,000)date:須為合法 YYYY-MM-DD 日期category:須為非空字串(最多 50 字元),存入時會去掉頭尾空白;數字、物件等非字串視為無效description:可選,最多 500 字元expense_type:可選,須為fixed/variable/onetime,其餘值一律視為未設定
去重邏輯: 逐筆比對 user_id + date + type + amount + category + description 完全相同的既有記錄,命中即視為重複、不重複匯入(計入 duplicates)。匯入的記錄一律不會帶 wallet_id/location(視為「未分類」),也不會帶 client_id。
其他規則:
records內不是物件的項目(例如字串、數字)計入invalid,不影響其他筆。- 單次最多 20,000 筆,超過回 400,請分批匯入。
- 有新增時會清除該用戶的統計快取,匯入後統計頁立即反映新資料。
- CSV 匯出:備註、分類若以
=、+、-、@開頭,會加上單引號前綴,避免被試算表當公式執行;Excel 匯出則將這類字串標為文字;JSON 備份維持原值。
回應 (200):
{
"imported": 8,
"duplicates": 2,
"invalid": 1,
"total": 11
}錯誤回應 (400):
{ "error": "格式錯誤:需要包含 records 陣列" }備註: 速率限制 5 次/小時。
端點: GET /admin/api/accounting/trends
認證: 需要 JWT Token
查詢參數 (可選):
months: 顯示月份數量 (預設 6,最大 24)
範例請求:
GET /admin/api/accounting/trends?months=12
回應 (200):
{
"months": ["2023-08", "2023-09", "2023-10", "2023-11", "2023-12", "2024-01", "2024-02"],
"income": [50000, 52000, 50000, 55000, 50000, 60000, 50000],
"expense": [32000, 35000, 28000, 40000, 45000, 38000, 32500]
}功能特點:
- 預設顯示最近 6 個月
- 支援自訂月份數量(1-24)
- 自動補齊缺少資料的月份(顯示為 0)
- 按月份升冪排序
- 速率限制:100 次/分鐘
前端應用範例 (Chart.js):
const response = await fetch('/admin/api/accounting/trends?months=6', {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
new Chart(ctx, {
type: 'line',
data: {
labels: data.months,
datasets: [
{
label: '收入',
data: data.income,
borderColor: 'rgb(34, 197, 94)'
},
{
label: '支出',
data: data.expense,
borderColor: 'rgb(239, 68, 68)'
}
]
}
});端點: GET /api/user/profile
認證: 需要 JWT Token
回應 (200):
{
"email": "user@example.com",
"name": "張三",
"created_at": "2024-01-15T10:30:00Z"
}端點: GET /health 或 GET /
描述: 輕量級健康檢查(無需認證)
回應 (200):
{
"status": "healthy",
"service": "accounting-system"
}端點: GET /status
描述: 系統狀態檢查(包含資料庫連線狀態)
回應 (200):
{
"status": "ok",
"db_status": "connected",
"message": "記帳系統運作正常"
}所有需要認證的端點都必須在請求標頭中包含 JWT Token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
- 預設 7 天
- 過期後需要重新登入
- 修改密碼或重設密碼後,在那之前簽發的 Token 會立即失效(回 401),所有舊裝置需重新登入;修改密碼之後重新登入取得的新 Token 不受影響。從未改過密碼的帳號不受影響
未認證或 Token 無效時:
{
"error": "未授權"
}HTTP 狀態碼: 401
為防止濫用,API 實施速率限制:
| 端點類別 | 限制 |
|---|---|
| 註冊 | 5 次/小時 |
| 登入 | 10 次/分鐘(另有失敗鎖定,見「登入」) |
| 忘記密碼 | 5 次/小時 |
| 重設密碼 | 10 次/小時 |
密碼強度檢查/密碼規則(validate-password、password-config) |
60 次/分鐘 |
| 資料匯出 | 10 次/小時 |
| 資料匯入 | 5 次/小時 |
| 修改密碼 | 5 次/小時 |
| 一般 API | 100 次/分鐘 |
超過限制會返回:
{
"error": "Too Many Requests"
}HTTP 狀態碼: 429
| 狀態碼 | 說明 |
|---|---|
| 200 | 成功 |
| 201 | 創建成功 |
| 400 | 請求錯誤(驗證失敗、參數錯誤) |
| 401 | 未認證 |
| 403 | 無權限 |
| 404 | 資源不存在 |
| 429 | 請求次數過多 |
| 500 | 伺服器錯誤 |
- 必須大於 0
- 最大值:10,000,000
- 格式:數字或浮點數
- 格式:YYYY-MM-DD
- 必須為有效日期
- 不可為空
- 最大長度:50 字元
- 允許自訂分類
- 最大長度:500 字元
- 可選欄位
- 必須符合 Email 格式
- 不可重複
- 最小長度:12 字元
- 必須包含大小寫字母、數字、特殊符號
- 詳細規則見認證相關
curl -X POST http://localhost:5001/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "MyS3cur3P@ssw0rd!XyZ",
"name": "張三"
}'curl -X POST http://localhost:5001/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "MyS3cur3P@ssw0rd!XyZ"
}'curl -X POST http://localhost:5001/admin/api/accounting/records \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"type": "expense",
"amount": 1250.5,
"category": "餐飲",
"date": "2024-02-24",
"description": "午餐"
}'curl -X GET "http://localhost:5001/admin/api/accounting/stats?start_date=2024-02-01&end_date=2024-02-28" \
-H "Authorization: Bearer YOUR_TOKEN"curl -X GET "http://localhost:5001/admin/api/accounting/export?start_date=2024-02-01&end_date=2024-02-28" \
-H "Authorization: Bearer YOUR_TOKEN" \
-o "記帳記錄.csv"查看 CHANGELOG.md 獲取最新的 API 變更資訊。
如有問題或建議,請至 GitHub Issues 提出。