MyFin Budget API 使用详解

MyFin Budget(myfin-api 5.5.0)REST API 使用详解:认证会话机制、17 个端点域全路由表、账户/分类/实体/交易/预算/统计全链路 curl 实测回显、分页过滤约定与错误码手册,全部请求与响应为真实实测记录。

MyFin BudgetAPINode.js财务记账2026/10/24

MyFin Budget API 使用详解

目标:以 REST API 视角完整打通 MyFin Budget——登录拿会话、建账户、记交易、做预算、查统计、做备份,每个请求附真实响应回显,并给出全部 17 个端点域的路由清单与错误码手册。所有 curl 请求与响应均在实测环境(myfin-api 5.5.0,Ubuntu 22.04)真实执行记录。

一、环境信息

  • 操作系统:Ubuntu 22.04 LTS x86_64
  • API 版本:myfin-api 5.5.0(GET / 实测 {"info":"MyFin API","version":"5.5.0"})
  • API 基地址:http://localhost:8151(三服务部署形态,API 容器 3001 映射宿主 8151;部署见《MyFin Budget Docker Compose 部署指南》)
  • 客户端工具:curl 7.81+(任意 HTTP 客户端均可)
  • 测试账号:admin(实例首个用户,经 POST /setup/init 创建)
  • 路由清单来源:官方源码 src/routes/routeDefinitions.ts(5.5.0,2026-10-24)

二、前置条件(端点/认证/客户端工具)

  • 端点形态:全部业务端点直接挂在 API 根路径下(无 /api 前缀),如 /auth/、/accounts/;请求与响应均为 JSON。
  • 认证方式:登录换取 UUID 会话键后,后续请求以两个请求头携带身份——sessionkey: <登录返回的键> 与 authusername: <用户名>(Android 客户端形态额外加 mobile: true,会话有效期 30 天)。这是源码 commonsController.ts 的实际校验逻辑,实测缺 authusername 头会得到 Prisma 查询错误(username undefined)。
  • 会话有效期:Web 会话 30 分钟滑动续期(源码 sessionManager.ts 实查),每次有效请求顺延一个窗口;过期重新登录即可,无需刷新令牌。
  • 限流约定:全局限流每 IP 每分钟 100 请求(源码 rateLimiter.ts 实查 windowMs: 60*1000, limit: 100),超限返回 You have exceeded your 100 requests per minute limit.;脚本调用注意节奏。
  • 客户端工具:curl 即可覆盖全部场景;Windows/Mac 用户可用任意 REST 客户端(Postman 等),无特殊请求头之外的要求。

三、API 总览

3.1 端点域全路由表

17 个端点域共 60 余个端点(方法 + 路径照抄自 5.5.0 路由定义源码;标 ★ 的域在本文第七章有全链路实测回显):

域基路径端点
Setup ★/setupPOST /init(实例初始化,仅无用户时可用)
Auth ★/authPOST /(登录);POST /recovery/sendOtp;POST /recovery/setNewPassword
Session/validityPOST /(校验会话有效性)
Users ★/usersPOST /(注册);PUT /changePW/;PUT /changeCurrency;POST /demo/(灌演示数据)
User Data ★/userGET /categoriesEntitiesTags;GET /backup;PUT /restore
Accounts ★/accountsPOST /;GET /;PUT /;DELETE /;GET /stats/balance-snapshots/;GET /recalculate-balance/all
Categories ★/catsGET /;POST /;PUT /;DELETE /
Entities ★/entitiesGET /;POST /;PUT /;DELETE /
Tags/tagsGET /filteredByPage/:page;POST /;PUT /:id;DELETE /:id
Transactions ★/trxsGET /;GET /filteredByPage/:page;POST /step0;POST /step1;PUT /;DELETE /;GET /inMonthAndCategory;POST /auto-cat-trx;POST /import/step0;POST /import/step1;POST /import/step2
Budgets ★/budgetsGET /;GET /filteredByPage/:page;GET /list/summary;GET /:id;GET /matrix;POST /step0;POST /step1;PUT /;PUT /:id;PUT /:id/description;PUT /status;DELETE /
Rules/rulesGET /;POST /;PUT /;DELETE /
Goals/goalsGET /;POST /;PUT /:id;DELETE /:id
Stats ★/statsGET /dashboard/month-expenses-income-distribution;GET /dashboard/month-by-month;GET /userStats;GET /stats/monthly-patrimony-projections;GET /category-expenses-evolution;GET /category-income-evolution;GET /year-by-year-income-expense-distribution
Invest Assets/invest/assetsGET /;GET /summary;GET /stats;POST /;PUT /:id;PUT /:id/value;DELETE /:id;DELETE /:id/value/:year/:month
Invest Transactions/invest/trxGET /;GET /filteredByPage/:page;POST /;PUT /:id;DELETE /:id
Invest Reports/invest/reportsGET /annual/:year

