bewCloud v4.21.0 API 使用详解
bewCloud v4.21.0 全部程序化接口实测详解:会话认证与首次调用、files/dashboard/expenses/calendar/contacts/auth 六个 JSON API 域端点参数与真实响应、WebDAV 与 CalDAV/CardDAV 端点 curl 调用、上传会话标签算法、错误形态速查与分页过滤约定,全部请求回显 2026-10-30 实测。
bewCloud v4.21.0 API 使用详解
目标:把 bewCloud v4.21.0 对外暴露的全部程序化接口讲清并逐条实测——会话认证与首次调用、files/dashboard/expenses/calendar/contacts/auth 六个 JSON API 域的端点、参数与真实响应,以及 WebDAV 文件端点与 CalDAV/CardDAV 协议端点的 curl 调用方法。所有请求与响应均为 2026-10-30 在实测环境(Linux x86_64、bewCloud v4.21.0)逐条执行的真实回显,不转述官方文档示例。
本文是 bewCloud 系列的第四篇。部署过程见 《bewCloud v4.21.0 Docker Compose 部署指南》,接口行为背后的配置项见 《bewCloud v4.21.0 配置参数详解》。
bewCloud 没有独立的管理 CLI,也没有单独的 API 网关——它对外只有一个入口端口,Web 界面、JSON API、WebDAV、CalDAV/CardDAV 四类协议面共用。这意味着「程序化使用 bewCloud」= 用 HTTP 客户端(curl、脚本、DAV 客户端)直接对话这一个端口,本篇逐域演示怎么做。
一、环境信息
| 项目 | 值 |
|---|---|
| 组件版本 | bewCloud v4.21.0 |
| 服务端点 | http://<服务器IP>:8155(Web / JSON API / WebDAV / CalDAV / CardDAV 共用单端口,应用内 8000) |
| 实测账号 | admin@example.com / q1w2e3r4(首用户管理员,注册过程见 4.1) |
| 配置要点 | skipCookieDomainSecurity: true(HTTP+IP 访问)、allowPublicSharing: true、allowDirectoryDownloads: true(本篇分享与打包下载实测依赖,取值依据见配置篇) |
| 客户端工具 | curl 8.5.0(2.3 实测) |
| 实测日期 | 2026-10-30 |
二、前置条件(端点/认证/客户端工具)
2.1 服务端点
bewCloud 的全部接口收敛在一个端口(本篇实测映射为宿主 8155)。四类协议面按路径前缀划分:
| 协议面 | 路径前缀 | 认证载体 | 典型用途 |
|---|---|---|---|
| JSON API | /api/* | 会话 Cookie | 脚本化操作文件、记账、日历、仪表盘 |
| WebDAV | /dav/ | 会话 Cookie | 桌面挂载网络磁盘、脚本上传下载 |
| CalDAV/CardDAV | /caldav/、/carddav/、/.well-known/* | 会话 Cookie 或 Basic 认证(5.8 实测两者均通过) | 手机日历、通讯录直连同步 |
| 页面路由 | /login、/signup、/dashboard、/file-share/<id> 等 | Cookie / 公开 | 会话建立、公开分享落地 |
页面路由与 JSON API 同源:登录态由同一枚 Cookie 承载,页面里前端小岛调用的正是 /api/* 这套接口——用 curl 模拟浏览器行为即可完全复现界面上的操作。
2.2 认证方式
实测结论先给出,细节与回显见第四节与 5.9:
- 没有 API Key/Token 机制:bewCloud v4.21.0 不提供长期 API 令牌,程序化调用一律以登录会话 Cookie 模拟浏览器会话——脚本先 POST
/login拿 Cookie,后续请求全部携带; - 会话 Cookie:登录后服务端签发名为
bewcloud-app-v1的 HttpOnly Cookie,值是一个 JWT(内含user_id与session_id),有效期长达一年(4.2 实测 Cookie 过期时间戳); - DAV 端点双认证:
/caldav/、/carddav/既接受会话 Cookie,也接受邮箱+口令的 Basic 认证(5.8 两种方式实测均返回 207)——后者正是手机日历/通讯录客户端使用的形态; - 上传会话标签:
/api/files/upload要求额外提交upload_session_tag(会话 ID 与口令盐的 SHA-256 哈希,见 5.1),这是官方针对「上传队列跨会话存活」设计的防护,纯 Cookie 调用也绕不开。
2.3 客户端工具
本篇全部示例用 curl 完成,实测环境版本:
curl --version | head -1
实测回显:
curl 8.5.0 (x86_64-pc-linux-gnu) libcurl/8.5.0 OpenSSL/3.0.13 zlib/1.2.11 brotli/1.1.0 zstd/1.5.5 libidn2/2.3.7 libpsl/0.21.2 (+libidn2/2.3.7) libssh/0.10.6/openssl/zlib nghttp2/1.59.0 librtmp/2.3 OpenLDAP/2.6.10
后续示例中的 Cookie 统一保存在 ~/bewcloud-cookie.txt(-c 写入、-b 携带),路径可按需替换。
三、API 总览
3.1 JSON API 域清单
v4.21.0 的 JSON API 按应用域划分(端点清单来源:官方仓库 routes.ts 与实例容器内 pages/api/ 路由目录实查,2026-10-30;方法与参数行为以下各节实测为准):
| 域 | 端点 | 说明 |
|---|---|---|
| files | /api/files/upload、upload-chunk、upload-abort、get、get-directories、search、create-directory、delete-directory、rename-directory、move-directory、rename、move、delete、download-directory、create-share、get-share、update-share、delete-share、update-sort | 文件上传(直传+分片)、目录/文件管理、公共分享;另有 /dav/ WebDAV 通道(5.7) |
| dashboard | /api/dashboard/save-links、save-notes | 仪表盘快捷链接与速记保存 |
| expenses | /api/expenses/add-budget、add-expense、update-budget、update-expense、delete-budget、delete-expense、auto-complete、export-expenses、import-expenses | 记账与预算管理 |
| calendar | /api/calendar/add、add-event、update、delete、delete-event、search-events、export-events、import | 日历与事件管理;另有 /caldav/ 协议通道 |
| contacts | /api/contacts/add-addressbook、get-addressbooks、add、get、delete-addressbook、delete、import | 通讯录与地址簿;另有 /carddav/ 协议通道 |
| auth | /api/auth/multi-factor/email/setup、passkey/{begin,setup-begin,setup-complete,verify}、totp/setup、enable、disable | MFA/Passkey 管理(enableMultiFactor 开启后可用) |
清单里的端点绝大多数是 POST;download-directory 是 GET(查询参数传目录路径,5.1 实测踩过 POST 返回 501 的坑)。本篇逐域实测最常用的一条主线,其余端点的请求结构与同域端点同构(同样的 success 包裹与字段命名风格)。
3.2 认证与限额约定
- 认证:全部
/api/*与/dav/、/caldav/、/carddav/端点要求已登录会话;未认证请求的实际形态见 4.4 实测; - 请求体:JSON 域接口为 JSON 请求体(
Content-Type: application/json),文件上传为 multipart 表单——均以各节实测回显为准; - 响应包裹:业务响应统一为 JSON 对象,第一字段是
success布尔值,失败时附error字符串(5.6 的 403 实测可见); - 限额:一般请求体上限
core.maxRequestSizeInMegabytes(默认 12MB),文件上传走独立限额files.maxUploadSizeInMegabytes(默认 100MB),配置依据见配置篇 5.2/5.3; - 无分页:列表类响应一次性返回全量条目(5.1/5.4 实测均无游标或页码字段),详见第六节。
四、认证与首次调用(curl 实测)
4.1 注册第一个用户
新实例没有任何用户时,第一个注册请求会被放行并自动成为管理员(allowSignups=false 也放行,行为依据见配置篇 5.1)。先确认注册页与表单字段名:
curl -s http://<服务器IP>:8155/signup | grep -oE 'name="[^"]+"|type="email"|type="password"' | sort | uniq -c
实测回显:
1 name="author"
1 name="description"
1 name="email"
1 name="password"
1 name="viewport"
1 type="email"
1 type="password"
(author/description/viewport 是页面 meta 标签的同名属性,注册表单字段只有 email 和 password 两个。)按表单字段提交注册:
curl -s -X POST http://<服务器IP>:8155/signup \
-d 'email=admin@example.com&password=q1w2e3r4' \
-o /dev/null -w '%{http_code} %{redirect_url}\n'
实测回显:
303 http://192.168.146.128:8155/login?success=signup&email=admin%40example.com
303 跳转到登录页并携带 success=signup 参数——首用户注册成功,该账号即实例管理员。注册后如果用同一账号再次提交,注册页会重渲染并提示账号已存在。
4.2 登录并获取会话 Cookie
curl -s -c ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/login \
-d 'email=admin@example.com&password=q1w2e3r4' \
-o /dev/null -w '%{http_code} %{redirect_url}\n'
实测回显:
303 http://192.168.146.128:8155/
登录成功,303 跳转首页,会话 Cookie 已写入 ~/bewcloud-cookie.txt。查看拿到的 Cookie 内容:
cat ~/bewcloud-cookie.txt
实测回显:
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_192.168.146.128 FALSE / FALSE 1796016700 bewcloud-app-v1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7InVzZXJfaWQiOiI5ZDVlYmFkNC1mNmE3LTQ1ODgtODg0ZS1mOGM4YzhlZGU2MzIiLCJzZXNzaW9uX2lkIjoiYTg5MWM3ZTQtZDQ5MC00NmRiLTg1MTYtOTExYjY5ZTMzMGI2In19.sRkPQF0J-PS5ZIw13UJC3v25SX5X8hbNYd4pvqIq24w
关键信息三处:Cookie 名 bewcloud-app-v1;标记 #HttpOnly_ 前缀说明服务端下了 HttpOnly 属性(浏览器脚本读不到,curl 不受影响);过期时间戳 1796016700 换算约为 2027 年——会话有效期一年。JWT 载荷(中间段 base64 解码)含 user_id 与 session_id,5.1 节计算上传标签要用到 session_id。
4.3 携带 Cookie 访问受保护资源
curl -s -b ~/bewcloud-cookie.txt -o /dev/null -w '%{http_code}\n' http://<服务器IP>:8155/dashboard
实测回显:
200
4.4 未认证请求的实际形态
不带 Cookie 分别打页面与 API 端点,确认未认证形态(脚本里可据此判断会话失效):
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://<服务器IP>:8155/dashboard
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://<服务器IP>:8155/api/dashboard/save-notes -X POST -H 'Content-Type: application/json' -d '{}'
实测回显:
302 http://192.168.146.128:8155/login?redirectTo=%2Fdashboard
302 http://192.168.146.128:8155/login?redirectTo=%2Fapi%2Fdashboard%2Fsave-notes
两类端点行为一致:302 重定向到登录页并带回跳地址,而不是 REST 风格的 401 JSON。这是「API 为页面服务」的同构设计——脚本侧判断会话失效的依据是 302 而非 401。
五、各 API 域详解
以下各域请求均在 2026-10-30 实测执行,回显逐条照抄(长响应按「输出节选(实测首尾)」标注截取)。除特别注明外,请求都携带 4.2 拿到的 Cookie(-b ~/bewcloud-cookie.txt)。实测环境用户 ID 为 9d5ebad4-f6a7-4588-884e-f8c8c8ede632,默认日历 ID 为 03f9ab87-3c19-49a0-95ed-cd31b40b5795(取自 /calendar 页面数据),读者实测时会得到自己的 ID。
5.1 files 域:文件与目录
files 域是最常被脚本化的域。主线六步:建目录 → 上传 → 列目录 → 建分享 → 公开访问 → 打包下载,最后清理。
创建目录(JSON 请求体 {parentPath, name}):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/files/create-directory \
-H 'Content-Type: application/json' -d '{"parentPath":"/","name":"docs"}'
实测回显(输出节选):
{"success":true,"newDirectories":[{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","directory_name":".Trash",…},{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","directory_name":"docs","has_write_access":true,"size_in_bytes":4096,"file_share_id":null,"updated_at":"2026-10-30T05:36:36.986Z","created_at":"2026-10-30T05:36:36.986Z"}]}
首次创建目录时,应用自动建立了 .Trash 回收站目录;响应返回变更后的完整目录列表(newDirectories),这个「变更 + 新列表」的响应模式贯穿 files 域。
上传文件(multipart 表单,字段:path_in_view、parent_path、name、contents、overwrite、upload_session_tag)。upload_session_tag 的算法(源码 lib/auth.ts 实查):SHA-256("<session_id>:<PASSWORD_SALT>"),其中 session_id 在 4.2 的 JWT 载荷里、PASSWORD_SALT 是部署时 .env 里的值。实测环境先算标签再上传:
SID=a891c7e4-d490-46db-8516-911b69e330b6
TAG=$(printf '%s:bewcloud-demo-salt-4d8b1f6a' "$SID" | sha256sum | cut -d' ' -f1)
echo 'hello bewCloud' > /tmp/hello.txt
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/files/upload \
-F 'path_in_view=/' -F 'parent_path=/' -F 'name=hello.txt' \
-F "upload_session_tag=$TAG" -F 'overwrite=true' -F 'contents=@/tmp/hello.txt'
实测回显(输出节选):
{"success":true,"newFiles":[{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","file_name":"hello.txt","has_write_access":true,"size_in_bytes":15,"file_share_id":null,"updated_at":"2026-10-30T05:36:37.166Z","created_at":"2026-10-30T05:36:37.166Z"}],"newDirectories":[{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","directory_name":".chunk-uploads",…},…]}
上传成功返回新文件条目(15 字节);首次上传还会自动创建 .chunk-uploads 分片暂存目录(配合 /api/files/upload-chunk 大文件分片上传)。缺 upload_session_tag 或标签算错,上传返回 403 Forbidden——这是纯 Cookie 调用也绕不开的防护(详见第七节 FAQ)。
列出目录(JSON 请求体 {parentPath},只返回目录;文件列表由页面渲染):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/files/get-directories \
-H 'Content-Type: application/json' -d '{"parentPath":"/"}'
实测回显(输出节选):
{"success":true,"directories":[{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","directory_name":".chunk-uploads",…},{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","directory_name":".Trash",…},{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","directory_name":"docs","has_write_access":true,"size_in_bytes":4096,"file_share_id":null,"updated_at":"2026-10-30T05:36:36.986Z","created_at":"2026-10-30T05:36:36.986Z"}]}
创建公共分享(JSON 请求体 {pathInView, filePath, password},password 可空):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/files/create-share \
-H 'Content-Type: application/json' -d '{"pathInView":"/","filePath":"/hello.txt","password":""}'
实测回显(输出节选):
{"success":true,"newFiles":[{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","file_name":"hello.txt","has_write_access":true,"size_in_bytes":15,"file_share_id":"55d3ac8e-5d0c-4ffb-9af0-c3080b3e70c4",…}],"newDirectories":[…],"createdFileShareId":"55d3ac8e-5d0c-4ffb-9af0-c3080b3e70c4"}
响应尾部的 createdFileShareId 就是分享 ID,公开链接为 /file-share/<id>。
公开访问分享链接(不带 Cookie 访问):
curl -s -o /dev/null -w '%{http_code}\n' http://<服务器IP>:8155/file-share/55d3ac8e-5d0c-4ffb-9af0-c3080b3e70c4
curl -s http://<服务器IP>:8155/file-share/55d3ac8e-5d0c-4ffb-9af0-c3080b3e70c4 | grep -o '<title>[^<]*</title>'
实测回显:
200
<title>File Share - bewCloud</title>
分享落地页无需登录即可打开,页内提供文件下载。若 allowPublicSharing=false(默认值),create-share 接口整体不可用(403)。
目录打包下载(注意是 GET + 查询参数,不是 POST):
curl -s -b ~/bewcloud-cookie.txt \
'http://<服务器IP>:8155/api/files/download-directory?parentPath=/&name=docs' \
-o /tmp/docs.zip -w '%{http_code} %{content_type}\n'
unzip -l /tmp/docs.zip | tail -2
实测回显(目录中已放入一个 12 字节的 dav-note.txt,见 5.7):
200 application/zip
--------- -------
12 1 file
allowDirectoryDownloads=false(默认值)时该端点返回 403;用 POST 打它则返回 501 Not Implemented(5.9 实测踩坑记录)。
删除文件与分享(两个端点):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/files/delete \
-H 'Content-Type: application/json' -d '{"parentPath":"/","name":"hello.txt"}'
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/files/delete-share \
-H 'Content-Type: application/json' -d '{"pathInView":"/","fileShareId":"aa80bdcf-fa9a-4501-808d-595ebeb57012"}'
curl -s -o /dev/null -w '%{http_code}\n' http://<服务器IP>:8155/file-share/aa80bdcf-fa9a-4501-808d-595ebeb57012
实测回显(输出节选):
{"success":true,"newFiles":[]}
{"success":true,"newFiles":[{"user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","parent_path":"/","file_name":"share.txt",…}],"newDirectories":[…]}
404
delete 删除文件(newFiles 变空);delete-share 撤销分享——注意它要求同时传 pathInView 与 fileShareId,只传 ID 实测返回 400 Bad Request;分享撤销后原公开链接立即 404。
5.2 dashboard 域:速记与快捷链接
dashboard 域只有两个保存端点(读取随页面渲染返回)。实测速记保存:
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/dashboard/save-notes \
-H 'Content-Type: application/json' -d '{"notes":"API 实测速记 2026-10-30"}'
实测回显:
{"success":true}
save-links 同构,请求体为 links 数组(每项含名称与 URL),保存后仪表盘显示自定义快捷链接。两个接口都是「全量覆盖」语义:每次保存的是完整列表,不是增量追加。
5.3 expenses 域:记账
记账域主线四步:建预算 → 记支出 → 自动补全 → 导出。
建预算({name, month, value, currentMonth}):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/expenses/add-budget \
-H 'Content-Type: application/json' -d '{"name":"餐饮","month":"2026-10","value":800,"currentMonth":"2026-10"}'
实测回显:
{"success":true,"newBudgets":[{"id":"a04deb98-d75f-4785-8c2e-16db42c40641","user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","name":"餐饮","month":"2026-10","value":800,"extra":{"usedValue":0,"availableValue":800},"created_at":"2026-10-30T05:37:46.736Z"}]}
记支出({cost, description, budget, date, is_recurring, month}):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/expenses/add-expense \
-H 'Content-Type: application/json' \
-d '{"cost":35.5,"description":"午餐","budget":"餐饮","date":"2026-10-30","is_recurring":false,"month":"2026-10"}'
实测回显(输出节选):
{"success":true,"newExpenses":[{"id":"3bc6e879-fe73-44a5-b5ad-d2c37da1cf0b","user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","cost":35.5,"description":"午餐","budget":"餐饮","date":"2026-10-30","is_recurring":false,"extra":{},"created_at":"2026-10-30T05:37:47.078Z"}],"newBudgets":[{"id":"a04deb98-d75f-4785-8c2e-16db42c40641","user_id":"9d5ebad4-f6a7-4588-884e-f8c8c8ede632","name":"餐饮","mon
响应同时带回支出与预算(预算的 extra 内含用量,界面据此渲染进度)。描述自动补全(输入至少 2 个字符):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/expenses/auto-complete \
-H 'Content-Type: application/json' -d '{"name":"午餐"}'
实测回显:
{"success":true,"suggestions":["午餐"]}
导出(注意是 POST;GET 打它返回 501):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/expenses/export-expenses
实测回显:
{"success":true,"jsonContents":{"expenses":[{"description":"午餐","budget":"餐饮","date":"2026-10-30","is_recurring":false,"cost":35.5,"extra":{}}],"budgets":[{"name":"餐饮","month":"2026-10","value":800,"extra":{}}]}}
导出为 JSON(expenses + budgets 两组数组),配合同域的 import-expenses 可完成实例间迁移。CSV 形态的导入导出在网页界面里完成。
5.4 calendar 域:日历与事件
calendar 域的写操作实际落点在 Radicale(事件以 ICS 文件存储)。日历 ID 可从 /calendar 页面数据中获得,实测环境默认日历为 03f9ab87-3c19-49a0-95ed-cd31b40b5795。
新建日历({name}):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/calendar/add \
-H 'Content-Type: application/json' -d '{"name":"家庭"}'
实测回显(输出节选):
{"success":true,"newCalendars":[{"description":"","timezone":"","url":"http://radicale:5232/9d5ebad4-f6a7-4588-884e-f8c8c8ede632/82b804ca-fec4-434f-b528-6bd9d33c0862/","ctag":"\"c12f179192d17dee9485f544d09cf75c31aecc38adeb3c5783304a3ef42f31fb\"","calendarColor":"#BC133D","displayName":"家庭",…
响应里的 url 直指 Radicale 内部集合地址——印证日历数据真实落在 Radicale 上。
新建事件(calendarIds 是"当前视图显示哪些日历"、calendarId 是事件归属日历,两者都是数组/单值并用):
CAL=03f9ab87-3c19-49a0-95ed-cd31b40b5795
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/calendar/add-event \
-H 'Content-Type: application/json' \
-d "{\"calendarIds\":[\"$CAL\"],\"calendarView\":\"month\",\"calendarStartDate\":\"2026-10-01\",\"calendarId\":\"$CAL\",\"title\":\"API 实测事件\",\"startDate\":\"2026-10-30 15:00\",\"endDate\":\"2026-10-30 16:00\",\"isAllDay\":false}"
实测回显(输出节选):
{"success":true,"newCalendarEvents":[{"url":"http://radicale:5232/9d5ebad4-f6a7-4588-884e-f8c8c8ede632/03f9ab87-3c19-49a0-95ed-cd31b40b5795/d339694b-964f-4fbb-8950-f41affe429b7.ics","etag":"\"ed7f6f6d69ade5e0e4c8783fa7744303b6bc751591dee784e1fd443edf523e1c\"","data":"BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//PYVOBJECT//NONSGML Version 0.9.9//EN\r\nBEGIN:VEVENT\r\nUID:d33969
搜索事件({calendarIds, searchTerm}):
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/calendar/search-events \
-H 'Content-Type: application/json' \
-d '{"calendarIds":["03f9ab87-3c19-49a0-95ed-cd31b40b5795"],"searchTerm":"API"}'
实测回显(输出节选):
{"success":true,"matchingCalendarEvents":[{"url":"http://radicale:5232/9d5ebad4-f6a7-4588-884e-f8c8c8ede632/03f9ab87-3c19-49a0-95ed-cd31b40b5795/d339694b-964f-4fbb-8950-f41affe429b7.ics",…,"data":"BEGIN:VCALENDAR\r\n…(节选)\r\nSUMMARY:API 实测事件\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR","uid":"d339694b-964f-4fbb-8950-f41affe429b7","startDate":"2026-10-30T15:00:00.000Z","endDate":"2026-10-30T16:00:00.000Z","title":"API 实测事件","transparency":"opaque","calendarId":"03f9ab87-3c19-49a0-95ed-cd31b40b5795"}]}
搜索命中了刚建的事件,响应携带完整 ICS 原文与起止时间。export-events 同域可用,导出格式即 ICS。
5.5 contacts 域:通讯录与地址簿
通讯录数据同样落在 Radicale,接口按「地址簿 → 条目」两级组织。实测地址簿列表与新建:
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/contacts/get-addressbooks \
-H 'Content-Type: application/json' -d '{}'
curl -s -b ~/bewcloud-cookie.txt -X POST http://<服务器IP>:8155/api/contacts/add-addressbook \
-H 'Content-Type: application/json' -d '{"name":"同事"}'
实测回显(输出节选):
{"success":true,"addressBooks":[]}
{"success":true,"addressBooks":[{"url":"http://radicale:5232/9d5ebad4-f6a7-4588-884e-f8c8c8ede632/4b876b58-ff33-4458-a37c-ffb88bb5f139/","ctag":"\"c3820dfd7f46fadc3e804f138fb5a6c6cea2dfc53ac3d52a806160dcba5f831d\"","displayName":"%E5%90%8C%E4%BA%8B","resourcetype":["addressbook","collection"],"syncToken":"http://radicale.org/ns/sync/e3b0c442…
新实例地址簿为空,新建后返回的 displayName 是 URL 编码后的名称(%E5%90%8C%E4%BA%8B 即「同事」)。条目级增删(add/delete/import)同域可用,vCard 数据结构与日历域同构。
5.6 auth 域:MFA 与 Passkey
auth 域端点在 enableMultiFactor=false(默认)时的行为——返回 403 与明确的 JSON 错误说明(这是标准错误形态的最佳样本):
curl -s -w '\nHTTP %{http_code}\n' -b ~/bewcloud-cookie.txt -X POST \
http://<服务器IP>:8155/api/auth/multi-factor/totp/setup -H 'Content-Type: application/json' -d '{}'
curl -s -w '\nHTTP %{http_code}\n' -b ~/bewcloud-cookie.txt -X POST \
http://<服务器IP>:8155/api/auth/multi-factor/passkey/begin -H 'Content-Type: application/json' -d '{}'
实测回显:
{"success":false,"error":"Multi-factor authentication is not enabled on this server"}
HTTP 403
{"success":false,"error":"Passwordless passkey login requires multi-factor authentication to be enabled."}
HTTP 403
把配置 auth.enableMultiFactor 改为 true 并重启应用后(配置方法见配置篇 5.1,.env 需补 MFA_KEY/MFA_SALT),这组端点即按 TOTP/passkey 注册流程工作。
5.7 WebDAV 文件端点(/dav/)
/dav/ 是文件域的另一扇门:桌面 WebDAV 客户端挂载与脚本批量传文件都走这里,路径与 /api/files 操作的是同一份 data-files 存储(5.1 上传的 hello.txt 与本节 PUT 的文件在 PROPFIND 结果中同现)。
探测能力(OPTIONS):
curl -s -b ~/bewcloud-cookie.txt -X OPTIONS -D - -o /dev/null http://<服务器IP>:8155/dav/ | grep -iE 'HTTP/|allow|dav'
实测回显:
HTTP/1.1 200 OK
allow: OPTIONS, GET, HEAD, PUT, DELETE, COPY, MOVE, MKCOL, PROPFIND, LOCK, UNLOCK
dav: 1, 2
ms-author-via: DAV
Class 1 + Class 2(dav: 1, 2)意味着完整 WebDAV 能力含锁——主流桌面客户端(Windows 资源管理器、macOS Finder、Linux 的 davfs2/GVFS)均可挂载。
上传与下载(PUT/GET):
printf 'from webdav\n' > /tmp/dav-note.txt
curl -s -b ~/bewcloud-cookie.txt -X PUT --data-binary @/tmp/dav-note.txt \
-o /dev/null -w '%{http_code}\n' http://<服务器IP>:8155/dav/docs/dav-note.txt
curl -s -b ~/bewcloud-cookie.txt http://<服务器IP>:8155/dav/docs/dav-note.txt
实测回显:
201
from webdav
PUT 到 /dav/docs/(5.1 建的目录)返回 201 Created,GET 回读内容一致。
列目录(PROPFIND):
curl -s -b ~/bewcloud-cookie.txt -X PROPFIND -H 'Depth: 1' \
http://<服务器IP>:8155/dav/ -o /tmp/propfind.xml -w '%{http_code}\n'
grep -oE '<D:href>[^<]*</D:href>' /tmp/propfind.xml | head -5
实测回显:
207
<D:href>/dav/</D:href>
<D:href>/dav/docs/</D:href>
<D:href>/dav/hello.txt</D:href>
<D:href>/dav/.chunk-uploads/</D:href>
<D:href>/dav/.Trash/</D:href>
207 Multi-Status 是 WebDAV 列目录的标准响应;Depth: 1 返回根目录及其直接子项,与 5.1 API 视角看到的目录树一致。
5.8 CalDAV/CardDAV 协议端点
日历与通讯录的协议端点由应用反代到 Radicale(架构见介绍篇第三节),认证由应用完成。well-known 发现端点(DAV 客户端配置时只需给域名,客户端靠它自动发现协议路径):
curl -s -o /dev/null -D - http://<服务器IP>:8155/.well-known/caldav | grep -iE 'HTTP/|location'
curl -s -o /dev/null -D - http://<服务器IP>:8155/.well-known/carddav | grep -iE 'HTTP/|location'
实测回显:
HTTP/1.1 301 Moved Permanently
Location: /caldav
HTTP/1.1 301 Moved Permanently
Location: /carddav
CalDAV PROPFIND(先带 Cookie,再用 Basic 认证验证手机客户端形态):
curl -s -b ~/bewcloud-cookie.txt -X PROPFIND -H 'Depth: 0' \
http://<服务器IP>:8155/caldav/ -o /tmp/caldav.xml -w '%{http_code}\n'
head -c 260 /tmp/caldav.xml
curl -s -u 'admin@example.com:q1w2e3r4' -X PROPFIND -H 'Depth: 0' \
http://<服务器IP>:8155/caldav/ -o /dev/null -w '%{http_code}\n'
实测回显:
207
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:" xmlns:RADICALE="http://radicale.org/ns/"><response><href>/caldav/</href><propstat><prop><principal-collection-set><href>/caldav/</href></principal-collection-set><RADICALE:version>3.7.6</RADICALE…
207
CardDAV PROPFIND:
curl -s -b ~/bewcloud-cookie.txt -X PROPFIND -H 'Depth: 0' \
http://<服务器IP>:8155/carddav/ -o /tmp/carddav.xml -w '%{http_code}\n'
head -c 200 /tmp/carddav.xml
实测回显:
207
<?xml version='1.0' encoding='utf-8'?>
<multistatus xmlns="DAV:" xmlns:RADICALE="http://radicale.org/ns/"><response><href>/carddav/</href><propstat><prop><principal-collection-set><href>/carddav/</hre…
两个协议端点的 PROPFIND 都返回 207,响应 XML 里的 RADICALE:version 3.7.6 印证了后端版本;Cookie 与 Basic 认证实测均通过——手机日历/通讯录 App(iPhone「日历/通讯录」账户、Davx⁵ 等)用 http://<服务器IP>:8155 加邮箱口令即可直连。5.4/5.5 经 API 建的日历与地址簿,正是从这些协议端点可同步到的集合。
5.9 错误码形态速查
本篇实测触达的全部错误形态汇总(脚本侧可直接按此做分支判断):
| 场景 | 实测响应 | 判断依据 |
|---|---|---|
| 未认证访问页面或 API | 302 → /login?redirectTo=… | Location 头(4.4) |
| 会话 Cookie 失效 | 同上,302 回登录页 | 同上 |
| 访问不存在的路径 | 404(HTML 404 页,<title>404 - Page not found - bewCloud</title>) | 状态码 |
| 口令错误的登录 | 200(重渲染登录页并显示错误) | 响应体含错误提示 |
| JSON API 用错 HTTP 方法(如 GET 打 POST-only 端点) | 501 Not Implemented(text/plain) | 状态码 |
| 请求体参数不合法(如 auto-complete 的 name 少于 2 字符) | 400 Bad Request(text/plain) | 状态码 |
| 业务性拒绝(如 MFA 未启用时调 MFA 端点) | 403 + JSON {"success":false,"error":"…"} | JSON error 字段 |
上传缺 upload_session_tag 或标签不匹配 | 403 Forbidden | 状态码 |
| 分享链接被撤销后访问 | 404 | 状态码 |
形态规律:页面同构错误走重定向/HTML,JSON API 的业务错误走 {"success":false,"error":…} JSON;501/400 这类框架级错误则是裸文本。错误信息字面量(如上表引号内文本)均为实测原文。
六、分页/过滤/排序通用约定
v4.21.0 的 JSON API 没有统一的分页机制:本篇实测的所有列表响应(newDirectories/directories/newExpenses/matchingCalendarEvents/addressBooks)都是一次性全量返回,无页码、游标或 limit/offset 参数——这是单用户个人云数据规模下的设计取舍,数据量大时靠应用侧过滤。
过滤与排序按域各自约定,实测依据如下:
- 目录过滤:
get-directories支持directoryPathToExclude参数(排除指定目录,源码pages/api/files/get-directories.tsRequestBody 实查),移动/复制对话框用它排除自身; - 文件排序:文件页的排序状态经
/api/files/update-sort持久化,请求体{sortBy, sortOrder}(实例前端 JS 实查),下次登录保持; - 文件搜索:
/api/files/search按名称过滤文件与目录; - 事件过滤:
search-events的searchTerm按事件标题匹配(5.4 实测命中); - 描述补全:
auto-complete按历史描述前缀联想,输入至少 2 字符(不足实测返回 400)。
七、常见问题
Q1:为什么没有 API Token?脚本每次都要先登录吗?
是。bewCloud 的 API 面为自家前端服务,会话即凭证。脚本的标准姿势是 4.2 的一步登录(Cookie 有效期一年,4.2 实测),之后重复使用 Cookie 文件即可;JWT_SECRET 轮换会使所有会话失效(配置篇 5.8),脚本需具备重新登录能力。
Q2:/api/files/upload 明明带了 Cookie 还是 403?
缺 upload_session_tag 或算错。该标签 = SHA-256("<session_id>:<PASSWORD_SALT>"),session_id 从登录 Cookie 的 JWT 载荷解码,PASSWORD_SALT 是 .env 里的值(5.1 实测计算与上传全过程)。标签随会话变化,重登后要重算。只做文件搬运的脚本建议直接走 /dav/ PUT——没有标签要求,且能力完整(5.7)。
Q3:明明路径正确,为什么返回 501?
方法用错了。JSON API 几乎全是 POST,唯 download-directory 等极少数是 GET;用 GET 打 POST-only 端点实测返回 501 Not Implemented(5.3/5.9)。先对照 3.1 清单确认方法。
Q4:DAV 客户端(手机日历/通讯录、桌面网盘挂载)分别怎么配?
三类入口:手机日历/通讯录用 CalDAV/CardDAV——服务器填 http://<服务器IP>:8155,用户名邮箱、口令即登录口令(Basic 认证实测通过,5.8),客户端会自动走 /.well-known 发现;桌面文件挂载用 WebDAV 地址 http://<服务器IP>:8155/dav;纯脚本用 5.1 的 files API。HTTP 明文场景下这些凭据会明文传输,公网部署务必套 HTTPS 反代。
Q5:API 建的日历/地址簿,换一个 CalDAV 客户端能看到吗?
能看到。API 写操作落在 Radicale 集合(5.4/5.5 响应里的 url 字段即 Radicale 地址),任何 CalDAV/CardDAV 客户端经 5.8 的协议端点连入后看到的是同一份数据——「API 面」与「协议面」只是同一存储的两个入口。
Q6:脚本怎么判断会话失效?
任何请求返回 302 且 Location 指向 /login?redirectTo=… 即会话失效(4.4 实测形态),重新执行登录步骤刷新 Cookie 文件即可。
八、参考链接
- 官方仓库(路由源码 routes.ts):https://github.com/bewcloud/bewcloud
- WebDAV/CalDAV/CardDAV 端点行为:官方 FAQ.md(https://github.com/bewcloud/bewcloud/blob/main/FAQ.md)
- 上传与请求限额的配置依据:
lib/config.ts(https://github.com/bewcloud/bewcloud/blob/main/lib/config.ts) - 本系列其他篇章:《bewCloud v4.21.0 介绍与选型指南》、《bewCloud v4.21.0 Docker Compose 部署指南》、《bewCloud v4.21.0 配置参数详解》