Notifo v1.3.0 API 使用详解

Notifo 1.3.0 API 实战详解:X-ApiKey 与 OAuth client_credentials 双认证、全端点总览、创建应用到 Mailhog 收信的全链路 curl 实测回显,以及 204 假成功等实测坑。

NotifoNotifoAPIWebhook2026/10/30

Notifo v1.3.0 API 使用详解

目标:给对接者一份「照着敲就能通」的 Notifo 1.3.0 API 实战手册——三种认证方式、全端点总览、从创建 App 到收到邮件的全链路 curl 实测(含真实回显),以及只在实测中才会暴露的坑(204 假成功、405 错端点、Bearer 401 等)。本文所有回显均来自本地 1.3.0 实例(2026-10-30 实测)。

一、环境信息

项目内容
服务版本Notifo 1.3.0(squidex/notifo:1.3.0)
API 基地址http://192.168.1.200:8151(下文简写为 $B,替换为你的部署地址)
API 规范运行实例自带 OpenAPI:GET /api/openapi.json 或 /swagger/v1/swagger.json(实测均返回 200)
认证实测应用 API Key、用户 API Key、OAuth client_credentials 三种均实测通过
部署环境三容器 Compose(notifo + mongo:5 + mailhog),Mailhog UI http://192.168.1.200:8150

二、前置条件

  1. 已按《Notifo v1.3.0 Docker Compose 部署指南》完成部署并登录 Dashboard;
  2. 已创建 App 并拿到一把 Admin 角色的 API Key(App 页可见,32 位随机串形如 gdxvtksxxo9...);
  3. 若要验证 Email 渠道,App 内已启用 SMTP 集成(测试环境指向 Mailhog:主机 notifo-mailhog、端口 1025,实测保存后状态即 Verified)。

三、认证方式(三种,均实测)

Notifo 的 API Key 认证用的是自定义请求头 X-ApiKey,而不是常规的 Bearer——这是对接第一坑。

3.1 应用 API Key(服务端对接主通道)

curl -s -H "X-ApiKey: $APPKEY" "$B/api/apps/$APP/users?take=1"
# 实测 200:{"items":[{"id":"user-1","apiKey":"homvt2...","fullName":"Demo User One",...}],"total":1}

3.2 用户 API Key(用户自操作通道)