3.2 通用约定

  • 成功响应:写操作返回 JSON 字符串消息(如 "Account successfully created")或对象(如 {"budget_id":2});读操作返回 JSON 数组/对象。
  • 认证:除 POST /setup/init、POST /auth/、GET / 外,所有端点都要求 sessionkey + authusername 双请求头。
  • 金额:读接口中金额多为字符串形式的元值("amount":"42.5"),备份接口中为整数分值(315750 = 3157.50 元)。

四、认证与首次调用(curl 实测)

4.1 实例初始化(首个用户)

仅当系统中没有任何用户时可用(否则 HTTP 406):

curl -s -X POST http://localhost:8151/setup/init \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"q1w2e3r4","email":"admin@example.com","currency":"USD"}'

实测回显:

"Done!"

currency 必须取自内置货币码(USD/EUR/CNY/GBP/JPY 等 20 余种,详见配置参数详解篇 5.7);email 必须为合法格式(实测 admin@myfin.local 被拒:{"message":"\"email\" must be a valid email"})。

4.2 登录(POST /auth/)

curl -s -X POST http://localhost:8151/auth/ \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"q1w2e3r4"}'

实测回显:

{"user_id":1,"username":"admin","email":"admin@example.com","sessionkey":"465e8818-bc1a-485b-891d-3f9a5ad19403","sessionkey_mobile":null,"last_update_timestamp":"0","accounts":[],"currency":"USD","is_demo":false}

关键字段:sessionkey 即后续全部请求的身份凭证;user_id 为用户主键;currency 为当前本位币。用户不存在或密码错误时返回 {"message":"User Not Found"} 之类错误消息。

4.3 带认证的首个请求

后续请求统一带双请求头。以读账户列表为例:

curl -s -H "sessionkey:465e8818-bc1a-485b-891d-3f9a5ad19403" \
  -H "authusername:admin" http://localhost:8151/accounts/

实测回显:

[]

认证失败实测:伪造 sessionkey 返回 HTTP 401(实测 -w "%{http_code}" 回显):

401

💡 说明:以下第七章各域实测均使用同一会话(sessionkey:465e8818-... + authusername:admin),示例中简写为 -H "sessionkey:$SK" -H "authusername:admin"($SK 即登录返回的会话键,实际调用时替换为真实值)。

五、分页/过滤/排序通用约定

  • 交易列表 GET /trxs/:查询参数 trx_limit(单次返回条数上限,最小 1、最大 300、默认 300,源码 joi schema 实查)。
  • 分页列表 GET /trxs/filteredByPage/:page、GET /tags/filteredByPage/:page、GET /budgets/filteredByPage/:page:路径参数 page 从 1 起;查询参数 page_size(默认 99999,最大 300,源码 DEFAULT_TRANSACTIONS_FETCH_LIMIT: 99999)与 query(可选过滤词)。
  • 排序:列表接口按时间倒序返回(最新交易在前),无显式排序参数。
  • 统计类:按 month/year 定位,部分端点另有 nr_of_months/limit 等窗口参数(缺失时返回 "\"limit\" is required" 之类的参数缺失错误)。

六、各 API 域详解(全链路实测)

6.1 Accounts 域(账户管理)★

