OpenBudgeteer 1.11.2 API 使用详解
OpenBudgeteer 1.11.2 独立 REST API 实战:Scalar/OpenAPI 3.0.1 入口、8 资源域 60+ 端点清单、curl 与 Python 全流程实测、错误语义与幂等等行为细节(2026-10-24 实测)。
OpenBudgeteer 1.11.2 API 使用详解
目标:面向想把 OpenBudgeteer 纳入自动化流程的读者,讲清独立 API 服务的部署形态、OpenAPI 文档获取方式、8 个资源域共 60+ 端点的用法、认证现状与错误语义,附可直接复用的 curl 与 Python 示例。本文全部端点行为与回显均在 1.11.2 实测(2026-10-24),无法实测处标注官方文档溯源。
一、环境信息
- 软件版本:OpenBudgeteer 1.11.2(2026-10-23 发布)
- API 服务形态:独立服务
axelander/openbudgeteer-api(与 Web 前端分离部署,共享同一个数据库),API 版本 1.1.0(OpenAPIinfo.version实测) - 接口规范:OpenAPI 3.0.1
- 接口文档 UI:Scalar(
/api端点);机器可读描述文件:/openapi/v1.1/openapi.json - 服务端口:应用内监听 8080(Compose 部署建议映射宿主 8082,见《OpenBudgeteer Docker Compose 部署指南》)
- 实测环境:Web + API + MariaDB 11.8 + Redis 8.2,
APPSETTINGS_DEMO_DATA=true演示数据集;全部 curl 实测于 2026-10-24
二、前置条件
- API 服务已随部署栈启动(Compose 编排见部署指南 4.1 节;若未部署 API 服务,单独补启即可,配置与 Web 相同的数据库四要素);
- 验证服务可达:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8082/openapi/v1.1/openapi.json
实测回显:
200
- 准备好一个 API 测试用客户端(curl 或任意 HTTP 客户端)。
三、认证与鉴权
实测结论:1.11.2 的 API 服务当前没有任何鉴权——不带任何凭据直接调用全部端点均返回 200(本文所有实测即裸调用)。
背景:v1.11 给 Web 前端增加了认证功能(官方 Release Notes:"Add Authentication for Web App"),但该能力未覆盖 API 服务,API 文档(Scalar/OpenAPI 描述)中也没有 securityScheme 定义。
生产建议:
- API 服务只绑定内网地址,或经反向代理(nginx 等)加一层 Basic Auth/IP 白名单后再暴露;
- 与 Web 前端不同,API 服务的数据库账号只要写权限,无需 root(
CONNECTION_ROOT_PASSWORD不适用于 API 服务)。
四、API 基础信息
4.1 接口文档与描述文件
| 入口 | 地址 | 说明 |
|---|---|---|
| Scalar 交互文档 | http://<服务器IP>:8082/api | 浏览器打开,可在线发请求(实测 HTTP 302 → /api/ 渲染 Scalar UI) |
| OpenAPI 描述文件 | http://<服务器IP>:8082/openapi/v1.1/openapi.json | 机器可读,用于生成客户端代码 |
⚠️ 版本差异提示:官方 API 文档页仍写 "The documentation is accessible via Swagger UI using the entpoint
/swagger",但 1.11.2 实测/swagger/index.html返回 404——v1.10 起文档 UI 已从 Swagger 换成 Scalar(Release Notes:"API: Replace Swagger with Scalar"),入口以本文实测的/api为准。
4.2 通用约定
- 基址:
http://<服务器IP>:8082,纯 JSON 接口(Content-Type: application/json,/movement/balance与/bucket/balance/{id}两个端点直接返回纯数字); - 资源主键:全部为 GUID(如
08df3171-f0f8-4ae3-89f8-acd094c203e6); - 日期格式:
yyyy-MM-dd(如2026-10-24),月份参数用yyyy-MM-dd(如2026-10-01); - 列表端点无分页:全部一次性返回 JSON 数组,数据量大时注意响应体体积;
- 金额:十进制度量,序列化保留两位小数(支出为负数、收入为正数);
- 无幂等保证:重复 POST 同一载荷会创建多条记录(实测对
/movement重复 POST 产生两条 movement)。
五、端点清单
按 8 个资源域整理(60+ 端点全量见 /openapi/v1.1/openapi.json;下表为高频核心端点,{id} 均为 GUID 路径参数):
账户 Account:
| 方法与路径 | 说明 |
|---|---|
GET /account | 全部账户(isActive 1/0 表示启用/停用) |
POST /account | 创建账户 |
GET /account/{id} | 单账户 |
PATCH /account | 更新账户(body 带 id) |
DELETE /account/{id} | 删除账户(级联删除其名下交易,实测) |
交易 Transaction:
| 方法与路径 | 说明 |
|---|---|
GET /transaction | 全部交易 |
POST /transaction | 创建交易(不带预算分配) |
GET /transaction/{id} | 单交易 |
GET /transaction/fromAccount/{id} | 按账户查交易 |
GET /transaction/withEntities/{id} | 单交易 + 关联实体(account 展开、budgetedTransactions 展开) |
POST /transaction/withBucket | 创建交易 + 同请求完成桶预算分配(最常用) |
DELETE /transaction/{id} | 删除交易 |
预算桶 Bucket:
| 方法与路径 | 说明 |
|---|---|
GET /bucket | 全部桶(含系统桶) |
GET /bucket/withoutSystemBuckets | 用户桶(v1.9 新增端点) |
GET /bucket/systemBuckets | 系统桶(固定 GUID:…0001 Income、…0002 Transfer) |
GET /bucket/{id} | 单桶 |
POST /bucket / PATCH /bucket | 创建 / 更新 |
GET /bucket/figures/{id} | 桶统计(余额 + 当期 input/output) |
GET /bucket/balance/{id} | 桶余额(纯数字) |
GET /bucket/inOut/{id} | 当期投入/支出(注意 balance 字段返回 null,见 7.2) |
DELETE /bucket/{id} | 删除桶 |
桶移动 Movement(按月把钱分进桶):
| 方法与路径 | 说明 |
|---|---|
GET /movement | 全部移动记录(可选 periodStart/periodEnd 过滤) |
POST /movement | 创建(即"给桶分配预算") |
GET /movement/fromBucket/{id} | 按桶查移动 |
DELETE /movement/{id} | 删除移动 |
桶分组 Group:
| 方法与路径 | 说明 |
|---|---|
GET /group | 桶组 + 组内桶(必带 full=true 查询参数,实测缺参返回 400) |
POST /group / PATCH /group/move/{id} / DELETE /group/{id} | 建 / 调序 / 删 |
规则 Rule Set:GET /rules、POST /rules、GET /rules/evaluate?transactionId=…、DELETE /rules/{id} 等(用于 CSV 导入的 Payee/桶自动映射)。
周期交易 Recurring:GET /recurring、POST /recurring、GET /recurring/pendingTransactions?yearMonth=2026-10-01(必带 yearMonth 参数)、DELETE /recurring/{id}。
导入 Import:GET /import(导入档案列表)、GET /import/{id}、POST /import(建导入档案)、POST /import/{id}(执行导入)等,CSV 导入向导的 API 化。
六、请求示例(curl 实测)
以下示例全部实测于演示数据环境,回显逐字摘自真实响应。
6.1 账户 CRUD
创建:
curl -s -X POST http://localhost:8082/account \
-H 'Content-Type: application/json' \
-d '{"name":"Cash Wallet","isActive":1}'
实测回显:
{"id":"08df3171-f0f8-4ae3-89f8-acd094c203e6","name":"Cash Wallet","isActive":1}
查询、改名:
curl -s http://localhost:8082/account/08df3171-f0f8-4ae3-89f8-acd094c203e6
{"id":"08df3171-f0f8-4ae3-89f8-acd094c203e6","name":"Cash Wallet","isActive":1}
curl -s -X PATCH http://localhost:8082/account \
-H 'Content-Type: application/json' \
-d '{"id":"08df3171-f0f8-4ae3-89f8-acd094c203e6","name":"Cash Wallet Renamed","isActive":1}'
{"id":"08df3171-f0f8-4ae3-89f8-acd094c203e6","name":"Cash Wallet Renamed","isActive":1}
6.2 交易:创建、查询、删除
创建一笔支出(amount 为负):
curl -s -X POST http://localhost:8082/transaction \
-H 'Content-Type: application/json' \
-d '{"accountId":"08df3171-f0f8-4ae3-89f8-acd094c203e6","transactionDate":"2026-10-24","payee":"Corner Shop","memo":"API test coffee","amount":-23.45}'
实测回显:
{"id":"08df3171-fd84-4d71-8714-3a7069774fc2","accountId":"08df3171-f0f8-4ae3-89f8-acd094c203e6","account":null,"transactionDate":"2026-10-24","payee":"Corner Shop","memo":"API test coffee","amount":-23.45,"budgetedTransactions":null}
按账户查交易:
curl -s http://localhost:8082/transaction/fromAccount/08df3171-f0f8-4ae3-89f8-acd094c203e6
[{"id":"08df3171-fd84-4d71-8714-3a7069774fc2","accountId":"08df3171-f0f8-4ae3-89f8-acd094c203e6","account":{"id":"08df3171-f0f8-4ae3-89f8-acd094c203e6","name":"Cash Wallet Renamed","isActive":1},"transactionDate":"2026-10-24","payee":"Corner Shop","memo":"API test coffee","amount":-23.45,"budgetedTransactions":null}]
创建交易并分配预算桶(一条请求完成记账 + 归桶,budgetedTransactions[].amount 为从该桶扣减的金额):
curl -s -X POST http://localhost:8082/transaction/withBucket \
-H 'Content-Type: application/json' \
-d '{"accountId":"08df3171-7a76-4713-8245-ebe2bc317c2f","transactionDate":"2026-10-24","payee":"Green Market","memo":"weekly groceries","amount":-42.10,"budgetedTransactions":[{"bucketId":"08df3171-7a81-47c5-8f46-bde056caa354","amount":-42.10}]}'
实测回显(注意 budgetedTransactions 已带 transactionId 回填):
{"id":"08df3172-700c-4b1b-812b-c38e255d1cd8","accountId":"08df3171-7a76-4713-8245-ebe2bc317c2f","account":null,"transactionDate":"2026-10-24","payee":"Green Market","memo":"weekly groceries","amount":-42.10,"budgetedTransactions":[{"id":"08df3172-700c-4f50-89b4-d2dee8dfdfe9","transactionId":"08df3172-700c-4b1b-812b-c38e255d1cd8","bucketId":"08df3171-7a81-47c5-8f46-bde056caa354","amount":-42.10}]}
6.3 预算桶:创建与统计
创建桶(bucketGroupId 为桶组 GUID;currentVersion.bucketType=1 配合 bucketTypeXParam=100 表示"每月 100 元"类的周期性支出桶类型参数):
curl -s -X POST http://localhost:8082/bucket \
-H 'Content-Type: application/json' \
-d '{"name":"Vacation Fund","bucketGroupId":"08df3171-7a7d-492b-8529-2eb68c0457c6","validFrom":"2026-10-01","isInactive":false,"isInactiveFrom":"0001-01-01","currentVersion":{"bucketType":1,"bucketTypeXParam":100,"notes":"monthly expense","validFrom":"2026-10-01"},"isHiddenFromSummaries":false}'
实测回显(节选,服务端自动生成 id 与首版 currentVersion.id、version:1):
{"id":"08df3172-44f7-4ba5-871e-48cba25e12c6","name":"Vacation Fund","bucketGroupId":"08df3171-7a7d-492b-8529-2eb68c0457c6","colorCode":null,"textColorCode":null,"validFrom":"2026-10-01","isInactive":false,"isInactiveFrom":"0001-01-01","currentVersion":{"id":"08df3172-44f8-44c3-8acf-3f1b559ddf15","bucketId":"08df3172-44f7-4ba5-871e-48cba25e12c6","version":1,"bucketType":1,"bucketTypeXParam":100,"...}}
桶统计三件套(以演示数据中的桶为例):
curl -s http://localhost:8082/bucket/figures/08df3171-7a81-47d5-8469-8bb12455df16
{"balance":109.14,"input":100.00,"output":0}
curl -s http://localhost:8082/bucket/balance/08df3171-7a81-47d5-8469-8bb12455df16
109.14
curl -s http://localhost:8082/bucket/inOut/08df3171-7a81-47d5-8469-8bb12455df16
{"balance":null,"input":100.00,"output":0}
6.4 桶移动(按月分配预算)
curl -s -X POST http://localhost:8082/movement \
-H 'Content-Type: application/json' \
-d '{"bucketId":"08df3172-44f7-4ba5-871e-48cba25e12c6","amount":150.00,"movementDate":"2026-10-24"}'
实测回显:
{"id":"08df3172-4dac-41d6-80ad-2caa5981b496","bucketId":"08df3172-44f7-4ba5-871e-48cba25e12c6","amount":150.00,"movementDate":"2026-10-24"}
按桶查移动、确认余额变化:
curl -s http://localhost:8082/movement/fromBucket/08df3172-44f7-4ba5-871e-48cba25e12c6
[{"id":"08df3172-4b2d-47cf-8f42-b4059df03795","bucketId":"08df3172-44f7-4ba5-871e-48cba25e12c6","amount":150.00,"movementDate":"2026-10-24"},{"id":"08df3172-4dac-41d6-80ad-2caa5981b496","bucketId":"08df3172-44f7-4ba5-871e-48cba25e12c6","amount":150.00,"movementDate":"2026-10-24"}]
curl -s http://localhost:8082/bucket/figures/08df3172-44f7-4ba5-871e-48cba25e12c6
{"balance":300.00,"input":150.00,"output":0}
(两条 150.00 的 movement 是因为该示例 POST 实际执行了两次——API 对重复提交不做幂等去重,余额随之累加到 300.00,这正是"无幂等保证"的实证。)
6.5 桶组与周期交易
桶组(缺 full 参数会被拒):
curl -s 'http://localhost:8082/group?full=true'
实测回显(节选,系统组 System 挂着系统桶 Income/Transfer):
[{"id":"00000000-0000-0000-0000-000000000001","name":"System","position":0,"buckets":[{"id":"00000000-0000-0000-0000-000000000001","name":"Income","bucketGroupId":"00000000-0000-0000-0000-000000000001","colorCode":null,"textColorCode":null,"validFrom":"1990-01-01","isInactive":false,"isInactiveFrom":"9999-12-31","currentVersion":null,"...}},"..."]
本月待执行的周期交易:
curl -s 'http://localhost:8082/recurring/pendingTransactions?yearMonth=2026-10-01'
[]
(演示数据集未配周期交易,返回空数组;配了周期交易的库会在此返回当月应生成的交易清单。)
七、错误语义与行为细节(实测)
7.1 错误码语义
实测 1.11.2 的错误处理较为粗糙,响应体一律为空,只能靠状态码判断:
| 场景 | 状态码 | 实测命令 |
|---|---|---|
| GUID 格式非法(路由不匹配) | 404 | curl -s -o /dev/null -w '%{http_code}' http://localhost:8082/account/not-a-guid |
| 实体不存在(GUID 合法但查无此资源) | 500 | curl -s -o /dev/null -w '%{http_code}' http://localhost:8082/account/00000000-0000-0000-0000-0000000000aa |
| 缺少必需查询参数 | 400 | curl -s -o /dev/null -w '%{http_code}' http://localhost:8082/group |
| 成功创建/更新/删除 | 200(空响应体) | 各 POST/PATCH/DELETE 示例 |
客户端封装建议:把"查无资源返回 500"处理成业务上的 404 语义;删除类操作的 200 与创建类一样无响应体,确认删除成功靠再查一次。
7.2 三个容易误读的行为
/bucket/inOut/{id}的balance恒为null:当期投入/产出端点不计算累计余额,余额请用/bucket/figures/{id}或/bucket/balance/{id};figures与inOut的统计口径不同:figures是桶全周期累计(实测 balance 109.14 含历史结转),inOut只统计当期(input 100.00 为当月分配额);- 删除账户会级联删交易:
DELETE /account/{id}返回 200 后,该账户名下的BankTransaction一并消失(实测:删除带 1 笔交易的账户后,GET /transaction/{交易id}由 200 变 500)。批量操作前务必确认账户下无历史交易,或先做数据库备份(备份命令见部署指南第十节)。
八、Python 客户端示例
一个最小可用的封装(仅依赖 requests,覆盖记账主流程):
#!/usr/bin/env python3
"""OpenBudgeteer API 1.1 最小客户端:查账户 -> 记账并归桶 -> 读桶余额"""
import requests
BASE = "http://localhost:8082" # API 服务地址
def get(path):
r = requests.get(f"{BASE}{path}", timeout=10)
r.raise_for_status()
return r.json() if r.text else None
def post(path, payload):
r = requests.post(f"{BASE}{path}", json=payload, timeout=10)
r.raise_for_status() # 注意:错误时 body 为空,只能看状态码
return r.json() if r.text else None
# 1. 找到主账户
accounts = get("/account")
checking = next(a for a in accounts if a["name"] == "Checking account")
# 2. 找到 Groceries 桶
buckets = get("/bucket/withoutSystemBuckets")
groceries = next(b for b in buckets if b["name"] == "Groceries")
# 3. 记一笔并归桶
tx = post("/transaction/withBucket", {
"accountId": checking["id"],
"transactionDate": "2026-10-24",
"payee": "Green Market",
"memo": "weekly groceries",
"amount": -42.10,
"budgetedTransactions": [{"bucketId": groceries["id"], "amount": -42.10}],
})
print("transaction id:", tx["id"])
# 4. 看桶余额
print("groceries balance:", get(f"/bucket/balance/{groceries['id']}"))
要点:所有写操作成功时 r.text 为空串,r.json() 前先判空;异常分支只拿得到状态码(见 7.1)。
九、常见问题
Q1:按官方文档访问 /swagger 返回 404?
1.11.2 已无 Swagger UI。文档入口是 Scalar:http://<服务器IP>:8082/api;OpenAPI 描述文件在 /openapi/v1.1/openapi.json。官方 API 页的 /swagger 表述滞后于 v1.10 的 Scalar 迁移(Release Notes 原文 "API: Replace Swagger with Scalar, accessible via endpoint /api"),以实测为准。
Q2:API 与 Web 界面数据不同步?
不存在这个问题——两者共用同一数据库,API 写入的记录 Web 刷新即见(实测交互验证)。若 Web 页面显示滞后,是 Blazor 页面未刷新(F5 即可),不是数据问题。
Q3:列表接口能分页吗?
不能。所有列表端点一次返回全量数组,OpenAPI 描述中也没有任何分页参数。交易量大的库建议按 periodStart/periodEnd(/movement)或 fromAccount(/transaction)缩小范围。
Q4:重复脚本跑出了双倍预算?
API 无幂等保护(6.4 节的 300.00 就是这么来的)。写操作前先查目标资源是否已存在,或用脚本层去重(例如先 GET /movement/fromBucket/{id} 核对该月是否已有分配)。误写入的 movement 用 DELETE /movement/{id} 清理。
Q5:Web 端 v1.11 的登录功能管得住 API 吗?
管不住。认证只加在 Web 前端,API 服务无鉴权(见"三、认证与鉴权"),务必在网络层(内网绑定/反代认证)兜底。
十、参考链接
- 官方 API 文档页(注意其
/swagger表述已滞后):https://theaxelander.github.io/latest/api/ - 本机 OpenAPI 描述:
http://<服务器IP>:8082/openapi/v1.1/openapi.json - Scalar 在线文档:
http://<服务器IP>:8082/api - GitHub Releases(API 版本演进记录):https://github.com/TheAxelander/OpenBudgeteer/releases
- 站内延伸:《OpenBudgeteer 1.11.2 介绍与选型指南》、《OpenBudgeteer Docker Compose 部署指南》、《OpenBudgeteer 1.11.2 配置参数详解》