Spoolman v0.27.0 API 使用详解
详解 Spoolman v0.27.0 REST API:厂商/耗材/丝盘三级模型端点、设置项特殊 JSON 编码写法、备份导出搜索、WebSocket 事件流与分页过滤排序约定,全部 curl/Python 实测回显。
Spoolman v0.27.0 API 使用详解
目标:全面介绍 Spoolman v0.27.0 REST API 的认证方式、核心端点、请求示例与实测回显,覆盖厂商/耗材/丝盘/设置/备份/导出/搜索及 WebSocket 事件流。已在真实服务器环境实测通过(Spoolman v0.27.0,SQLite 模式,systemd 托管于 7912 端口)。
一、环境信息
- 目标软件:Spoolman v0.27.0(commit
bed3f14) - API 协议:HTTP/1.1 + WebSocket(HTTP Upgrade)
- API 规范:OpenAPI 3.1.0,文档自动生成于
/api/v1/openapi.json - 认证方式:无内置认证,仅支持基于网络位置的访问控制;面向内网或反向代理使用
- 请求工具:curl / Python
urllib.request/ 浏览器开发者工具 - 测试环境:Standalone,systemd 托管,监听
http://localhost:7912
二、前置条件
-
Spoolman 服务已运行:
systemctl is-active Spoolman -
确认 API 可访问:
curl -s http://localhost:7912/api/v1/info | python3 -m json.tool -
基础概念:Spoolman 的数据模型是 Vendor → Filament → Spool 三级。API 围绕这三个实体展开,另有 Setting、Backup、Export、Search 等管理端点。
三、API 总览
| 域 | 端点 | 方法 | 说明 |
|---|---|---|---|
| 信息 | /api/v1/info | GET | 版本、构建信息、数据目录 |
| 健康 | /api/v1/health | GET | 服务健康状态 |
| 厂商 | /api/v1/vendor | GET / POST | 列表 / 创建 |
| 厂商 | /api/v1/vendor/{id} | GET / PATCH / DELETE | 详情 / 修改 / 删除 |
| 耗材 | /api/v1/filament | GET / POST | 列表 / 创建 |
| 耗材 | /api/v1/filament/{id} | GET / PATCH / DELETE | 详情 / 修改 / 删除 |
| 耗材标签 | /api/v1/filament/{id}/tag | POST | 添加标签 |
| 耗材标签 | /api/v1/filament/{id}/tag/{uid} | DELETE | 移除标签 |
| 丝盘 | /api/v1/spool | GET / POST | 列表 / 创建 |
| 丝盘 | /api/v1/spool/{id} | GET / PATCH / DELETE | 详情 / 修改 / 删除 |
| 丝盘分组 | /api/v1/spool/group | GET | 按指定字段分组统计 |
| 丝盘批量 | /api/v1/spool/field/{field} | PATCH | 批量修改指定字段 |
| 丝盘消耗 | /api/v1/spool/{id}/use | PUT | 按长度扣减消耗 |
| 丝盘称重 | /api/v1/spool/{id}/measure | PUT | 按整卷称重校准 |
| 丝盘标签 | /api/v1/spool/{id}/tag | POST / DELETE | 添加 / 移除 NFC 标签 |
| 设置 | /api/v1/setting/ | GET | 读取全部设置项 |
| 设置 | /api/v1/setting/{key} | GET / POST | 读取 / 修改单项设置 |
| 自定义字段 | /api/v1/field/{entity_type} | GET | 获取某实体的自定义字段 |
| 字段取值 | /api/v1/field/{entity_type}/{key}/values | GET | 获取自定义字段已有值 |
| 外部目录 | /api/v1/external/filament | GET | 外部目录耗材列表 |
| 外部搜索 | /api/v1/external/filament/search | GET | 外部目录搜索 |
| 导出 | /api/v1/export/{spools|filaments|vendors} | GET | CSV / JSON 导出 |
| 搜索 | /api/v1/search | GET | 全文搜索(丝盘 + 耗材 + 厂商) |
| 备份 | /api/v1/backup | POST | 触发 SQLite 备份 |
| 标签扫描 | /api/v1/tag/scan | POST | NFC 标签扫描登记 |
| 标签读取器 | /api/v1/tag/reader | GET | 读取器状态 |
| WebSocket | /api/v1/spool | WS | 监听丝盘变更事件 |
四、认证与首次调用
Spoolman 没有 API Token 或 Basic Auth。所有端点公开可访问,依赖网络层安全(内网 / VPN / 反向代理)。首次调用无需额外 headers:
curl -s http://localhost:7912/api/v1/info
实测回显:
{"version":"0.27.0","debug_mode":false,"automatic_backups":true,"data_dir":"/root/.local/share/spoolman","logs_dir":"/root/.local/share/spoolman","backups_dir":"/root/.local/share/spoolman/backups","db_type":"sqlite","external_db_name":"SpoolmanDB","git_commit":"bed3f14","build_date":null}
健康检查:
curl -s http://localhost:7912/api/v1/health
实测回显:
{"status":"healthy"}
⚠️ 安全提醒:由于无认证,切勿将 Spoolman 端口直接暴露公网。应置于带认证的反向代理(如 Nginx + Basic Auth、Authelia 等)之后,或仅在内网可访问。
五、核心 API 域详解
5.1 Vendor(厂商)
创建厂商(POST /api/v1/vendor):
curl -s -X POST http://localhost:7912/api/v1/vendor \
-H 'Content-Type: application/json' \
-d '{"name":"eSUN"}'
实测回显:
{"id":1,"registered":"2026-10-25T00:53:47Z","name":"eSUN","extra":{}}
查询厂商详情(GET /api/v1/vendor/1):
curl -s http://localhost:7912/api/v1/vendor/1
实测回显:
{"id":1,"registered":"2026-10-25T00:53:47Z","name":"eSUN","extra":{}}
5.2 Filament(耗材型号)
创建耗材(POST /api/v1/filament):
curl -s -X POST http://localhost:7912/api/v1/filament \
-H 'Content-Type: application/json' \
-d '{
"name":"PLA+ 白色",
"vendor_id":1,
"material":"PLA",
"density":1.24,
"diameter":1.75,
"weight":1000,
"spool_weight":140,
"price":89.0
}'
实测回显(关键字段节选):
{"id":1,"registered":"2026-10-25T00:53:47Z","name":"PLA+ 白色","vendor":{"id":1,"name":"eSUN","extra":{}},"material":"PLA","density":1.24,"diameter":1.75,"weight":1000,"spool_weight":140,"price":89.0,"extra":{}}
5.3 Spool(实物丝盘)
创建丝盘(POST /api/v1/spool):
curl -s -X POST http://localhost:7912/api/v1/spool \
-H 'Content-Type: application/json' \
-d '{"filament_id":1,"initial_weight":1000,"spool_weight":140,"price":89.0}'
实测回显(关键字段节选):
{"id":1,"registered":"2026-10-25T00:53:55Z","remaining_weight":1000.0,"used_weight":0.0,"remaining_length":335283.6194167644,"used_length":0.0,"archived":false,"filament":{"id":1,"name":"PLA+ 白色"}}
💡 说明:创建时只需提供
filament_id、initial_weight、spool_weight与可选price;remaining_weight、remaining_length由后端根据密度/直径自动计算。
扣减消耗(PUT /api/v1/spool/{id}/use):
curl -s -X PUT http://localhost:7912/api/v1/spool/1/use \
-H 'Content-Type: application/json' \
-d '{"use_length":5.5}'
实测回显(关键字段节选):
{"id":1,"used_length":5.5,"used_weight":0.016404022390259954,"remaining_weight":999.9835959776098,"remaining_length":335278.1152374521}
💡 说明:扣减后
used_length累加,used_weight按密度/直径自动换算,first_used/last_used自动更新。
称重校准(PUT /api/v1/spool/{id}/measure):
curl -s -X PUT http://localhost:7912/api/v1/spool/1/measure \
-H 'Content-Type: application/json' \
-d '{"weight":950}'
实测回显(关键字段节选):
{"id":1,"remaining_weight":810.0000000000001,"used_weight":189.9999999999999,"remaining_length":271579.73172757926}
💡 说明:
weight为整卷含空轴的称重值,后端自动扣除spool_weight(皮重 140g)反推remaining_weight。
修改丝盘属性(PATCH /api/v1/spool/{id}):
curl -s -X PATCH http://localhost:7912/api/v1/spool/1 \
-H 'Content-Type: application/json' \
-d '{"location":"一号干燥箱","comment":"上机打印中"}'
实测回显:200 OK,返回全量丝盘对象(节选):
{"id":1,"location":"一号干燥箱","comment":"上机打印中"}
错误示例:访问不存在的 ID:
curl -s http://localhost:7912/api/v1/spool/999
实测回显(HTTP 404):
{"message":"No spool with ID 999 found."}
缺少必填字段:
curl -s -X POST http://localhost:7912/api/v1/spool \
-H 'Content-Type: application/json' \
-d '{}'
实测回显(HTTP 422):
{"detail":[{"type":"missing","loc":["body","filament_id"],"msg":"Field required","input":{}}]}
5.4 设置(Setting)
读取全部设置:
curl -s http://localhost:7912/api/v1/setting/
实测回显(节选):
{"currency":{"value":"\"EUR\"","is_set":false,"type":"string"},"round_prices":{"value":null,"is_set":false,"type":"boolean"},"locations":{"value":[],"is_set":true,"type":"array"}}
修改单项设置(POST /api/v1/setting/{key}):
OpenAPI schema 显示该端点 body 类型为 string,但实测发现:字符串本身必须是一个 JSON 字面量(即对 Spoolman 而言,其存储的是「以字符串形式存在的 JSON 编码值」)。直接 curl -d '"CNY"' 会因 shell 转义问题导致空请求体返回 400。
正确做法(Python 实测):
import json, urllib.request
# body 需要是 JSON 字符串,其内容又是一个 JSON 字符串
body = json.dumps(json.dumps("CNY")).encode()
req = urllib.request.Request(
"http://localhost:7912/api/v1/setting/currency",
data=body,
headers={"Content-Type": "application/json"},
method="POST")
with urllib.request.urlopen(req) as r:
print(r.read().decode())
实测回显:
{"value":"\"CNY\"","is_set":true,"type":"string"}
重置为未设置状态(设 is_set 回 false):
body = json.dumps(json.dumps("null")).encode()
实测回显:
{"value":"\"EUR\"","is_set":false,"type":"string"}
💡 说明:此设计的目的是让 setting 值能承载任意 JSON 类型(字符串、布尔、数组、对象)。修改后无需重启服务,UI 立即生效。
5.5 备份(Backup)
触发 SQLite 手动备份:
curl -s -X POST http://localhost:7912/api/v1/backup
实测回显(HTTP 200):
{"path":"/root/.local/share/spoolman/backups/spoolman.db","created":true}
备份文件落在数据目录 backups/ 下,文件名带时间戳(如 spoolman_2026-10-25_011800.db)。
5.6 导出(Export)
导出丝盘 CSV(必须显式指定 fmt 参数):
curl -s "http://localhost:7912/api/v1/export/spools?fmt=csv" | head -3
实测回显(表头 + 第一行):
archived,comment,filament.article_number,filament.color_hex,filament.comment,filament.density,filament.diameter,filament.external_id,filament.id,filament.material,filament.multi_color_direction,filament.multi_color_hexes,filament.name,filament.price,filament.registered,filament.settings_bed_temp,filament.settings_extruder_temp,filament.spool_weight,filament.tags,filament.vendor.comment,filament.vendor.empty_spool_weight,filament.vendor.external_id,filament.vendor.id,filament.vendor.name,filament.vendor.registered,filament.vendor_id,filament.weight,filament_id,first_used,id,initial_weight,last_used,location,lot_nr,price,registered,spool_weight,tags,used_weight
False,上机打印中,,,,1.24,1.75,,1,PLA,,,PLA+ 白色,89.0,2026-10-25 00:53:47,,,140.0,,,,,1,eSUN,2026-10-25 00:53:47,1,1000.0,1,2026-10-25 00:53:55,1,1000.0,2026-10-25 00:54:06,一号干燥箱,,89.0,2026-10-25 00:53:55,140.0,,189.9999999999999
错误示例:不传 fmt 参数:
curl -s "http://localhost:7912/api/v1/export/spools"
实测回显(HTTP 422):
{"detail":[{"type":"missing","loc":["query","fmt"],"msg":"Field required","input":null}]}
5.7 搜索(Search)
全文搜索(同时命中丝盘、耗材、厂商):
curl -s "http://localhost:7912/api/v1/search?q=PLA"
实测回显(节选):
{"spools":[],"filaments":[{"filament":{"id":1,"name":"PLA+ 白色","material":"PLA",...}}],"vendors":[]}
💡 说明:搜索按名称模糊匹配,结果按三类实体分组返回。
5.8 WebSocket 事件流
部分列表端点(如 /api/v1/spool)同时提供 WebSocket 升级,用于订阅实时变更事件:
import asyncio, json, websockets
async def main():
async with websockets.connect("ws://localhost:7912/api/v1/spool") as ws:
# 在另一个线程触发一次修改(如 PATCH /spool/1)
# ...
msg = await asyncio.wait_for(ws.recv(), timeout=5)
print(msg)
asyncio.run(main())
实测回显(触发 PATCH 后收到的事件):
{"type":"updated","resource":"spool","date":"2026-10-25T01:21:46.957626Z","payload":{"id":1,"registered":"2026-10-25T00:53:55Z","first_used":"2026-10-25T00:53:55Z","last_used":"2026-10-25T00:54:06Z",...}}
六、分页、过滤、排序通用约定
丝盘/耗材/厂商列表端点支持以下通用查询参数(以 /api/v1/spool 为例,参数来自 OpenAPI 3.1.0):
| 参数 | 说明 | 示例 |
|---|---|---|
filament.name | 按耗材名模糊搜索(不区分大小写) | filament.name=PLA |
filament.material | 按材料模糊搜索 | filament.material=PLA |
filament.vendor.name | 按厂商名模糊搜索 | filament.vendor.name=eSUN |
filament_id / filament.id | 按精确 ID(支持逗号分隔多值) | filament_id=1,2,3 |
vendor_id / filament.vendor.id | 按厂商 ID | vendor_id=1 |
location | 按存放位置模糊搜索 | location=一号干燥箱 |
lot_nr | 按批次号模糊搜索 | lot_nr=LOT001 |
tag | 按 NFC 标签 UID 精确匹配 | tag=ABC123 |
allow_archived | 是否包含已归档丝盘(默认 false) | allow_archived=true |
first_used / last_used / registered | 时间范围过滤,格式 start,end(含两端) | first_used=2026-10-01,2026-10-31 |
sort | 排序字段,格式 field:asc 或 field:desc,可多字段逗号分隔 | sort=remaining_weight:asc |
limit | 每页最大条数 | limit=10 |
offset | 分页偏移量(需配合 limit) | offset=0 |
实测示例:
curl -s "http://localhost:7912/api/v1/spool?filament.material=PLA&sort=remaining_weight:asc&limit=10&offset=0"
实测回显(数组,节选首条关键字段):
[{"id":1,"registered":"2026-10-25T00:53:55Z","first_used":"2026-10-25T00:53:55Z","last_used":"2026-10-25T00:54:06Z","filament":{"name":"PLA+ 白色","material":"PLA"},"remaining_weight":999.9835959776098,...}]
七、常见问题
Q1:POST 返回 422 Field required?
请求体缺少必填字段。对照 OpenAPI 文档(/api/v1/openapi.json)或本文示例补齐字段。如 POST /spool 必须有 filament_id。
Q2:POST /setting/{key} 一直报 400 / 422?
该端点 body 必须是「JSON 字符串,其内容又是一个 JSON 字面量」。curl 直接写容易因 shell 转义出错,建议用 Python 按 5.4 节示例构造,或先用 GET /api/v1/setting/{key} 观察当前值的 JSON 编码形态再构造。
Q3:导出返回 422?
/export/* 端点必须带 fmt 查询参数(csv 或 json),如 ?fmt=csv。
Q4:WebSocket 连不上?
确认地址是 ws:// 而非 http://,路径为 /api/v1/spool(或其他支持 WS 的列表端点)。连接建立后若无事件,会处于空闲等待状态;触发一次数据变更(如 PATCH)即可收到推送。
Q5:返回 404 No spool with ID ... found.?
资源 ID 不存在。列表接口先查全量确认 ID 再操作。
八、参考链接
- 官方仓库:https://github.com/Donkie/Spoolman
- OpenAPI 文档:部署后访问
http://<服务器IP>:7912/api/v1/openapi.json或http://<服务器IP>:7912/docs(Swagger UI) - 安装部署:见本专栏《Ubuntu 22.04 安装 Spoolman v0.27.0 指南》《Spoolman v0.27.0 Docker Compose 部署指南》
- 配置参数:见本专栏《Spoolman v0.27.0 配置参数详解》