创建账户(POST /accounts/)。账户类型 7 选 1:CHEAC 支票/SAVAC 储蓄/INVAC 投资/CREAC 信用/OTHAC 其他/WALLET 钱包/MEALAC 餐补卡;状态取 Ativa(活跃)或 Inativa(停用,源自葡萄牙语):

curl -s -H 'Content-Type: application/json' -H "sessionkey:$SK" -H "authusername:admin" \
  -X POST http://localhost:8151/accounts/ \
  -d '{"name":"Living Checking","type":"CHEAC","description":"Daily spending","status":"Ativa","exclude_from_budgets":false,"current_balance":2500.00,"color_gradient":"#2E86AB"}'

实测回显:

"Account successfully created"

读取全部账户(GET /accounts/,含实时余额):

curl -s -H "sessionkey:$SK" -H "authusername:admin" http://localhost:8151/accounts/

实测回显:

[{"account_id":1,"name":"Living Checking","type":"CHEAC","description":"Daily spending","status":"Ativa","color_gradient":"#2E86AB","exclude_from_budgets":0,"balance":"2500","users_user_id":1},{"account_id":2,"name":"Cash Wallet","type":"WALLET","description":"","status":"Ativa","color_gradient":null,"exclude_from_budgets":0,"balance":"150","users_user_id":1}]

余额快照(GET /accounts/stats/balance-snapshots/?nr_of_months=6):

[{"month":10,"year":2026,"account_snapshots":[{"account_id":1,"balance":0},{"account_id":2,"balance":0}]}]

更新(PUT /accounts/,body 含 account_id + 变更字段)、删除(DELETE /accounts/,body {"account_id":1})、全量重算余额(GET /accounts/recalculate-balance/all)同域可组合使用。

6.2 Categories 与 Entities 域(分类/实体)★

创建分类(POST /cats/;color_gradient 必填色值,exclude_from_budgets 必填布尔):

curl -s -H 'Content-Type: application/json' -H "sessionkey:$SK" -H "authusername:admin" \
  -X POST http://localhost:8151/cats/ \
  -d '{"name":"Groceries","description":"Food at home","color_gradient":"#6A9F58","status":"Ativa","exclude_from_budgets":false}'

实测回显:

"Category successfully created!"

创建实体(POST /entities/,仅需名称):

curl -s -H 'Content-Type: application/json' -H "sessionkey:$SK" -H "authusername:admin" \
  -X POST http://localhost:8151/entities/ -d '{"name":"Corner Market"}'

实测回显:

"Entity successfully created!"

聚合读取分类+实体+标签(GET /user/categoriesEntitiesTags):

{"categories":[{"category_id":1,"name":"Groceries","type":"M","icon_key":"category"}],"entities":[{"entity_id":1,"name":"Corner Market"}],"tags":[]}

6.3 Transactions 域(交易记账)★

第一步 step0:POST /trxs/step0 返回记账表单所需的实体/分类/账户下拉数据(实测节选):

{"entities":[{"entity_id":1,"name":"Corner Market","users_user_id":1}],"categories":[{"category_id":1,"name":"Groceries",...,"status":"Ativa","exclude_from_budgets":0}],"accounts":[{"account_id":1,"name":"Living Checking",...}]}

第二步 step1:POST /trxs/step1 创建交易。交易类型三选一:E 支出(需 account_from_id)、I 收入(需 account_to_id)、T 转账(from/to 都要);date_timestamp 为 Unix 秒;tags 必须传 JSON 字符串(如 "[]"),传数组会得到 {"message":"Unexpected end of JSON input"}(服务端对 tags 做 JSON.parse,实测踩坑记录):

curl -s -H 'Content-Type: application/json' -H "sessionkey:$SK" -H "authusername:admin" \
  -X POST http://localhost:8151/trxs/step1 \
  -d '{"amount":42.50,"type":"E","description":"Weekly groceries","entity_id":1,"category_id":1,"account_from_id":1,"date_timestamp":1792788500,"is_essential":false,"tags":"[]"}'

实测回显:

"Transaction successfully created"

读取交易列表(GET /trxs/?trx_limit=300):

