MyFin Budget API 使用详解
MyFin Budget(myfin-api 5.5.0)REST API 使用详解:认证会话机制、17 个端点域全路由表、账户/分类/实体/交易/预算/统计全链路 curl 实测回显、分页过滤约定与错误码手册,全部请求与响应为真实实测记录。
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 ★ | /setup | POST /init(实例初始化,仅无用户时可用) |
| Auth ★ | /auth | POST /(登录);POST /recovery/sendOtp;POST /recovery/setNewPassword |
| Session | /validity | POST /(校验会话有效性) |
| Users ★ | /users | POST /(注册);PUT /changePW/;PUT /changeCurrency;POST /demo/(灌演示数据) |
| User Data ★ | /user | GET /categoriesEntitiesTags;GET /backup;PUT /restore |
| Accounts ★ | /accounts | POST /;GET /;PUT /;DELETE /;GET /stats/balance-snapshots/;GET /recalculate-balance/all |
| Categories ★ | /cats | GET /;POST /;PUT /;DELETE / |
| Entities ★ | /entities | GET /;POST /;PUT /;DELETE / |
| Tags | /tags | GET /filteredByPage/:page;POST /;PUT /:id;DELETE /:id |
| Transactions ★ | /trxs | GET /;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 ★ | /budgets | GET /;GET /filteredByPage/:page;GET /list/summary;GET /:id;GET /matrix;POST /step0;POST /step1;PUT /;PUT /:id;PUT /:id/description;PUT /status;DELETE / |
| Rules | /rules | GET /;POST /;PUT /;DELETE / |
| Goals | /goals | GET /;POST /;PUT /:id;DELETE /:id |
| Stats ★ | /stats | GET /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/assets | GET /;GET /summary;GET /stats;POST /;PUT /:id;PUT /:id/value;DELETE /:id;DELETE /:id/value/:year/:month |
| Invest Transactions | /invest/trx | GET /;GET /filteredByPage/:page;POST /;PUT /:id;DELETE /:id |
| Invest Reports | /invest/reports | GET /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 401 | sessionkey 无效/过期 | 重新 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 界面「批量导入」向导一一对应。
九、参考链接
- myfin-api 源码与路由定义(src/routes/routeDefinitions.ts):https://github.com/afaneca/myfin-api
- 官方 Releases(5.5.0):https://github.com/afaneca/myfin-api/releases
- Android 客户端(API 消费参考实现):https://github.com/afaneca/myfin-android
- 站内延伸:《MyFin Budget 介绍与选型指南》、《MyFin Budget Docker Compose 部署指南》、《MyFin Budget 配置参数详解》