VoucherVault 1.30.3 API 使用详解
VoucherVault v1.30.3 REST API 实测详解:唯一统计端点 /api/get/stats 的 Bearer Token 认证、完整响应字段口径、按用户过滤、Home Assistant 接入示例与排障实录(含 v1.30.3 未知用户 500 bug)。
VoucherVault 1.30.3 API 使用详解
目标:讲清 VoucherVault v1.30.3 REST API 的能力边界、认证方式、调用方法与响应字段,并给出 Home Assistant 接入示例。全部请求与回显来自 2026-10-25 的真实环境(安装方式见《Ubuntu 22.04 安装 VoucherVault 指南》)。
一、环境信息
| 项目 | 值 |
|---|---|
| 软件版本 | VoucherVault v1.30.3(Django 5.2.18) |
| API 基础路径 | http://<host>:<port>/en/api/(语言前缀 /en/ 不可省略) |
| 认证方式 | HTTP Header Authorization: Bearer <api_token> |
| 端点数量 | 1 个(只读统计端点,详见下文) |
| 数据格式 | JSON(Content-Type: application/json) |
二、能力边界:先看清楚能做什么
VoucherVault 的 API 只有一个端点:
GET /en/api/get/stats
它返回库内卡券的聚合统计与明细,设计目标是给 Home Assistant 等外部仪表盘做数据展示。没有卡券的增删改查写接口,也没有用户管理接口——所有写操作必须在 Web 界面完成。需要完整读写 API 的场景请评估 OfferKit 类促销引擎(对比见《VoucherVault 介绍与选型指南》)。
三、获取与管理 API Token
Token 存于数据库 AppSettings 单例模型的 api_token 字段(UUID 格式),不是用户属性,全实例共用一个 Token。
方式一:Django 管理后台查看/重新生成(推荐)
- 用 admin 账号登录
http://<host>:<port>/en/admin/; - 进入「MYAPP → App settings」;
- 列表页可见当前 Token,并有「Regenerate token」操作可一键轮换(轮换后旧 Token 立即失效)。
方式二:命令行查看(服务器本地排障用)
裸机形态:
cd /opt/vouchervault/app
runuser -u www-data -- /opt/vouchervault/venv/bin/python manage.py shell -c \
"from myapp.models import AppSettings; print(AppSettings.objects.last().api_token)"
f4597b50-79d6-43e6-9101-c5d0bf5bfda2
⚠️ 若查询结果为
None(新装实例从未保存过 App settings),说明 Token 尚未生成。登录管理后台打开一次 App settings 保存即可,或用 shell 执行AppSettings.objects.create()触发默认 UUID 生成。
四、调用实录
以下示例 Token 为 f4597b50-79d6-43e6-9101-c5d0bf5bfda2(测试实例,环境已销毁)。
4.1 不带 Token → 403
curl -s -w '\nHTTP %{http_code}\n' http://127.0.0.1:8000/en/api/get/stats
Unauthorized. Invalid or missing authorization token.
HTTP 403
4.2 错误 Token → 403
curl -s -w '\nHTTP %{http_code}\n' \
-H 'Authorization: Bearer wrong-token-123' \
http://127.0.0.1:8000/en/api/get/stats
Unauthorized. Invalid or missing authorization token.
HTTP 403
403 响应体是纯文本,不是 JSON——客户端做错误处理时注意区分。
4.3 正确 Token → 200
测试库中预置了 4 张卡券(代金券/礼品卡/优惠券/会员卡各一,礼品卡含两笔消费记录):
curl -s -H 'Authorization: Bearer f4597b50-79d6-43e6-9101-c5d0bf5bfda2' \
http://127.0.0.1:8000/en/api/get/stats | python3 -m json.tool
{
"item_stats": {
"total_items": 4,
"total_value": "139.50",
"vouchers": 1,
"giftcards": 1,
"coupons": 1,
"loyaltycards": 1,
"used_items": 0,
"available_items": 4,
"expired_items": 0,
"soon_expiring_items": 1
},
"item_details": [
{
"id": "544b4194-ddba-4ba5-9a97-509ee004e759",
"type": "voucher",
"name": "Amazon Gift Voucher",
"redeem_code": "AMZ-2026-ABCD-EFGH",
"code_type": "qrcode",
"pin": null,
"issuer": "Amazon",
"value": "50.00",
"value_type": "money",
"currency": "EUR",
"issue_date": "2026-10-25",
"expiry_date": "2027-01-23",
"description": "Birthday gift card",
"is_used": false,
"is_pinned": false,
"user__username": "admin"
},
{
"id": "35d32bf0-54da-49d2-875a-803602db7f53",
"type": "giftcard",
"name": "Starbucks Card",
"redeem_code": "SBUX-1234-5678",
"code_type": "barcode",
"pin": null,
"issuer": "Starbucks",
"value": "100.00",
"value_type": "money",
"currency": "EUR",
"issue_date": "2026-10-25",
"expiry_date": "2027-04-23",
"description": "Coffee prepaid card",
"is_used": false,
"is_pinned": false,
"user__username": "admin"
}
],
"issuer_stats": [
{"issuer": "Starbucks", "count": 1, "total_value": "79.50"},
{"issuer": "LocalMart", "count": 1, "total_value": "10.00"},
{"issuer": "IKEA", "count": 1, "total_value": "0.00"},
{"issuer": "Amazon", "count": 1, "total_value": "50.00"}
],
"user_stats": {
"total_users": 1,
"active_users": 1,
"disabled_users": 0,
"superusers": 1,
"staff_members": 1
}
}
(为节省篇幅,item_details 只展示了前 2 条,实际返回全部 4 条。)
4.4 按用户过滤
?user=<username> 只统计某个用户的卡券:
curl -s -H 'Authorization: Bearer f4597b50-79d6-43e6-9101-c5d0bf5bfda2' \
'http://127.0.0.1:8000/en/api/get/stats?user=admin'
返回结构相同,数据集收缩到该用户名下。
五、响应字段详解
5.1 item_stats(聚合统计)
| 字段 | 含义 | 计算口径(源码核对) |
|---|---|---|
total_items | 卡券总数 | 全量计数 |
total_value | 有效卡券净值合计 | 仅统计「未使用且未过期」:面值 + 交易流水合计(消费为负数) |
vouchers / giftcards / coupons / loyaltycards | 四类卡券各自数量 | 按 type 分组计数 |
used_items | 已核销数量 | is_used=True |
available_items | 可用数量 | 未使用 − 已过期 |
expired_items | 已过期数量 | expiry_date < now |
soon_expiring_items | 临期数量 | 未过期但距到期 < EXPIRY_THRESHOLD_DAYS(默认 30 天,配置见《VoucherVault 配置参数详解》) |
实测验证净值算法:Starbucks 卡面值 100.00,两笔消费 −12.50、−8.00,净值 79.50;合计 50.00 + 79.50 + 10.00 + 0.00 = 139.50,与返回一致。
5.2 item_details(卡券明细)
| 字段 | 含义 |
|---|---|
id | 卡券 UUID |
type | voucher / giftcard / coupon / loyaltycard |
redeem_code / code_type / pin | 核销码、码制(qrcode/barcode/code128 等)、PIN |
issuer | 发行方 |
value / value_type / currency | 面值、面值类型(money/percentage/multiplier)、币种 |
issue_date / expiry_date | 发行/到期日期(ISO 格式) |
is_used / is_pinned | 是否已核销、是否置顶 |
user__username | 属主用户名(Django ORM 双下划线字段直接外暴露) |
⚠️ 注意:redeem_code 明文出现在响应里,该 API 等同于卡券数据导出通道,Token 必须按敏感凭据管理。
5.3 issuer_stats(发行方聚合)
按 issuer 分组的 count 与 total_value(同样含交易净值口径)。无固定排序(实测返回顺序与字母序无关),消费方如需排序自行处理。
5.4 user_stats(用户聚合)
实例级用户计数:总数、激活数、禁用数、超户数、staff 数。与 ?user= 过滤无关,始终返回全实例数据。
六、接入 Home Assistant
官方设计场景就是把统计挂到 HA 仪表盘。在 configuration.yaml 中配置 RESTful Sensor:
rest:
- resource: http://192.168.1.10:8130/en/api/get/stats
headers:
Authorization: "Bearer f4597b50-79d6-43e6-9101-c5d0bf5bfda2"
scan_interval: 3600
sensor:
- name: "VoucherVault 临期卡券"
value_template: "{{ value_json.item_stats.soon_expiring_items }}"
unit_of_measurement: "张"
json_attributes_path: "$.item_stats"
json_attributes:
- total_items
- total_value
- available_items
- expired_items
要点:
scan_interval建议 3600 秒,到期提醒本就是天级粒度;- 更多维度(如按发行方)可用
template传感器解析json_attributes; - 生产环境务必走 HTTPS,避免 Token 与核销码明文过网。
七、排障实录
1. ?user= 传了不存在的用户名,返回 500 而非预期的 404
实测回显:
{"error": "An unexpected error occurred.", "details": "name 'ObjectDoesNotExist' is not defined"}
HTTP 500
这是 v1.30.3 的真实 bug:views.py 的 get_stats 里 except ObjectDoesNotExist: 捕获了一个从未 import 的异常类,导致用户不存在时抛 NameError 走通用 500 分支。对接侧按「500 且 details 含 ObjectDoesNotExist 即用户不存在」兼容处理,或等待上游修复。
2. 请求路径少了语言前缀返回 404
端点注册在 i18n_patterns 内,/api/get/stats 不带 /en/ 会 404。固定写 /en/api/get/stats 即可(不接受 Accept-Language 协商)。
3. Token 复制后仍 403
确认 Header 格式严格为 Authorization: Bearer <uuid>(Bearer 与 Token 之间一个空格);确认没有在管理后台误点 Regenerate token 导致 Token 已轮换。
4. total_value 与卡券面值之和对不上
total_value 是净值:扣除了礼品卡交易流水,且不含已使用与已过期的卡券。对账时先按 item_details 逐条筛 is_used=false 且未过期再加总。
八、参考链接
- 官方仓库(API 源码
myapp/views.py::get_stats):https://github.com/l4rm4nd/VoucherVault (查询日期 2026-10-25) - 站内:《VoucherVault 介绍与选型指南》
- 站内:《Ubuntu 22.04 安装 VoucherVault 指南》
- 站内:《VoucherVault Docker Compose 部署指南》
- 站内:《VoucherVault 配置参数详解》