[{"transaction_id":1,"date_timestamp":1792788500,"amount":"42.5","type":"E","is_essential":0,"description":"Weekly groceries","entity_id":1,"entity_name":"Corner Market","categories_category_id":1,"category_name":"Groceries","accounts_account_from_id":1,"account_to_name":null,"accounts_account_to_id":null,"account_from_name":"Living Checking"}]

⚠️ 排障实录:category_id 引用不存在的分类时,服务端返回 {"message":"\nInvalid \prisma.transactions.create()` invocation:\n\nForeign key constraint violated on the fields: (`categories_category_id`)"}`(外键约束,实测记录)。创建交易前先用 step0 的返回确认分类/实体/账户 ID。

其余:更新 PUT /trxs/(字段前缀 new_,支持 split_* 拆分组)、删除 DELETE /trxs/(body {"transaction_id":1})、按分类按月查询 GET /trxs/inMonthAndCategory?month=10&year=2026&cat_id=1&type=E、自动分类建议 POST /trxs/auto-cat-trx、CSV 批量导入三步 POST /trxs/import/step0|step1|step2。

6.4 Budgets 域(逐月预算)★

step0 取历史均值(POST /budgets/step0,body {"month":10,"year":2026},实测节选)——为每个分类返回四个参考均值:

{"categories":[{"category_id":1,"name":"Groceries",...,"avg_previous_month_credit":0,"avg_previous_month_debit":0,"avg_same_month_previous_year_credit":0,"avg_same_month_previous_year_debit":0,"avg_12_months_credit":0,"avg_12_months_debit":0,"avg_lifetime_credit":0,"avg_lifetime_debit":0}],"initial_balance":"-"}

step1 创建预算(POST /budgets/step1):cat_values_arr 为 JSON 字符串数组,元素含 category_id 与可选 planned_value_debit(计划支出)/planned_value_credit(计划收入):

curl -s -H 'Content-Type: application/json' -H "sessionkey:$SK" -H "authusername:admin" \
  -X POST http://localhost:8151/budgets/step1 \
  -d '{"month":10,"year":2026,"observations":"October plan","cat_values_arr":"[{\"category_id\":1,\"planned_value_debit\":600},{\"category_id\":2,\"planned_value_credit\":3500}]"}'

实测回显:

{"budget_id":2}

⚠️ 排障实录:cat_values_arr 引用不存在的分类 ID 时,整个创建事务回滚,返回 {"message":"The requested resource could not be found."}(HTTP 404,实测记录)——先建分类再做预算。

读取单预算(GET /budgets/2,实测节选):

{"observations":"October plan","month":10,"year":2026,"is_open":true,"initial_balance":0,"categories":[{"category_id":1,"name":"Groceries",...,"planned_amount_credit":0,"planned_amount_debit":600,"current_amount_debit":42.5,...}]}

预算列表(GET /budgets/,含储蓄率):

[{"budget_id":2,"month":10,"year":2026,"observations":"October plan","is_open":true,"initial_balance":null,"users_user_id":1,"balance_value":2900,"balance_change_percentage":"NaN","credit_amount":3200,"debit_amount":42.5,"savings_rate_percentage":90.625}]

其余:整体更新 PUT /budgets/(同 step1 结构 + budget_id)、单分类计划值 PUT /budgets/:id(body {"category_id":1,"planned_expense":600})、关闭/打开 PUT /budgets/status、删除 DELETE /budgets/(body {"budget_id":3},实测 "Budget was successfully removed.")、预算矩阵 GET /budgets/matrix?month=10&year=2026&budget_ids=2(budget_ids 必填,实测缺省报 "\"budget_ids\" is required")。

6.5 Stats 域(统计)★

月度收支分布(GET /stats/dashboard/month-expenses-income-distribution?month=10&year=2026,实测节选):

{"last_update_timestamp":1792788507,"categories":[{...,"name":"Groceries","planned_amount_debit":"600","current_amount_debit":42.5},{"name":"Salary","planned_amount_credit":"3500","current_amount_credit":3200}]}

用户计数统计(GET /stats/userStats):

{"nr_of_trx":2,"nr_of_entities":1,"nr_of_categories":2,"nr_of_accounts":2,"nr_of_budgets":1,"nr_of_rules":0,"nr_of_tags":0,"nr_of_goals":0}

逐月余额(GET /stats/dashboard/month-by-month?month=10&year=2026&limit=6,limit 必填):

[{"month":10,"year":2026,"balance_value":3157.5},{"month":9,"year":2026,"balance_value":0},{"month":8,"year":2026,"balance_value":0},{"month":7,"year":2026,"balance_value":0},{"month":6,"year":2026,"balance_value":0},{"month":5,"year":2026,"balance_value":0}]

其余统计:月度资产预测 GET /stats/stats/monthly-patrimony-projections、分类支出/收入演变、逐年收支分布。

6.6 User Data 域(备份/恢复/币种)★

用户级备份(GET /user/backup,实测节选——apiVersion 标识 + 完整账本 JSON):

{"apiVersion":"5.5.0","accounts":[{"account_id":1,"name":"Living Checking","type":"CHEAC",...,"current_balance":315750,...}]}

💡 说明:备份接口金额为整数分值(315750 = 3157.50),与列表接口的字符串元值("3157.5")不同,消费时注意换算。

变更本位币(PUT /users/changeCurrency,参数名是 currency;实测传 new_currency 报 "\"currency\" is required"):

curl -s -H 'Content-Type: application/json' -H "sessionkey:$SK" -H "authusername:admin" \
  -X PUT http://localhost:8151/users/changeCurrency -d '{"currency":"EUR"}'

实测回显:

"Currency was successfully updated"

恢复备份 PUT /user/restore(body 为备份 JSON,此端点放宽 body 上限至 100MB——源码 app.ts 为其单独挂载大 payload 解析,实查)。

七、错误码与排障手册(实测汇总)

现象实测触发条件处理
{"message":"\"email\" must be a valid email"}初始化/注册用 .local 等非法域邮箱改用合法域名邮箱
{"message":"Unexpected end of JSON input"}trxs step1 的 tags 传了数组而非 JSON 字符串tags 传 "[]" 形式字符串
Foreign key constraint violated on the fields: (categories_category_id)交易引用不存在的分类/实体/账户先 step0 取真实 ID
{"message":"The requested resource could not be found."}(404)预算 cat_values_arr 引用不存在的分类先建分类再做预算
HTTP 401sessionkey 无效/过期重新 POST /auth/ 登录
HTTP 406已有用户时再调 POST /setup/init实例已初始化,走登录或注册
"\"limit\" is required" / "\"budget_ids\" is required"month-by-month 缺 limit、matrix 缺 budget_ids按第五节补全必填查询参数
You have exceeded your 100 requests per minute limit.单 IP 每分钟请求超 100降低请求节奏,窗口自动重置
{"message":"\nInvalid \prisma.users.findUnique()` invocation...username: undefined"}`缺 authusername 请求头业务请求带全 sessionkey + authusername 双头

八、常见问题

Q1:有没有 Swagger/OpenAPI 文档? 源码内置 OpenAPI 文档生成(src/docs/openApi.ts),但 Swagger 界面仅在 NODE_ENV=development 模式挂载(生产镜像不暴露)。生产环境建议以本文路由表为索引、按域实测调试。

Q2:sessionkey 会过期吗? 会。Web 会话 30 分钟滑动窗口(每次有效请求自动续期),脚本长跑建议每批请求后校验返回码,401 即重登。

Q3:能绕过认证做调试吗? 可以临时 BYPASS_SESSION_CHECK=true(跳过全部会话校验),但这等于关闭鉴权,仅限隔离环境调试,用完立即改回并重建容器。

Q4:Android 客户端走的是同一套 API 吗? 是。同一 REST API,登录时服务端为移动端单独维护 sessionkey_mobile(30 天有效期),请求头加 mobile: true 即走移动会话(源码实查)。

Q5:批量导入 CSV 的三步接口怎么串? POST /trxs/import/step0(返回账户清单)→ POST /trxs/import/step1(body account_id + trx_list,做规则预匹配)→ POST /trxs/import/step2(确认入库)。与 Web 界面「批量导入」向导一一对应。

九、参考链接