Novu 3.19.0 API 使用详解
Novu 3.19.0 REST API 完整调用手册:v1/v2 双前缀端点地图、双认证方式实测、触发/收件箱/偏好/Topics 各域请求响应回显、分页约定与错误码实测。
Novu 3.19.0 API 使用详解
目标:给出 Novu 3.19.0 自托管 REST API 的完整调用手册——双版本前缀(v1/v2)的端点地图、两种认证方式、各业务域的请求/响应实测与错误码。全部请求经 curl 在自托管 Novu 3.19.0 上实测,响应 JSON 均为真实回显(部署环境见 《Docker Compose 部署 Novu 指南》,参数体系见 《Novu 3.19.0 配置参数详解》)。
一、环境信息
- API 基址:
http://192.168.146.128:8148(api 服务,宿主端口按部署实际;以下示例统一此基址) - 服务版本:3.19.0(健康检查实测
apiVersion: 3.19.0) - 客户端:curl 7.81.0(Ubuntu 22.04 自带);官方另有 Node.js/Python/Go/PHP/.NET/Java 六种 Server SDK(
@novu/api、novu-py、novu-go等) - API 规范:官方 OpenAPI 3.0 规范可从 https://docs.novu.co/openapi.json 获取(本文撰写时该规范标注 info.version 3.15.0,端点结构与 3.19.0 实测一致)
- 测试账号:
admin@quzuo.net(实测注册);演示 ApiKey:16c57f0a2dc1df7fe7ad4efc6fe3a5ee(实测环境 Development 环境的 key,读者部署后以自己环境为准)
二、前置条件
- 已按部署指南完成 Novu 3.19.0 部署,
/v1/health-check返回 ok; - 准备 curl(或任意 HTTP 客户端);JSON 请求体一律
Content-Type: application/json; - 一个已登录管理账号(用于换取组织上下文与 ApiKey,见第四章)。
三、API 总览
3.1 双版本前缀与端点地图
Novu API 同时存在 /v1 与 /v2 两套前缀,同一资源可能分属两套,这是 v3.19.0 实测最容易踩的坑:
| 业务域 | v1 端点(仍活跃) | v2 端点(新体系) |
|---|---|---|
| 认证/组织/环境 | /v1/auth/register, /v1/auth/login, /v1/organizations, /v1/environments | — |
| 订阅者 | /v1/subscribers(创建/列表) | /v2/subscribers/{subscriberId}(详情/改/删)、/v2/subscribers(分页列表) |
| Workflow | /v1/workflows(旧契约,见 5.2) | /v2/workflows(增删改查/预览/发布 diff) |
| 触发 | /v1/events/trigger、/trigger/bulk、/trigger/broadcast、DELETE /v1/events/trigger/{transactionId} | — |
| 消息管理 | /v1/messages、/v1/notifications、/v1/subscribers/{id}/notifications/feed 等 | /v2/subscribers/{id}/notifications(feed 变体)、preferences、archive/read 等 |
| Topics | — | /v2/topics 全套 |
| 集成 | /v1/integrations、/v1/integrations/active | — |
| 其他 | /v1/notification-groups、/v1/layouts(v2)、/v2/contexts、/v2/translations | — |
完整端点清单建议从官方 OpenAPI 规范导出核对;本文第五章覆盖日常高频域。
3.2 认证方式(实测两种)
- ApiKey 认证(服务端调用业务 API 的主方式):请求头
Authorization: ApiKey <环境的ApiKey>; - Bearer JWT 认证(管理面/账号操作):请求头
Authorization: Bearer <登录token>,用于 organizations/environments 等管理端点。
两种方式混用的实测表现:对 /v1/subscribers 用 Bearer 管理态 token 会得到鉴权失败(该端点只认 ApiKey),反之管理端点用 ApiKey 亦不通——按域选认证方式。
3.3 统一错误结构与限流
错误响应统一为(实测回显):
{"error":"Unauthorized","statusCode":401,"timestamp":"2026-10-21T14:14:59.781Z","path":"/v1/subscribers","message":"API Key not found","ctx":{"error":"Unauthorized","statusCode":401}}
校验失败统一 HTTP 422,errors 字段携带按字段分组的消息数组。限流由 IS_API_RATE_LIMITING_ENABLED 控制(默认关闭,见配置参数详解篇)。
四、认证与首次调用(curl 实测)
4.1 注册首个账号
curl -sS -X POST http://192.168.146.128:8148/v1/auth/register \
-H 'Content-Type: application/json' \
-d '{"email":"admin@quzuo.net","password":"q1w2e3r4","firstName":"Admin","lastName":"Novu"}'
密码不满足策略时的实测回显(HTTP 422):
{"statusCode":422,"timestamp":"2026-10-21T14:14:08.741Z","path":"/v1/auth/register","message":"Validation Error","ctx":{},"errors":{"general":{"messages":["The password must contain minimum 8 and maximum 64 characters, at least one uppercase letter, one lowercase letter, one number and one special character #?!@$%^&*()-"],"value":"No Value Recorded"}}}
按策略改用 Q1w2e3r4# 重试(实测回显,token 为超长 JWT,截断展示):
{"data":{"user":{"_id":"6ad8c8bca08bbb1cea67c940","firstName":"admin","lastName":"novu","email":"admin@quzuo.net","createdAt":"2026-10-21T14:14:20.888Z","id":"6ad8c8bca08bbb1cea67c940"},"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2YWQ4YzhiY2EwOGJiYjFjZWE2N2M5NDAi..._UJeID7Do8bn8a0zFYj28n9G5Pdr0TA28k1EqhmfCK0"}}
⚠️ 此 token 的 organizationId 为 null(注册后尚未归属组织),还不能访问环境类资源。
4.2 创建组织并换取组织上下文 token
TOKEN=$(python3 -c "import json;print(json.load(open('r_auth_register.json'))['data']['token'])")
curl -sS -X POST http://192.168.146.128:8148/v1/organizations \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Quzuo Lab"}'
{"data":{"productUseCases":{"delay":false,"translation":false,"digest":false,"multi_channel":false,"in_app":false,"agents":false},"_id":"6ad8c8c6a08bbb1cea67c947","name":"Quzuo Lab","apiServiceLevel":"free","isTrial":false,"language":[],"createdAt":"2026-10-21T14:14:30.327Z","id":"6ad8c8c6a08bbb1cea67c947"}}
重新登录换发带组织上下文的 token(实测,本次登录同时自动初始化 Development/Production 两环境):
curl -sS -X POST http://192.168.146.128:8148/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"admin@quzuo.net","password":"Q1w2e3r4#"}'
4.3 获取环境与 ApiKey
curl -sS http://192.168.146.128:8148/v1/environments -H "Authorization: Bearer $TOKEN2"
实测回显(节选,关键字段完整保留):
{"data":[{"_id":"6ad8c8c6a08bbb1cea67c94e","name":"Development","identifier":"eMFf1kH9DNd3","_organizationId":"6ad8c8c6a08bbb1cea67c947","apiKeys":[{"key":"16c57f0a2dc1df7fe7ad4efc6fe3a5ee","hash":"2d2fc9bb8bb404f9b64b1acf148dd73a9092e461c3ba676a8d8ec768505f4451","_id":"6ad8c8c6a08bbb1cea67c94f"}],"color":"#ff8547","type":"dev","slug":"dev_env_h0LqSOJBBGoWiDoU"},{"_id":"6ad8c8c6a08bbb1cea67c977","name":"Production","identifier":"eCsunyMzt0eL","apiKeys":[{"key":"b4a8911bcecab6383c7cfd21a200973c","hash":"e137c9ced9c33b15fad22a841dc42b4f4141a60ed63017f18dccd8ef6da17d7e"}],"color":"#7e52f4","type":"prod","slug":"prod_env_h0LqSOJBBGoWiDp9"}]}
拿到 ApiKey 即可开始业务调用(后续示例统一用 Development 环境的 key)。
五、各 API 域详解
5.1 Subscribers 域
创建订阅者(端点 POST /v1/subscribers,参数 subscriberId 必填且业务侧唯一):
curl -sS -X POST http://192.168.146.128:8148/v1/subscribers \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee' \
-H 'Content-Type: application/json' \
-d '{"subscriberId":"user-001","email":"alice@example.com","firstName":"Alice","lastName":"Chen","locale":"zh-CN"}'
{"data":{"_id":"6ad8c8eba08bbb1cea67c998","_organizationId":"6ad8c8c6a08bbb1cea67c947","_environmentId":"6ad8c8c6a08bbb1cea67c94e","firstName":"Alice","lastName":"Chen","subscriberId":"user-001","email":"alice@example.com","locale":"zh-CN","channels":[],"isOnline":false,"deleted":false,"createdAt":"2026-10-21T14:15:07.885Z","id":"6ad8c8eba08bbb1cea67c998"}}
列表(GET /v1/subscribers,平铺分页结构,实测空库):
{"page":0,"hasMore":false,"pageSize":10,"data":[]}
详情与错误码(GET /v2/subscribers/{subscriberId},查询不存在的 ID 实测 404):
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://192.168.146.128:8148/v2/subscribers/not-exist-999 \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee'
HTTP 404
{"error":"Not Found","statusCode":404,"timestamp":"2026-10-21T14:18:17.024Z","path":"/v2/subscribers/not-exist-999","message":"Subscriber: not-exist-999 was not found","ctx":{"error":"Not Found","statusCode":404}}
5.2 Workflows 域
创建(v2 契约):POST /v2/workflows,必填 name/workflowId/steps;步骤 type 为小写枚举(in_app/email/sms/push/chat/delay/digest/throttle/custom/http_request),In-App 内容写进 controlValues:
curl -sS -X POST http://192.168.146.128:8148/v2/workflows \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee' \
-H 'Content-Type: application/json' \
-d '{"name":"Order Notify","workflowId":"order-notify","description":"订单状态通知","steps":[{"type":"in_app","name":"In-App Step","stepId":"inapp-step-1","controlValues":{"subject":"订单通知","body":"订单 {{payload.orderId}} 已发货,请查收"}}]}'
实测回显(截断保留关键字段):
{"data":{"_id":"6ad8c943a08bbb1cea67c9c8","slug":"order-notify_wf_h0LrAheQct1xRbui","workflowId":"order-notify","name":"Order Notify","active":true,"steps":[{"controlValues":{"subject":"订单通知","body":"订单 {{payload.orderId}} 已发货,请查收"},"name":"In-App Step","stepId":"inapp-step-1","type":"in_app","issues":{}}],"description":"订单状态通知","status":"ACTIVE"}}
实测踩坑一:旧端点契约。对 /v1/workflows 提交同样的步骤会得到两类 422(实测回显):
{"statusCode":422,"path":"/v1/workflows","message":"Validation Error","errors":{"general":{"messages":["Notification group must be provided ","notificationGroupId must be a string"]}}}
补齐 notificationGroupId 后继续报 {"message":"Unexpected error: message template is missing"}。结论:v3.19.0 的 workflow 管理一律走 /v2/workflows,不要用旧端点。
实测踩坑二:模板变量命名空间。controlValues 里写裸变量 {{orderId}} 时,创建虽成功但响应的 steps[].issues.controls 会报错(实测回显):
{"issues":{"controls":{"body":[{"message":"Variable \"orderId\" invalid or missing namespace","issueType":"ILLEGAL_VARIABLE_IN_CONTROL_VALUE","variableName":"orderId"}]}}}
改为 {{payload.orderId}} 后 issue 清空。可用变量命名空间实测归纳:payload.*(触发入参)、subscriber.*、actor.*、workflow.*、steps.*(前序步骤输出)、context.*、env.*。
更新:PUT /v2/workflows/{workflowId}(全量提交 name/steps 等,实测同创建参数结构,成功后 steps[].issues 归零);列表:GET /v2/workflows;详情:GET /v2/workflows/{workflowId}(实测返回 workflowId/status/steps/issues 全量结构,见 5.3 触发后的联动核验)。
5.3 Events 触发域
单发(multicast 到指定订阅者):POST /v1/events/trigger,name 填 workflowId:
curl -sS -X POST http://192.168.146.128:8148/v1/events/trigger \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee' \
-H 'Content-Type: application/json' \
-d '{"name":"order-notify","to":{"subscriberId":"user-001"},"payload":{"orderId":"SO-2026-1021"}}'
{"data":{"acknowledged":true,"status":"processed","transactionId":"txn_6ad8c961wo49me5f0qna"}}
批量:POST /v1/events/trigger/bulk,事件数组放进 events 键(实测直接传数组会 422 events should not be empty,正确形态如下):
curl -sS -X POST http://192.168.146.128:8148/v1/events/trigger/bulk \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee' \
-H 'Content-Type: application/json' \
-d '{"events":[{"name":"order-notify","to":{"subscriberId":"user-001"},"payload":{"orderId":"SO-BULK-01"}},{"name":"order-notify","to":{"subscriberId":"user-001"},"payload":{"orderId":"SO-BULK-02"}}]}'
{"data":[{"acknowledged":true,"status":"processed","transactionId":"txn_6ad8c9a721bazjzj8vu3"},{"acknowledged":true,"status":"processed","transactionId":"txn_6ad8c9a77hxdo3kj6gkh"}]}
全员广播:POST /v1/events/trigger/broadcast(不带 to,面向环境内全部订阅者):
{"data":{"acknowledged":true,"status":"processed","transactionId":"txn_6ad8ce7box9vwjtnb76n"}}
Topic 组播(已知限制,实测):to 用 {"topicKey":"order-updates"} 触发同样返回 processed(实测 transactionId: txn_6ad8c9e7q3yf5nrjizo4),但消息不落订阅者收件箱:api 日志确认事件已投递 Workflow 队列,worker 无消费记录,MongoDB notifications/jobs 集合无对应记录(订阅关系本身正常,见 5.6)。3.19.0 自托管版生产使用 topic 广播前务必先验证该路径。
触发结果核验:GET /v1/notifications 列出环境内全部通知(实测单发 1 条 + bulk 2 条 = 3 条,每条带 to/transactionId),配合 5.4 的 feed 双向核验消息是否真正落地。
5.4 Inbox 消息域(feed / 未读数 / 已读标记)
收件箱 feed:GET /v1/subscribers/{subscriberId}/notifications/feed:
curl -sS 'http://192.168.146.128:8148/v1/subscribers/user-001/notifications/feed' \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee'
实测回显(节选,保留全部业务字段):
{"data":[{"cta":{"action":{"buttons":[]},"type":"redirect"},"_id":"6ad8c961ad1cd2fdfdc2d0e3","templateIdentifier":"order-notify","stepId":"inapp-step-1","subject":"订单通知","channel":"in_app","content":"订单 SO-2026-1021 已发货,请查收","providerId":"novu","seen":false,"read":false,"archived":false,"status":"sent","transactionId":"txn_6ad8c961wo49me5f0qna","payload":{"orderId":"SO-2026-1021"},"severity":"none","createdAt":"2026-10-21T14:17:05.490Z","subscriber":{"subscriberId":"user-001","firstName":"Alice","lastName":"Chen"}}],"totalCount":1,"hasMore":false,"pageSize":10,"page":0}
payload 变量渲染确认:content 已把 {{payload.orderId}} 替换为实际值。
未读数:GET /v1/subscribers/{subscriberId}/notifications/unseen,实测 {"data":{"count":1}}。
标记已读/已看:POST /v1/subscribers/{subscriberId}/messages/mark-as。⚠️ 实测参数名为 markAs(枚举 read/seen/unread/unseen 单值)+ messageId 数组;写成 {"mark":{"seen":true,"read":true}} 会 422(实测回显:markAs must be one of the following values: read, seen, unread, unseen):
curl -sS -X POST 'http://192.168.146.128:8148/v1/subscribers/user-001/messages/mark-as' \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee' \
-H 'Content-Type: application/json' \
-d '{"messageId":["6ad8c961ad1cd2fdfdc2d0e3"],"markAs":"read"}'
{"data":[{"_id":"6ad8c961ad1cd2fdfdc2d0e3","subject":"订单通知","channel":"in_app","read":true,"seen":true,"lastSeenDate":"2026-10-21T14:17:36.342Z","lastReadDate":"2026-10-21T14:17:36.342Z","status":"sent"}]}
复查 unseen 实测归零:{"data":{"count":0}}。
5.5 Preferences 域
GET /v2/subscribers/{subscriberId}/preferences 返回全局偏好与按工作流偏好(实测回显,节选):
{"global":{"enabled":true,"channels":{"in_app":true}},"workflows":[{"enabled":true,"channels":{"in_app":true},"overrides":[{"channel":"in_app","source":"subscriber"}],"workflow":{"slug":"order-notify_wf_h0LrAheQct1xRbui","identifier":"order-notify","name":"Order Notify"}}]}
修改走 PATCH /v2/subscribers/{subscriberId}/preferences(body 携带 workflowId 与渠道开关),批量 PATCH .../preferences/bulk。
5.6 Topics 域
创建:POST /v2/topics(key 全环境唯一):
{"_id": "6ad8c98ca08bbb1cea67ca73", "name": "订单动态", "key": "order-updates", "createdAt": "2026-10-21T14:17:48.126Z"}
订阅:POST /v2/topics/{topicKey}/subscriptions。⚠️ 实测参数结构为数组 subscriptions(每项含 subscriberId 与可选 identifier),写成 {"subscribers":["user-001"]} 会 422 At least one subscription is required:
curl -sS -X POST http://192.168.146.128:8148/v2/topics/order-updates/subscriptions \
-H 'Authorization: ApiKey 16c57f0a2dc1df7fe7ad4efc6fe3a5ee' \
-H 'Content-Type: application/json' \
-d '{"subscriptions":[{"subscriberId":"user-001","identifier":"sub-user-001"}]}'
{"data": [{"_id": "6ad8c99cedc396bd6ca5f3bc", "identifier": "sub-user-001", "topic": {"key": "order-updates", "name": "订单动态"}, "subscriber": {"subscriberId": "user-001", "firstName": "Alice", "lastName": "Chen"}}]}
订阅关系查询:GET /v2/topics/{topicKey}/subscriptions(实测返回上述订阅项数组)。另有旧式 POST /v1/subscribers/{subscriberId}/subscriptions/{topicKey} 等端点,建议统一走 v2。
六、分页/过滤/排序通用约定
实测两种响应形态并存,解析前先判断结构:
- v1 平铺形态(GET /v1/subscribers、GET /v1/messages、feed 等):
{"data":[...],"page":0,"hasMore":false,"pageSize":10},feed 额外带totalCount; - v2 嵌套形态(GET /v2/workflows 实测):
{"data":{"workflows":[...],"totalCount":1,...}}——data是对象不是数组,按业务键再取数组; - 过滤参数:feed/messages 支持
channel(如channel=in_app)、分页参数page/limit;GET /v1/notifications支持 transactionId 等查询参数; - 排序:列表默认按创建时间倒序(实测 feed 最新消息在最前)。
七、常见问题(FAQ)
Q1:Authorization: ApiKey <key> 返回 401 API Key not found?
key 属于环境,确认用的是 GET /v1/environments 返回的 apiKeys[].key 明文(不是 hash),且当前环境与资源所属环境一致;跨环境资源(如用 Development 的 key 访问 Production 的 workflow)同样按不存在处理。
Q2:创建 workflow 一路 422?
先确认打的是 /v2/workflows 而不是 /v1/workflows(见 5.2 踩坑一);再对照必填项 name/workflowId/steps 与步骤小写枚举;模板变量必须带命名空间({{payload.xxx}}),裸变量不会创建失败但会在 issues 里报 ILLEGAL_VARIABLE_IN_CONTROL_VALUE 并可能导致渲染为空。
Q3:触发返回 processed 但收件箱没消息?
按链路核验:GET /v1/notifications 看通知记录是否生成 → feed 按 transactionId 搜 → api/worker 日志按 transactionId 搜。实测中单发/bulk/broadcast 正常;topic 组播存在不落地问题(见 5.3 已知限制)。另外 workflow 的 steps[].issues 出现 MISSING_INTEGRATION(Inbox is not connected)时,检查环境内 in_app 集成状态(实测该 issue 出现在新建 workflow 的校验提示中,In-App 消息投递不受影响)。
Q4:bulk 触发报 events should not be empty?
请求体必须是 {"events":[...]} 包裹,直接传顶层数组会 422(见 5.3)。
Q5:mark-as 的参数到底怎么写?
{"messageId":["<消息ID>"],"markAs":"read"}——markAs 是单值枚举不是对象(见 5.4 实测对比)。
八、参考链接
- 官方 OpenAPI 规范(全端点):https://docs.novu.co/openapi.json
- 官方 Server SDK 文档:https://docs.novu.co/platform/sdks
- 官方触发文档:https://docs.novu.co/platform/trigger-action
- 站内同专栏:《Docker Compose 部署 Novu 指南》、《Novu 3.19.0 配置参数详解》、《Novu 3.19.0 介绍与选型指南》