OpenBudgeteer 1.11.2 API 使用详解

OpenBudgeteer 1.11.2 独立 REST API 实战:Scalar/OpenAPI 3.0.1 入口、8 资源域 60+ 端点清单、curl 与 Python 全流程实测、错误语义与幂等等行为细节(2026-10-24 实测)。

OpenBudgeteer财务记账自托管安装部署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(OpenAPI info.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

二、前置条件

  1. API 服务已随部署栈启动(Compose 编排见部署指南 4.1 节;若未部署 API 服务,单独补启即可,配置与 Web 相同的数据库四要素);
  2. 验证服务可达:
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8082/openapi/v1.1/openapi.json

实测回显:

200
  1. 准备好一个 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 格式非法(路由不匹配)404curl -s -o /dev/null -w '%{http_code}' http://localhost:8082/account/not-a-guid
实体不存在(GUID 合法但查无此资源)500curl -s -o /dev/null -w '%{http_code}' http://localhost:8082/account/00000000-0000-0000-0000-0000000000aa
缺少必需查询参数400curl -s -o /dev/null -w '%{http_code}' http://localhost:8082/group
成功创建/更新/删除200(空响应体)各 POST/PATCH/DELETE 示例

客户端封装建议:把"查无资源返回 500"处理成业务上的 404 语义;删除类操作的 200 与创建类一样无响应体,确认删除成功靠再查一次。

7.2 三个容易误读的行为

  1. /bucket/inOut/{id} 的 balance 恒为 null:当期投入/产出端点不计算累计余额,余额请用 /bucket/figures/{id} 或 /bucket/balance/{id};
  2. figures 与 inOut 的统计口径不同:figures 是桶全周期累计(实测 balance 109.14 含历史结转),inOut 只统计当期(input 100.00 为当月分配额);
  3. 删除账户会级联删交易: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 服务无鉴权(见"三、认证与鉴权"),务必在网络层(内网绑定/反代认证)兜底。

十、参考链接