用户对象自带 apiKey 字段(创建用户时返回),用它访问 /api/me/* 域接口,即「用户视角」的能力(查自己的通知/订阅、自行订阅、给自己发推送):

curl -s -H "X-ApiKey: $USERKEY" "$B/api/me"
# 实测 200:{"fullName":"Demo User One","emailAddress":"user1@example.com",
#           "preferredLanguage":"en","preferredTimezone":"UTC","supportedLanguages":["en"],...}

3.3 OAuth client_credentials(自动化脚本)

在环境变量 IDENTITY__ADMINCLIENTID/ADMINCLIENTSECRET 配置了客户端后(部署篇默认 notifo-admin / q1w2e3r4):

curl -s -X POST "$B/connect/token" \
  -d "grant_type=client_credentials&client_id=notifo-admin&client_secret=q1w2e3r4&scope=NotifoAPI"
# 实测 200:{"access_token":"eyJhbGciOiJSU0EtT0FFUCIs...(JWE)","token_type":"Bearer","expires_in":2592000}

得到的 token 用标准 Bearer 头调用(有效期 30 天):

curl -s -H "Authorization: Bearer $TOKEN" "$B/api/apps"
# 实测 200:[{"id":"8360bb27-...","name":"demo","role":"Owner","apiKeys":{...}}]

⚠️ 实测坑:把 API Key 直接放进 Authorization: Bearer <apiKey> 会稳定返回 401(应用 Key 与用户 Key 均如此,实测复现)。Bearer 头只服务 OAuth token;API Key 一律走 X-ApiKey。

四、API 总览

以下端点清单提取自运行实例 OpenAPI(/api/openapi.json),按域分组($APP 为 AppId):

域端点方法
应用/api/apps、/api/apps/$APPGET / POST
成员/api/apps/$APP/contributorsPOST
用户/api/apps/$APP/users、/api/apps/$APP/users/{id}GET / POST(批量 upsert)、DELETE
订阅/api/apps/$APP/users/{id}/subscriptions、DELETE .../subscriptions/{prefix}GET / POST / DELETE
主题/api/apps/$APP/topics、DELETE .../topics/{path}GET / POST / DELETE
发布/api/apps/$APP/eventsPOST(PublishManyDto)
通知/api/apps/$APP/notificationsGET(列表+渠道状态)
用户通知/api/apps/$APP/users/{id}/notificationsGET
集成/api/apps/$APP/integration、/integrations、`PUTDELETE /integrations/{id}`
Email 模板/api/apps/$APP/email-templates(含 /render、/{code}、/{id}/preview)GET / POST / PUT / DELETE
Messaging/SMS 模板/api/apps/$APP/messaging-templates、/sms-templates同上
媒体/api/apps/$APP/mediaGET / POST / DELETE
日志/api/apps/$APP/logsGET
用户自操作/api/me、/api/me/events(给自己发布)、/api/me/subscriptions、/api/me/notifications、/api/me/topicsGET / POST
系统用户/api/system-users(管理员账号管理)GET / POST / PUT / DELETE
元数据/api/languages、/api/timezonesGET
健康探测/healthz、/pingGET

五、全链路实战(创建 App → 收到邮件)

以下一条龙全部实测通过,把 $APPKEY、$B 换成你的值即可复跑。

1)创建 App(也可在 Dashboard 点出来)

curl -s -X POST -H "Content-Type: application/json" \
  -d '{"name":"demo"}' "$B/api/apps"

实测回显(节选,注意 apiKeys 就是现成的调用凭据):

{"id":"8360bb27-7714-4e45-8aca-1437184f1bd4","name":"demo","role":"Owner",
 "languages":["en"],
 "apiKeys":{"gdxvtksxxo9co9sj1k1fxatu8pcpzaptpt7tekpkt8ex":"Admin", ...}}

2)创建用户(批量 upsert,body 为数组)

curl -s -X POST -H "X-ApiKey: $APPKEY" -H "Content-Type: application/json" \
  -d '[{"id":"user-1","fullName":"Demo User One","emailAddress":"user1@example.com",
        "preferredLanguage":"en","preferredTimezone":"UTC"}]' \
  "$B/api/apps/$APP/users"

实测回显(用户 API Key 由此返回,3.2 节要用):

[{"id":"user-1","apiKey":"homvt2cyyyieitq5x7xfbzqpgm8ftyjsjpv6uctmovcx",
  "fullName":"Demo User One","emailAddress":"user1@example.com",...}]

3)为用户订阅主题

curl -s -X POST -H "X-ApiKey: $APPKEY" -H "Content-Type: application/json" \
  -d '{"subscribe":[{"topicPrefix":"releases/v130"}]}' \
  "$B/api/apps/$APP/users/user-1/subscriptions"
# 实测:204 No Content(空体即成功)

随后 GET 同路径可看到 {"items":[{"topicPrefix":"releases/v130","topicSettings":{}}],"total":1}。

4)发布通知(核心端点:POST /api/apps/$APP/events)

无模板快速发布用 preformatted,正文按语言键组织:

curl -s -X POST -H "X-ApiKey: $APPKEY" -H "Content-Type: application/json" \
  -d '{"requests":[{"topic":"releases/v130",
        "preformatted":{"subject":{"en":"v1.3.0 released"},
                        "body":{"en":"Notifo 1.3.0 is out, published via POST /api/apps/{appId}/events."}},
        "properties":{"version":"1.3.0"}}]}' \
  "$B/api/apps/$APP/events"
# 实测:204 No Content,分发为异步,通常 1~4 秒完成

PublishDto 常用字段(来自实例 OpenAPI,仅 topic 必填):

字段类型说明
topicstring目标主题路径(发给所有前缀匹配的订阅者)
templateCodestring走模板发布时的模板 code
preformattedobject免模板直发:subject/body 均为 {语言: 文本}
propertiesobject自定义属性,模板中引用,也会出现在通知详情
datastring业务侧附加数据(原样透传)
correlationIdstring业务关联 ID,可用于检索
schedulingobject延迟/定时发送
silentboolean静默通知
testboolean走测试集成(避免打扰真实用户)
timeToLiveInSecondsinteger过期时长,过期不再投递

5)确认通知与渠道状态

curl -s -H "X-ApiKey: $APPKEY" "$B/api/apps/$APP/notifications?take=1"

实测回显(节选):email 与 web 双渠道均 Handled,并给出跟踪 URL:

{"channels":{"email":{"status":{"f2fc0c41-...":{"status":"Handled",
   "configuration":{"EmailAddress":"user1@example.com"}}}},
 "web":{"status":{"651071e3-...":{"status":"Handled"}}}},
 "subject":"v1.3.0 released","created":"2026-10-30T00:33:21.345Z",
 "trackSeenUrl":"http://192.168.1.200:8151/api/tracking/notifications/.../seen?culture=en",
 "trackDeliveredUrl":"http://192.168.1.200:8151/api/tracking/notifications/.../delivered?culture=en"}

6)验证邮件落袋(Mailhog)

curl -s "http://192.168.1.200:8150/api/v2/messages?limit=1"

实测 Mailhog 收到主题 Your notifications from demo 的摘要邮件,text/plain 部分节选:

We have some notifications from demo for you.

v1.3.0 released
Notifo 1.3.0 is out, published via POST /api/apps/{appId}/events.

Use the following link to change your email preferences:
http://192.168.1.200:8151/api/email-preferences?access_token=...

💡 这封就是 Notifo 的 Email Digest:同一用户短时间内的多条通知合并成一封按语言渲染的摘要邮件,收件人可从邮件直达偏好设置页关停渠道。

六、各 API 域详解

App 域:GET /api/apps 列出当前凭据可见的应用;POST /api/apps 创建(body {"name":...},可带 languages);对单个 App 的 POST /api/apps/$APP 可更新语言等属性。

用户域:POST /api/apps/$APP/users 批量 upsert(按 id 幂等,改邮箱/属性再 POST 同 id 即更新);GET .../users?take=&skip= 分页;DELETE .../users/{id} 删除。实测用户对象含 settings(每渠道偏好)、requiresWhitelistedTopics、allowedTopics 等字段。

订阅域:POST .../users/{id}/subscriptions body 为 {"subscribe":[{"topicPrefix":"...","topicSettings":{...}}],"unsubscribe":["books/old"]},同请求可同时增删;topicSettings 可按渠道覆写发送行为(delay、专用模板等)。实测删除用 DELETE .../subscriptions/{prefix}(前缀需 URL 编码斜杠)。

主题域:GET /api/apps/$APP/topics 列出已出现过的主题与订阅计数;POST 可手工登记主题(配合用户 requiresWhitelistedTopics 做白名单发放)。

发布域:见第五节。用户自通道 POST /api/me/events body 为单个 PublishDto(用户 API Key 鉴权),适合「用户自产内容触发站内通知」的场景。

通知域:GET /api/apps/$APP/notifications 是管理视角的全量通知列表,GET .../users/{id}/notifications 是单用户视角;列表项含每渠道 status(Handled/Skipped/Failed 等)与 firstDelivered/firstSeen 时间。跟踪 URL 需暴露给客户端回调以完善已读统计。

集成域:POST /api/apps/$APP/integration 启用集成(实测 SMTP 配置体:

{"type":"SMTP","properties":{"host":"notifo-mailhog","port":"1025",
 "fromEmail":"notifo@example.com","fromName":"Notifo Demo"},"enabled":true}

保存后返回 "status":"Verified")。GET /integrations 列出已启用项,PUT/DELETE /integrations/{id} 维护。

模板域:Email 模板按 code + 语言组织,PUT .../email-templates/{code}/{language} 设置某语言版本;POST .../email-templates/render 可以用假数据渲染预览。Messaging/SMS 模板结构类似。实测一个坑:Email 模板 language 字段必填,缺失时返回 400 {"details":["The Language field is required."],"errorCode":"VALIDATION_ERROR"}。

日志域:GET /api/apps/$APP/logs 返回 App 级系统日志(聚合去重),实测两条关键日志:Event has no subscriber.(发布时无订阅者)与 EMAIL: Cannot find template 'Unnamed'.(Email 投递缺模板)——排障先看这里。

系统用户域:/api/system-users 管理 Dashboard 登录账号(含 lock/unlock),区别于收通知的业务用户。

七、通用约定

  • 204 即成功:订阅、发布等写操作统一返回 204 No Content 空体,没有返回体可解析,成败靠后续查询或日志确认;
  • 错误结构:4xx/5xx 统一为 {"message":...,"errorCode":...,"details":[...],"traceId":...,"statusCode":...}(实测 VALIDATION_ERROR 400 与 401 均符合);
  • 分页:列表接口用 take + skip(实测 ?take=1 生效),响应含 items/total;
  • 多语言文本:所有可翻译字段均为 {语言键: 文本} 字典,如 "subject":{"en":"Hello","zh-CN":"你好"};
  • 主题层级:/ 分层(如 books/novels),订阅是前缀匹配——订阅 books 可收到 books/novels 的通知;实测单级主题(solo)同样合法;
  • 异步投递:POST /events 入队即返回,渠道投递在后台完成(实测 1~4 秒),客户端轮询通知列表或看日志确认结果。

八、常见问题(FAQ)

Q1:POST /api/apps/{appId}/notifications 发布为什么返回 405? 该路径只有 GET(列表)。1.3.0 的发布端点是 POST /api/apps/{appId}/events(实测 405 复现后经实例 OpenAPI 确认)。

Q2:订阅返回 204 但订阅列表没变化? 请求体写成了 {"requests":[...]}(错误)。正确体是 {"subscribe":[{"topicPrefix":...}]}——实测错误的 body 同样返回 204 且静默忽略,必须回查列表确认。

Q3:发布返回 204 但没人收到? 查 GET /api/apps/$APP/logs:若见 Event has no subscriber.,说明主题无匹配订阅者(实测复现:先发布后订阅就会这样)。发布前先完成订阅。

Q4:Email 渠道 Skipped? 日志见 EMAIL: Cannot find template 'Unnamed'.:发布时既无模板又缺 preformatted 文本,或 Email 集成未启用(实测两种情形均复现为 Skipped)。

Q5:Bearer 放 API Key 为什么 401? 见第三节:API Key 必须走 X-ApiKey 头;Authorization: Bearer 仅用于 /connect/token 换取的 OAuth token。

Q6:哪里找完整字段定义? 实例自带 OpenAPI:curl $B/api/openapi.json(实测 200),PublishDto、UpsertUserDto、SubscribeDto 等全部 schema 均在其中,是 1.3.0 最权威的接口文档。

九、参考链接