账户与组织
身份验证、会话、个人资料与安全设置、组织管理、团队和订阅计费。
本组接口涵盖个人账户及其所属工作区相关功能:登录、会话管理、个人资料和安全设置(通知偏好、双因素身份验证、通行密钥)、组织管理(成员、邀请、自定义角色)、成员团队分组,以及账单。
这里几乎所有端点都仅接受会话,依赖绑定到具体用户的 JWT,API 密钥无法访问。有两类例外:团队端点同时接受 JWT 和 API 密钥;公开身份验证端点,包括登录、注册、刷新、密码重置、通行密钥登录和双因素验证,本身无需会话,因为它们用于创建会话。下方各端点均明确标注身份验证要求。
所有错误响应采用共享封装(error、message、code、request_id)。参阅错误码。
身份验证
这些端点公开开放,无需会话。登录和注册分两步:首次调用通过邮件发送一次性代码,并返回短期签名 session 令牌;第二次调用确认代码,签发正式令牌对。部署启用验证码时,启动调用和两个密码重置调用都需要验证码:浏览器提供 Cloudflare Turnstile 令牌,原生应用提供App Attest 证明。
开始登录
POST /auth/login
向账户邮件地址发送登录代码,并返回签名会话令牌,供确认步骤使用。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 账户邮件地址。 |
password | string | 是 | 账户密码。 |
turnstile | string | 必填,除非发送了 app_attest | Cloudflare Turnstile 令牌。 |
app_attest | object | 否 | 原生应用的App Attest 证明,替代 turnstile 发送。 |
{
"email": "[email protected]",
"password": "correct horse battery staple",
"turnstile": "0.abc123..."
}响应
{
"session": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}确认登录
POST /auth/login/confirm
使用邮件代码和会话令牌换取令牌对。账户启用双因素身份验证时,不返回令牌对;two_fa_required 为 true,改为返回一次性的 pending_token,需将其传给 /auth/2fa/verify/。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
session | string | 是 | /auth/login 返回的令牌。 |
code | string | 是 | 邮件中的一次性代码。 |
turnstile | string | 否 | Cloudflare Turnstile 令牌。 |
{
"session": "eyJhbGciOiJIUzI1NiIs...",
"code": "489210"
}响应
成功时返回完整令牌对:
{
"access_token": "eyJhbGc...",
"access_token_expires_at": "2026-06-12T13:00:00Z",
"refresh_token": "eyJhbGc...",
"refresh_token_expires_at": "2026-07-12T12:00:00Z"
}启用双因素身份验证时,返回验证挑战,而非会话:
{
"two_fa_required": true,
"pending_token": "eyJhbGc...",
"expires_in": 300
}开始注册
POST /auth/register
创建待确认注册,并通过邮件发送确认代码。返回供确认步骤使用的签名会话令牌。
身份验证:公开端点,API 密钥不可用。
请求体
结构与 /auth/login 相同:email、password,以及 turnstile 或 app_attest。
{
"email": "[email protected]",
"password": "correct horse battery staple",
"turnstile": "0.abc123..."
}响应
{
"session": "eyJhbGciOiJIUzI1NiIs..."
}确认注册
POST /auth/register/confirm
确认邮件代码,创建账户并登录:响应结构与完成 /auth/login 相同,即 token 对或 two_fa_required 挑战,因此客户端可直接进入控制台,无需再次登录。部署不要求验证邮件地址时,/auth/register 本身会直接返回该结构。地址已存在账户时,返回 409 conflict 并拒绝,参阅错误码。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
session | string | 是 | /auth/register 返回的令牌。 |
code | string | 是 | 邮件中的一次性代码。 |
turnstile | string | 否 | Cloudflare Turnstile 令牌。 |
响应
{
"code_required": false,
"token": { "access_token": "…", "refresh_token": "…", "access_token_expires_at": "…" }
}刷新令牌
POST /auth/refresh
使用有效刷新令牌换取新的令牌对。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
refresh_token | string | 是 | 有效且未过期的刷新令牌。 |
{
"refresh_token": "eyJhbGc..."
}响应
新的令牌对,结构与登录确认成功响应相同(access_token、access_token_expires_at、refresh_token、refresh_token_expires_at)。
每次刷新都会轮换令牌对:保存新刷新令牌,丢弃旧令牌。已经交换过的刷新令牌会返回 401 并被拒绝。如果在交换后超过两分钟再次提交,视为重复使用,整个会话将结束,向该会话签发的所有令牌停止工作。两分钟内则视为两个调用者同时刷新,仅拒绝此次请求。
开始重置密码
POST /auth/reset-password
地址存在账户时,通过邮件发送重置代码。始终返回 200 OK,不泄露地址是否存在。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 账户邮件地址。 |
turnstile | string | 必填,除非发送了 app_attest | Cloudflare Turnstile 令牌。 |
app_attest | object | 否 | 原生应用的App Attest 证明,替代 turnstile 发送。 |
响应
200 OK,响应体为空。
确认重置密码
POST /auth/reset-password/confirm
使用邮件重置代码设置新密码,代码携带在签名 session 令牌中。重置令牌只能使用一次,并绑定到请求重置时的密码:无论通过本端点、登录后的修改,还是运维重置,只要密码被写入,此前签发的所有重置令牌都会返回 401,无论自身剩余有效期多长。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
session | string | 是 | 与该邮件地址绑定的重置会话令牌。 |
password | string | 是 | 新密码。 |
turnstile | string | 必填,除非发送了 app_attest | Cloudflare Turnstile 令牌。 |
app_attest | object | 否 | 原生应用的App Attest 证明,替代 turnstile 发送。 |
响应
200 OK,响应体为空。
开始通行密钥登录
POST /auth/passkey/login/begin
启动可发现的、无需用户名的 WebAuthn 断言。返回供浏览器传入 navigator.credentials.get() 的公钥请求选项。
身份验证:公开端点,API 密钥不可用。
响应
WebAuthn 断言选项对象,包括挑战、RP ID、允许的凭据和超时,可直接传给 WebAuthn API。完成步骤需回传内嵌的 session。
完成通行密钥登录
POST /auth/passkey/login/finish
验证签名断言,并签发令牌对。通行密钥属于强身份验证,因此没有邮件代码步骤。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
session | string | 是 | 开始步骤返回的会话值。 |
credential | object | 是 | 浏览器返回的原始 WebAuthn 断言(PublicKeyCredential)。 |
响应
令牌对,结构与登录确认成功响应相同。
验证双重身份验证登录
POST /auth/2fa/verify
使用需双因素验证的登录返回的一次性 pending_token,加上 TOTP 或恢复码,换取正式会话。
身份验证:公开端点,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
pending_token | string | 是 | /auth/login/confirm 返回的 pending_token。 |
code | string | 是 | 当前有效的 TOTP 代码,或未使用过的恢复码。 |
{
"pending_token": "eyJhbGc...",
"code": "123456"
}响应
令牌对,结构与登录确认成功响应相同。
第三方账户登录(原生应用)
三个端点均公开开放,API 密钥不可用。服务商验证的身份视为强身份验证,因此没有邮件代码步骤;首次登录会创建账户、组织和试用。
| 端点 | 请求体 | 用途 |
|---|---|---|
GET /auth/providers | 无 | 本部署支持的原生登录选项,使移动应用无需重新构建即可适配自托管后端。返回 {apple, google} 对象,包含 enabled 和公开的 client_id。 |
POST /auth/apple | identity_token(必填)、first_name、last_name | 交换“通过 Apple 登录”的身份令牌,使用 Apple 密钥验证。Apple 只会将姓名提供给应用,因此首次登录时应将姓名传入,用于预填。 |
POST /auth/google | id_token(必填) | 交换 Google Sign-In ID 令牌,使用 Google 密钥及 /auth/providers 中的客户端 ID 验证。 |
Apple 和 Google 均返回令牌对,结构与登录确认成功响应相同;账户已启用 TOTP 时则返回双因素验证挑战。
App Attest(原生应用)
iOS 应用通过 Apple App Attest 证明自身为真实应用,该证明在需要验证码的调用中替代验证码。浏览器仍发送 Turnstile 令牌。下方两个端点均公开开放,API 密钥不可用,并共用独立的每 IP 额度(APP_ATTEST_IP_RATE_LIMIT,默认每十五分钟 300 次),与其余 /auth 接口的额度分开。实例关闭 App Attest 时,返回 404,错误码为 app_attest_unavailable。GET /auth/config 通过 app_attest 报告是否开启。
| 端点 | 请求体 | 用途 |
|---|---|---|
POST /auth/app-attest/challenge | 无 | 返回 {"challenge": "..."},其中的 base64url 字符串只能使用一次,五分钟后过期。每次证明和每次断言都必须使用新的挑战。 |
POST /auth/app-attest/keys | key_id、attestation、challenge,均必填 | 注册该应用安装的 App Attest 密钥,每次安装注册一次。key_id 是 32 字节密钥标识符的 base64 编码,attestation 是 DCAppAttestService.attestKey 返回对象的 base64 编码;其客户端数据哈希为 challenge 的 UTF-8 字节经过 SHA-256 得到的值。返回 204。 |
服务器会根据 Apple App Attest 根证书验证证明的证书链,同时验证随机数、应用 ID、值为 0 的计数器、运行环境(除非实例允许,否则拒绝开发签名构建)和密钥 ID,然后保存密钥。密钥 ID 只能注册一次;再次注册相同 ID 会返回 app_attest_failed 并被拒绝。
之后,需要验证码的调用(POST /auth/login、/auth/register、/auth/reset-password、/auth/reset-password/confirm)携带 app_attest 对象,替代 turnstile:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
key_id | string | 是 | 已注册的密钥标识符,base64 编码。 |
assertion | string | 是 | DCAppAttestService.generateAssertion 返回的 base64 断言。 |
challenge | string | 是 | 从 POST /auth/app-attest/challenge 获取的新挑战。 |
{
"email": "[email protected]",
"password": "correct horse battery staple",
"app_attest": {
"key_id": "q1Zf3...=",
"assertion": "omlzaWduYXR1cmVYRzBF...",
"challenge": "b3Vy1Wq0..."
}
}断言使用的客户端数据哈希,是以下 UTF-8 字符串的 SHA-256,各部分之间以换行分隔:
warmbly-app-attest/v1
<action>
<challenge><action> 指定调用类型:login、register、reset_password 或 reset_password_confirm。为一种调用生成的证明不能用于另一种。每个挑战由首次提交它的调用消耗,断言计数器必须高于该密钥上次提交的值。每个已注册密钥还具有独立额度,每小时 30 次受保护调用;超出后返回 400,消息为 Too many attempts, please try again later.,与其他登录尝试额度的响应相同。
证明验证失败时返回 app_attest_failed。密钥 ID 从未在本服务器注册时返回 app_attest_key_unknown:应使用新挑战重新注册密钥,再重试调用。部署未启用验证码时,不检查 turnstile 或 app_attest。
GET /auth/config 同时携带 captcha 和 app_attest,应用需综合判断:未启用验证码时无需发送任何内容;启用验证码且具有 app_attest 时发送证明;启用验证码但没有 app_attest 时,应用内无法使用邮件地址和密码登录。通过 Apple、Google 或通行密钥登录始终无需验证码。iOS 模拟器无法使用 App Attest,因此本地开发继续使用 TURNSTILE_BYPASS_TOKEN,但仅在 APP_ENV=dev 时生效。
浏览器登录
浏览器无法自行完成服务商身份验证,因此需跳转过去,再携带授权码返回。“通过 Google 登录”、“通过 Apple 登录”和通用 OpenID Connect 都采用相同的三个步骤,只是服务商不同;部署提供的选项见 GET /auth/config 中的 providers 数组。
| 端点 | 请求体 | 用途 |
|---|---|---|
POST /auth/google/begin | 无 | 返回 {"url": "...", "binding": "..."}。将浏览器转到 url,在该浏览器中保留 binding,并在交换时提交。state、nonce 和 PKCE 验证值均由服务端生成和保存。 |
POST /auth/apple/begin | 无 | 相同流程,用于“通过 Apple 登录”。 |
POST /auth/oidc/begin | 无 | 相同流程,用于已配置的 OpenID Connect 服务商。 |
GET /auth/google/callback | 无 | Google 将浏览器返回到此地址。响应重定向至 <APP_URL>/auth/sso?code=...;登录被拒绝时重定向至 <APP_URL>/auth/login?sso_error=...。 |
POST /auth/apple/callback | 表单 POST | Apple 将浏览器返回到此地址。由于流程请求邮件地址权限范围,Apple 使用 response_mode=form_post,因此以表单提交而非重定向方式到达;拒绝情况也接受 GET。 |
GET /auth/oidc/callback | 无 | OpenID Connect 服务商将浏览器返回到此地址。 |
POST /auth/sso/exchange | code 和 binding,均必填 | 将任意回调返回的一次性交接代码换取会话。POST /auth/oidc/exchange 是同一端点的旧名称。 |
POST /auth/sso/link | pending_token 和 password,均必填 | 完成交换返回 link_required 的登录。接受已有账户的密码,将服务商身份关联到账户,然后返回会话,或双因素验证挑战。 |
回调绝不返回令牌。它向浏览器交付有效期 60 秒的不透明一次性代码,由客户端通过 POST 交换,因此令牌不会出现在 URL、浏览器历史、Referer 请求头或代理日志中。交换返回令牌对;账户已启用 TOTP 时,返回双因素验证挑战。
交换还有第三种结果。服务商身份通过 (issuer, subject) 组合绑定到账户;新身份首次登录时,会使用已验证邮件地址查找现有账户。如果该账户有密码,交换返回 link_required: true,附带 pending_token(有效期十分钟)、link_email 和 link_provider,此时尚未建立关联,也没有会话。客户端应要求输入账户密码,并将其连同令牌提交至 POST /auth/sso/link;密码正确后建立身份关联并返回会话,之后通过该服务商登录便可直接找到账户。账户已启用 TOTP 时,改为返回双因素验证挑战,只有 POST /auth/2fa/verify 通过后才建立身份关联。密码错误会返回与登录相同的 Invalid email or password.,并计入相同的每地址额度;三次错误,或经过十分钟后,挑战以 sso_link_expired 结束,必须重新开始登录。没有密码的账户,例如通过服务商或邀请创建的账户,无需询问密码,首次登录便会关联。原生端的 POST /auth/google 和 POST /auth/apple 也返回相同的 link_required 结果,并通过同一端点完成。
交换还要求 begin 返回的 binding 机密值。一次性的 state 只能证明回调对应本服务器发出的请求,无法证明对应当前浏览器发出的请求。缺少绑定时,攻击者可能用自己的服务商账户运行流程,再把结果链接发送给你,使你登录其工作区。绑定值不会发送给服务商,也不会出现在 URL 中,因此转发的链接缺少它,会返回 sso_wrong_browser 并被拒绝。
重定向 URI 由 API 提供,而非控制台,默认为 <API_PUBLIC_URL>/v1/auth/<provider>/callback,应在服务商处注册此值。参阅部署指南。
会话
已登录用户的自助会话管理。按 ID 撤销始终只能操作调用者自己的会话。
退出登录
POST /auth/logout
撤销当前会话,并关闭用户已打开的实时连接。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
退出所有设备上的登录
POST /auth/logout-all
撤销用户全部会话,包括当前会话,并关闭其已打开的实时连接。已撤销会话无法继续刷新。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
列出会话
GET /auth/sessions
返回账户安全页面所需的用户有效会话,标记当前会话并将其置顶。
身份验证:仅限会话,API 密钥不可用。
响应
直接返回会话视图数组。
[
{
"id": "1f1d...",
"current": true,
"browser": "Chrome",
"os": "macOS",
"location_city": "Austin",
"location_region": "Texas",
"location_country": "United States",
"country_code": "US",
"auth_provider": "email",
"created_at": "2026-06-10T08:12:00Z",
"last_active_at": "2026-06-12T09:40:00Z"
}
]撤销其他会话
DELETE /auth/sessions
结束当前会话以外的所有有效会话。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
撤销指定会话
DELETE /auth/sessions/:id
按 ID 结束用户的某个其他会话。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 要撤销的会话 ID。 |
个人资料与账户
获取当前用户
GET /auth/me
返回已登录用户,包括管理员标志和控制台初始加载所需的分类目录:文件夹、邮箱标签,以及通过 categories 返回的工作区标签。
这些目录属于工作区,而非调用者:同一组织的所有成员看到相同的文件夹和标签,无论由谁创建。读取范围是会话当前选择的工作区,因此切换工作区会改变返回内容。
身份验证:仅限会话,API 密钥不可用。
响应
{
"id": "9c2a...",
"first_name": "Alex",
"last_name": "Rivera",
"email": "[email protected]",
"avatar_url": "https://warmbly-assets.s3.amazonaws.com/avatars/users/9c2a-...jpg",
"roles": ["b1e4..."],
"referral_source": "google",
"onboarding_completed_at": "2026-05-01T10:00:00Z",
"max_organizations": 3,
"free_trial_used": true,
"admin_permissions": 0,
"is_admin": false,
"folders": [],
"tags": [],
"categories": [],
"created_at": "2026-04-20T12:00:00Z",
"updated_at": "2026-06-10T08:00:00Z"
}更新个人资料
PATCH /auth/me
更新可编辑的个人资料字段。名和姓均为必填,最长各 50 个字符。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
first_name | string | 是 | 1 至 50 个字符。 |
last_name | string | 是 | 1 至 50 个字符。 |
{
"first_name": "Alex",
"last_name": "Rivera"
}完成新手引导
PATCH /auth/me/onboarding
保存初始设置问卷,包括姓名和可选的用户角色回答。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
first_name | string | 是 | 1 至 50 个字符。 |
last_name | string | 是 | 1 至 50 个字符。 |
referral_source | string | 是 | 取值为 reddit、x、facebook、google、other 之一。 |
role | string | 否 | 取值为 founder、sales、marketing、agency、recruiter、other 之一。 |
team_size | string | 否 | 取值为 just_me、2-10、11-50、51-200、200+ 之一。 |
{
"first_name": "Alex",
"last_name": "Rivera",
"referral_source": "google",
"role": "sales",
"team_size": "2-10"
}上传头像
POST /auth/me/avatar
上传个人头像。使用 multipart/form-data,仅包含一个 file 字段。仅支持 PNG 或 JPG,最大 2 MB,尺寸不超过 1024×1024 像素。
身份验证:仅限会话,API 密钥不可用。
响应
{
"avatar_url": "https://warmbly-assets.s3.amazonaws.com/avatars/users/9c2a-1718193600.jpg"
}移除头像
DELETE /auth/me/avatar
清除个人头像,并尽力删除已保存文件。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
修改密码
POST /auth/me/password
更新已登录用户的密码。账户所有会话都会结束,包括发起请求的会话;响应返回为当前设备创建的新会话令牌对。修改前签发的令牌之后会被拒绝,因此客户端必须用返回值替换已保存的令牌对。返回 200 OK。
如果新密码已保存,但无法为设备签发新会话,响应为 409,错误码为 password_changed_sign_in_again。此时旧令牌同样无效,应丢弃并使用新密码重新登录。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
current_password | string | 是 | 现有密码。 |
new_password | string | 是 | 新密码。 |
{
"current_password": "correct horse battery staple",
"new_password": "a longer better passphrase"
}响应
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"access_token_expires_at": "2026-09-21T22:00:00Z",
"refresh_token": "eyJhbGciOiJIUzI1NiJ9...",
"refresh_token_expires_at": "2026-10-21T10:00:00Z"
}通知偏好与通知列表
限定在用户范围内,不检查组织权限。偏好设置是每用户一个对象;通知流是应用内通知列表。
获取通知偏好
GET /auth/me/notification-preferences
返回调用者偏好设置,并与默认值合并。
身份验证:仅限会话,API 密钥不可用。
响应
{
"preferences": {
"inbound_reply": { "enabled": false, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"inbound_out_of_office": { "enabled": false, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_bounce": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_complaint": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_worker_downtime": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_domain_auth": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"security_new_signin": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"billing_alert": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"team_activity": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"campaign_paused": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"placement_finished": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"placement_alert": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"inbox_action_required": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"email_digest_minutes": 30
}
}更新通知偏好
PUT /auth/me/notification-preferences
替换调用者偏好设置。完整偏好对象通过 preferences 键发送,并原样返回。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
preferences | object | 是 | 完整偏好设置对象。每个分类包含 enabled 和 channels 对象(in_app、email、slack、push),另有 email_digest_minutes,即邮件汇总窗口,范围 30 至 1440。请求中缺少的分类回退到默认值。 |
{
"preferences": {
"inbound_reply": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"inbound_out_of_office": { "enabled": false, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_bounce": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_complaint": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_worker_downtime": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"health_domain_auth": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"security_new_signin": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"billing_alert": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"team_activity": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"campaign_paused": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"placement_finished": { "enabled": true, "channels": { "in_app": true, "email": false, "slack": false, "push": true } },
"placement_alert": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"inbox_action_required": { "enabled": true, "channels": { "in_app": true, "email": true, "slack": false, "push": true } },
"email_digest_minutes": 30
}
}响应
与发送时相同的 { "preferences": { ... } } 对象。
列出通知
GET /auth/me/notifications
返回调用者最近的应用内通知流和未读数。通知流大小固定,不是游标分页列表。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
limit | query | int | 最多返回的条目数,默认 50。 |
unread | query | string | 1 或 true 表示仅返回未读条目。 |
响应
{
"notifications": [
{
"id": "f0aa...",
"user_id": "9c2a...",
"organization_id": "3d11...",
"category": "health_bounce",
"title": "Hard bounce on [email protected]",
"body": "A message bounced and the recipient was suppressed.",
"link": "/app/mailboxes/abc",
"read_at": null,
"created_at": "2026-06-12T09:00:00Z"
}
],
"unread": 3
}将所有通知标为已读
PUT /auth/me/notifications
将调用者整个通知流标为已读。
身份验证:仅限会话,API 密钥不可用。
响应
{ "ok": true }将指定通知标为已读
POST /auth/me/notifications/:id/read
将单条通知标为已读。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 通知 ID。 |
响应
{ "ok": true }注册设备令牌
POST /auth/me/device-tokens
注册用于移动推送的 APNs 设备令牌。用户允许通知后,移动应用会自动调用;采用插入或更新,因此每次启动时重复调用也是安全的。如果令牌已由其他账户注册,会转移给调用者,表示设备更换了账户。启用通知的投递遵循上方偏好中的 push 通道,并在服务端批量处理:一段静默期后的首个事件立即推送,批处理窗口内的后续事件合并为一条摘要。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | 是 | 十六进制 APNs 设备令牌。 |
platform | string | 否 | 目前仅支持 ios,也是默认值。 |
environment | string | 否 | production(默认),或沙盒构建使用 development。 |
{ "token": "a1b2c3...", "platform": "ios", "environment": "production" }响应
{ "ok": true }删除设备令牌
DELETE /auth/me/device-tokens/:token
移除调用者的一个设备令牌。移动应用在退出登录时调用,使设备停止接收该账户的推送。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
token | path | string | 要移除的十六进制设备令牌。 |
响应
{ "ok": true }双重身份验证
基于 TOTP 的双因素身份验证。启用和管理需要有效会话。登录验证端点本身(/auth/2fa/verify)公开开放,已在上文说明。
双重身份验证状态
GET /auth/2fa/status
报告调用者是否启用双因素身份验证、确认时间,以及剩余恢复码数。未启用时省略 confirmed_at。
身份验证:仅限会话,API 密钥不可用。
响应
{
"enabled": true,
"confirmed_at": "2026-09-19T10:24:00Z",
"recovery_codes_remaining": 8,
"recovery_codes_total": 10
}开始启用双重身份验证
POST /auth/2fa/enroll/start
生成 TOTP 密钥和 otpauth:// 配置 URI,仅返回一次。客户端将 URI 渲染为二维码;其余字段列明相同参数,供手动输入。
与下方确认启用操作一样,需要近期身份确认(POST /auth/reauth),否则返回 403 reauth_required。既没有密码也没有双因素验证的账户,可通过通行密钥或已关联登录服务商确认(GET /auth/reauth/methods)。这些方式都没有的账户,仅接受最近五分钟内登录的会话;更早的会话返回 400 reauth_no_factor。
身份验证:仅限会话,API 密钥不可用。
响应
{
"secret": "JBSWY3DPEHPK3PXP",
"otpauth_uri": "otpauth://totp/Warmbly:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Warmbly&algorithm=SHA1&digits=6&period=30",
"issuer": "Warmbly",
"account": "[email protected]",
"algorithm": "SHA1",
"digits": 6,
"period": 30
}确认启用双重身份验证
POST /auth/2fa/enroll/confirm
验证 TOTP 代码、启用双因素身份验证,并返回一次性恢复码,恢复码仅显示一次。代码不匹配时返回 400,错误码为 two_fa_invalid_code。需要与开始启用时相同的近期身份确认。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 身份验证器当前的 TOTP 代码。 |
响应
{
"recovery_codes": ["a1b2-c3d4", "e5f6-g7h8", "..."]
}重新生成恢复码
POST /auth/2fa/recovery-codes
将全部恢复码替换为新集合,仅返回一次。请求体需要当前 TOTP 或恢复码;不匹配时返回 400,错误码为 two_fa_invalid_code。新集合签发后,旧码立即失效。此处及 DELETE /auth/2fa 的错误代码尝试,与 POST /auth/reauth 计入同一账户尝试额度;耗尽后,两者均返回 400 和“尝试次数过多”,直到额度重置。无需 Idempotency-Key:证明代码只能使用一次,因此重试请求会被拒绝,不会重复轮换。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 当前 TOTP 或恢复码。 |
响应
{
"recovery_codes": ["a1b2-c3d4", "e5f6-g7h8", "..."]
}关闭双重身份验证
DELETE /auth/2fa
关闭双因素身份验证。请求体需要当前 TOTP 或恢复码;不匹配时返回 400,错误码为 two_fa_invalid_code。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 当前 TOTP 或恢复码。 |
响应
{ "ok": true }通行密钥
已登录用户的 WebAuthn 凭据管理。登录流程(/auth/passkey/login/begin 和 /finish)公开开放,已在上文说明。
开始注册通行密钥
POST /auth/passkey/register/begin
返回供浏览器传入 navigator.credentials.create() 的 WebAuthn 创建选项。
身份验证:仅限会话,API 密钥不可用。
响应
WebAuthn 创建选项对象,包括挑战、RP、用户和公钥参数。
完成注册通行密钥
POST /auth/passkey/register/finish
保存新凭据,并返回其展示视图。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 便于识别的通行密钥标签。 |
credential | object | 是 | 浏览器返回的原始 WebAuthn 证明(PublicKeyCredential)。 |
响应
{
"id": "c41f...",
"name": "MacBook Touch ID",
"credential_id": "b64url...",
"transports": ["internal"],
"backup_state": true,
"created_at": "2026-06-12T09:00:00Z",
"last_used_at": null
}列出通行密钥
GET /auth/passkey/credentials
返回用户已保存的通行密钥。
身份验证:仅限会话,API 密钥不可用。
响应
直接返回凭据视图数组,结构与完成注册的响应相同。
重命名通行密钥
PATCH /auth/passkey/credentials/:id
重命名已保存的通行密钥,并返回更新后的视图。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 凭据 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 新标签。 |
删除通行密钥
DELETE /auth/passkey/credentials/:id
移除已保存的通行密钥。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 凭据 ID。 |
账户高风险操作
延迟永久删除调用者自己的账户,需要确认短语,并提供宽限期。
获取账户高风险操作状态
GET /me/danger-zone
返回危险操作区摘要,以及任何待执行的删除。
身份验证:仅限会话,API 密钥不可用。
响应
{
"resource_type": "user",
"resource_id": "9c2a...",
"resource_name": "[email protected]",
"confirmation_hint": "[email protected]",
"grace_days": 14,
"pending_deletion": null
}安排删除账户
POST /me/danger-zone/delete
安排延迟永久删除账户。确认内容必须匹配账户邮件地址。返回 202 Accepted,包含计划删除记录。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
confirmation | string | 是 | 必须等于账户邮件地址。 |
reason | string | 否 | 可选的自由文本原因。 |
{
"confirmation": "[email protected]",
"reason": "switching tools"
}响应
{
"resource_type": "user",
"resource_id": "9c2a...",
"requested_by_user_id": "9c2a...",
"scheduled_at": "2026-06-12T09:00:00Z",
"execute_after": "2026-06-26T09:00:00Z",
"grace_days": 14,
"status": "pending"
}取消删除账户
DELETE /me/danger-zone/delete
取消待执行的账户删除。
身份验证:仅限会话,API 密钥不可用。
请求体(可选)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
reason | string | 否 | 可选的自由文本原因。 |
响应
{ "message": "deletion cancelled" }WebSocket 连接初始化
生成 WebSocket 令牌
POST /getaway
为实时 WebSocket 签发单会话令牌,并返回连接 URL 和以秒为单位的有效期。
身份验证:仅限会话,API 密钥不可用。
响应
{
"url": "wss://realtime.warmbly.com/socket?token=...",
"expires_in": 60
}组织
组织即工作区租户。这些端点处理工作区创建和切换、当前工作区及其限制、成员、自定义角色、邀请、所有权转移、头像和工作区危险操作区。所有 /organization/* 路由仅接受会话。修改操作由调用者的组织角色权限控制,各端点分别注明。
获取工作区发送限制状态
GET /organization/current/risk
返回工作区的滥用风险处置状态,受限时同时说明原因。任何成员均可读取:发送受限的工作区应能够看到这一状态。
身份验证:仅限会话,API 密钥不可用。
响应
| 字段 | 类型 | 说明 |
|---|---|---|
state | string | trusted、watch、restricted 或 suspended。 |
restricted | boolean | restricted 和 suspended 时为 true,发送量降低。 |
suspended | boolean | 完全停止发送时为 true。 |
reason | string | 有摘要时,提供通俗说明。 |
{
"state": "restricted",
"restricted": true,
"suspended": false,
"reason": "imported list is 30% invalid; disposable signup domain"
}有意不返回状态背后的检测证据。这些信息供运维人员使用;向被检测方提供触发判断的精确信号,会帮助其规避后续检查。各状态含义及可能触发它们的信号见工作区发送限制状态。
创建组织
POST /organization
创建由调用者拥有的新组织。返回 201 Created。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 1 至 255 个字符。 |
timezone | string | 否 | 工作区时区,IANA 名称,例如 America/New_York。新营销活动的日程初始采用此时区,没有自身时区的邮箱也据此解释预热时段和工作时间。控制台发送浏览器时区。 |
{ "name": "Acme Outbound", "timezone": "America/New_York" }响应
创建的组织对象。
{
"id": "3d11...",
"name": "Acme Outbound",
"slug": null,
"avatar_url": null,
"owner_user_id": "9c2a...",
"presence_show_online": true,
"presence_show_activity": true,
"timezone": "America/New_York",
"created_at": "2026-06-12T09:00:00Z",
"updated_at": "2026-06-12T09:00:00Z"
}列出我所属的组织
GET /organization
返回调用者加入的组织。
身份验证:仅限会话,API 密钥不可用。
响应
{
"data": [
{
"id": "ab12...",
"organization_id": "3d11...",
"user_id": "9c2a...",
"role": "owner",
"permissions": 524287,
"email": "[email protected]",
"name": "Alex Rivera",
"invited_at": "2026-06-12T09:00:00Z"
}
]
}切换组织
POST /organization/switch/:id
设置会话的当前组织。调用者必须是该组织成员。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 要切换到的组织。 |
响应
{
"message": "organization switched",
"organization_id": "3d11..."
}获取当前组织
GET /organization/current
返回会话当前组织,包含解析后的限制和实时计数。
身份验证:仅限会话,API 密钥不可用。
响应
{
"id": "3d11...",
"name": "Acme Outbound",
"owner_user_id": "9c2a...",
"presence_show_online": true,
"presence_show_activity": true,
"timezone": "America/New_York",
"created_at": "2026-06-12T09:00:00Z",
"updated_at": "2026-06-12T09:00:00Z",
"limits": {
"max_campaigns": 50,
"max_team_members": 10,
"max_email_accounts": 25,
"daily_campaign_limit": 500
},
"counts": {
"total_campaigns": 8,
"active_campaigns": 2,
"total_contacts": 1240,
"total_members": 3,
"email_accounts": 6,
"emails_sent_today": 180
}
}更新当前组织
PATCH /organization/current
更新当前组织,包括名称、短标识、时区和在线状态隐私开关。在线状态隐私变更会立即重新限制已连接的实时连接。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_settings。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 新的工作区名称。 |
slug | string | 否 | 新的工作区短标识。 |
presence_show_online | boolean | 否 | 为 false 时,实时服务不跟踪任何成员,没有人显示为在线。 |
presence_show_activity | boolean | 否 | 为 false 时,仍显示在线状态,但移除查看和编辑详情。 |
timezone | string | 否 | 工作区时区,IANA 名称。新营销活动默认使用此时区,没有自身时区的邮箱也据此解释时段。发送空字符串可清除,此后这些情况回退到 UTC。 |
{
"name": "Acme Outbound",
"timezone": "America/New_York",
"presence_show_activity": false
}响应
更新后的组织对象。
获取组织限额
GET /organization/current/limits
返回当前组织根据套餐解析的限制,以及当前用量计数。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
{
"limits": {
"max_campaigns": 50,
"max_team_members": 10,
"max_email_accounts": 25,
"daily_campaign_limit": 500
},
"counts": {
"total_campaigns": 8,
"active_campaigns": 2,
"total_contacts": 1240,
"total_members": 3,
"email_accounts": 6,
"emails_sent_today": 180
}
}列出成员
GET /organization/members
返回当前组织的成员,每项填充用户邮件地址、显示名称、角色和实际生效的权限位掩码。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
{
"data": [
{
"id": "ab12...",
"organization_id": "3d11...",
"user_id": "9c2a...",
"role": "owner",
"roles": [{ "id": "r1...", "name": "Owner", "color": "#0ea5e9" }],
"permissions": 524287,
"email": "[email protected]",
"name": "Alex Rivera",
"invited_at": "2026-06-12T09:00:00Z",
"accepted_at": "2026-06-12T09:05:00Z"
}
]
}邀请成员
POST /organization/members/invite
邀请某个邮件地址加入组织,并发送接受邀请链接。受邀人加入指定角色集合。返回 201 Created。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | string | 是 | 受邀人的邮件地址。 |
role_ids | uuid[] | 否 | 要分配的工作区角色,合计至少一个。 |
role_id | uuid | 否 | 单角色简写,与 role_ids 合并。 |
{
"email": "[email protected]",
"role_ids": ["r2..."]
}响应
{
"message": "invitation sent",
"invitation": {
"id": "inv1...",
"organization_id": "3d11...",
"email": "[email protected]",
"role": "member",
"permissions": 12288,
"invited_by": "9c2a...",
"expires_at": "2026-06-19T09:00:00Z",
"created_at": "2026-06-12T09:00:00Z"
}
}更新成员角色
PATCH /organization/members/:id
替换成员分配的角色集合,返回更新后的成员。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 成员用户 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
role_ids | uuid[] | 否 | 新分配的角色集合,合计至少一个。 |
role_id | uuid | 否 | 单角色简写,与 role_ids 合并。 |
{ "role_ids": ["r2...", "r3..."] }响应
更新后的成员对象,结构与成员列表条目相同。
移除成员
DELETE /organization/members/:id
将成员从组织移除。该成员的会话会立即停止选中此组织,因此下一次需要工作区的请求会返回 400,直到其切换到其他工作区。该成员在工作区内授予的所有 OAuth 应用授权都会撤销。API 密钥保留,因为它们属于工作区,而非创建它们的成员。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 成员用户 ID。 |
响应
{ "message": "member removed" }列出自定义角色
GET /organization/roles
返回组织自定义角色,即具名权限集合。所有成员均可列出,便于在成员名单显示角色标签。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
{
"data": [
{
"id": "r2...",
"organization_id": "3d11...",
"name": "Sales rep",
"description": "Run campaigns, view contacts",
"color": "#22c55e",
"permissions": 4352,
"member_count": 4,
"created_at": "2026-05-01T09:00:00Z",
"updated_at": "2026-06-01T09:00:00Z"
}
]
}创建自定义角色
POST /organization/roles
创建自定义角色。返回 201 Created,包含角色。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 角色名称。 |
description | string | 否 | 角色说明。 |
color | string | 否 | 角色标签的十六进制颜色。 |
permissions | int | 否 | 角色授予的组织权限位掩码。 |
{
"name": "Sales rep",
"description": "Run campaigns, view contacts",
"color": "#22c55e",
"permissions": 4352
}更新自定义角色
PATCH /organization/roles/:id
编辑自定义角色。变更会传播到所有获分配该角色的成员,权限读取无需 JOIN 查询。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 角色 ID。 |
请求体
所有字段均可选,nil 字段保持不变。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 新名称。 |
description | string | 否 | 新说明。 |
color | string | 否 | 新的角色标签颜色。 |
permissions | int | 否 | 新的权限位掩码。 |
删除自定义角色
DELETE /organization/roles/:id
移除自定义角色。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 角色 ID。 |
列出待接受的邀请
GET /organization/invitations
返回组织尚未接受的邀请。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
响应
{
"data": [
{
"id": "inv1...",
"organization_id": "3d11...",
"email": "[email protected]",
"role": "member",
"permissions": 12288,
"invited_by": "9c2a...",
"expires_at": "2026-06-19T09:00:00Z",
"created_at": "2026-06-12T09:00:00Z"
}
]
}取消邀请
DELETE /organization/invitations/:id
取消待接受的邀请。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 邀请 ID。 |
响应
{ "message": "invitation cancelled" }获取邀请链接
GET /organization/invitations/:id/link
为待接受邀请生成可分享的接受令牌,便于团队管理员复制有效的接受链接。邀请令牌只保存摘要,因此每次调用都会签发新的链接令牌,此前复制的链接失效;邮件中的链接仍可同时使用。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 邀请 ID。 |
响应
{ "token": "inv_token_abc123" }转移所有权
POST /organization/transfer-ownership
将组织所有权转移给另一位成员。
身份验证:仅限会话,API 密钥不可用。组织权限 transfer_ownership。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
new_owner_user_id | uuid | 是 | 要提升为所有者的成员用户 ID。 |
{ "new_owner_user_id": "7b88..." }响应
{ "message": "ownership transferred" }上传组织头像
POST /organization/avatar
上传工作区图片。使用 multipart/form-data,仅一个 file 字段。仅限所有者。PNG 或 JPG,最大 2 MB,尺寸不超过 1024×1024 像素。
身份验证:仅限会话,API 密钥不可用。需要已选择组织,且仅限所有者。
响应
{ "avatar_url": "https://warmbly-assets.s3.amazonaws.com/avatars/organizations/3d11-1718193600.jpg" }移除组织头像
DELETE /organization/avatar
清除工作区图片,并尽力删除已保存文件。仅限所有者。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。需要已选择组织,且仅限所有者。
获取组织高风险操作状态
GET /organization/current/danger-zone
返回工作区危险操作区摘要,以及任何待执行的删除。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
结构与账户危险操作区相同,resource_type 为 organization,确认提示使用组织名称。
安排删除组织
POST /organization/current/danger-zone/delete
安排延迟永久删除当前组织。仅限所有者,确认内容必须匹配组织名称。返回 202 Accepted,包含计划删除记录。
身份验证:仅限会话,API 密钥不可用。需要已选择组织,且仅限所有者。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
confirmation | string | 是 | 必须等于组织名称。 |
reason | string | 否 | 可选的自由文本原因。 |
{ "confirmation": "Acme Outbound" }取消删除组织
DELETE /organization/current/danger-zone/delete
取消待执行的组织删除。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
请求体(可选)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
reason | string | 否 | 可选的自由文本原因。 |
响应
{ "message": "deletion cancelled" }提交提高限额的申请
POST /organization/:orgId/limit-requests
提交提高组织某项限额的申请。提交时由服务端记录当前实际生效值。返回 201 Created。
没有发送套餐的工作区,包括免费工作区或 Warmup 套餐工作区,只能申请 max_email_accounts;其他字段返回 400 bad_request,因为工作区开始发送前,这些限额都不适用。每日发送量已不受限的工作区申请 daily_campaign_limit 时,也返回 400 bad_request,因为批准某个数值反而会施加上限,而不是提高上限。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
orgId | path | uuid | 组织 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
field | string | 是 | 限额字段,例如 max_email_accounts、daily_campaign_limit。 |
requested | int | 是 | 申请的值,必须大于当前实际生效限额。 |
reason | string | 是 | 1 至 2000 个字符。 |
{
"field": "max_email_accounts",
"requested": 50,
"reason": "Onboarding a new sales team next month."
}响应
{
"id": "lr1...",
"organization_id": "3d11...",
"field": "max_email_accounts",
"current_effective": 25,
"requested": 50,
"reason": "Onboarding a new sales team next month.",
"status": "pending",
"submitted_by": "9c2a...",
"submitted_at": "2026-06-12T09:00:00Z",
"review_notes": ""
}列出限额申请
GET /organization/:orgId/limit-requests
返回组织的限额提升申请。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
orgId | path | uuid | 组织 ID。 |
响应
{ "data": [ { "id": "lr1...", "field": "max_email_accounts", "status": "pending", "requested": 50 } ] }取消限额申请
DELETE /limit-requests/:id
取消待处理的限额申请。仅限提交者。返回 204 No Content。
身份验证:仅限会话,API 密钥不可用。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 限额申请 ID。 |
邀请(供受邀者使用)
这些端点位于 /organization 之外,因为它们操作已登录用户自己的待接受邀请。
列出我尚未接受的邀请
GET /invitations
返回发送到调用者邮件地址的邀请。
身份验证:仅限会话,API 密钥不可用。
响应
{
"data": [
{
"id": "inv1...",
"organization_id": "3d11...",
"email": "[email protected]",
"role": "member",
"expires_at": "2026-06-19T09:00:00Z",
"created_at": "2026-06-12T09:00:00Z",
"organization": { "id": "3d11...", "name": "Acme Outbound" }
}
]
}接受邀请
POST /invitations/accept
通过机密令牌(来自 /invite 链接),或邀请 ID(来自调用者自己的待接受列表)接受邀请。返回新的成员关系。
身份验证:仅限会话,API 密钥不可用。
请求体
token 和 invitation_id 必须且只能提供一个。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | 有条件必填 | /invite 链接中的机密令牌。 |
invitation_id | uuid | 有条件必填 | 调用者待接受列表中的邀请 ID。 |
{ "token": "inv_token_abc123" }响应
{
"message": "invitation accepted",
"member": {
"id": "ab13...",
"organization_id": "3d11...",
"user_id": "9c2a...",
"role": "member",
"permissions": 12288,
"email": "[email protected]",
"name": "Alex Rivera"
}
}团队
团队使用具名、带颜色的标签对现有组织成员分组,用于 CRM 负责人分配和路由。与本组其他接口不同,团队端点同时接受 JWT 和 API 密钥,且都需要已选择组织。读取映射到 CRM 读取权限范围,写入映射到 CRM 写入权限范围。
列出团队
GET /teams
返回组织的团队,每项填充成员列表。
身份验证:权限范围 READ_CRM · 组织权限 view_contacts。需要已选择组织。
响应
{
"data": [
{
"id": "t1...",
"organization_id": "3d11...",
"name": "West coast",
"color": "#0ea5e9",
"members": [
{ "user_id": "9c2a...", "email": "[email protected]", "name": "Alex Rivera", "added_at": "2026-06-01T09:00:00Z" }
],
"created_at": "2026-05-01T09:00:00Z",
"updated_at": "2026-06-01T09:00:00Z"
}
]
}创建团队
POST /teams
创建团队。返回 201 Created,包含团队。
身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 是 | 1 至 255 个字符。 |
color | string | 否 | 十六进制颜色,默认 #94a3b8。 |
{ "name": "West coast", "color": "#0ea5e9" }响应
创建的团队对象,成员初始为空。
获取团队
GET /teams/:id
返回单个团队及其成员。
身份验证:权限范围 READ_CRM · 组织权限 view_contacts。需要已选择组织。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 团队 ID。 |
更新团队
PATCH /teams/:id
部分更新团队名称或颜色。nil 字段保持不变。
身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 团队 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | string | 否 | 新名称。 |
color | string | 否 | 新颜色。 |
响应
更新后的团队对象。
删除团队
DELETE /teams/:id
删除团队。返回 204 No Content。
身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 团队 ID。 |
添加团队成员
POST /teams/:id/members
将已有组织成员加入团队,返回更新后的团队。
身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 团队 ID。 |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
user_id | uuid | 是 | 成员用户 ID,必须已属于该组织。 |
{ "user_id": "7b88..." }响应
更新后的团队对象,包含新成员。
移除团队成员
DELETE /teams/:id/members/:userId
从团队移除成员。返回 204 No Content。
身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
id | path | uuid | 团队 ID。 |
userId | path | uuid | 成员用户 ID。 |
订阅与计费
订阅按组织计费。这些端点读取当前订阅、试用和功能访问权限,管理 Stripe 结账和账单门户,变更套餐、验证折扣码,并提交企业咨询。所有 /subscription/* 路由仅接受会话。
获取订阅
GET /subscription
返回当前组织的订阅。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
{
"id": "sub1...",
"user_id": "9c2a...",
"organization_id": "3d11...",
"plan_id": "plan_pro...",
"stripe_customer_id": "cus_...",
"stripe_subscription_id": "sub_...",
"status": "active",
"current_period_start": "2026-06-01T00:00:00Z",
"current_period_end": "2026-07-01T00:00:00Z",
"cancel_at_period_end": false,
"is_enterprise": false,
"plan": { "id": "plan_pro...", "name": "Pro", "price": 99, "duration": "month" },
"created_at": "2026-04-20T12:00:00Z",
"updated_at": "2026-06-01T00:00:00Z"
}获取订阅及其限额
GET /subscription/limits
返回订阅及套餐速率限制。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
上述订阅对象,扩展包含套餐限制字段。
获取试用状态
GET /subscription/trial
返回组织免费试用状态。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
试用状态对象,包括试用是否有效、结束时间及是否已使用过。
获取功能状态
GET /subscription/features
返回组织的功能访问状态,以及主要受限功能的便捷标志。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
响应
{
"subscription": { "status": "active", "plan": "Pro" },
"can_send_campaigns": true,
"can_use_warmup": true,
"can_use_unibox": true
}创建结账会话
POST /subscription/checkout
针对某个价格创建 Stripe 结账会话,并返回重定向 URL。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_billing。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
price_id | string | 是 | 要订阅的 Stripe 价格 ID。 |
success_url | string | 是 | 结账成功后的重定向目标。 |
cancel_url | string | 是 | 用户取消时的重定向目标。 |
discount_code | string | 否 | 要应用的折扣码。 |
{
"price_id": "price_123",
"success_url": "https://app.warmbly.com/billing?status=success",
"cancel_url": "https://app.warmbly.com/billing?status=cancel"
}响应
{
"session_id": "cs_test_...",
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_..."
}验证折扣码
POST /subscription/discount/validate
预览折扣码对当前组织是否有效,可选择针对目标套餐检查,并返回供显示的折扣详情。
身份验证:仅限会话,API 密钥不可用。需要已选择组织。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
code | string | 是 | 折扣码。 |
plan_id | uuid | 否 | 用于计算折后金额的目标套餐。 |
{ "code": "LAUNCH20", "plan_id": "plan_pro..." }响应
{
"valid": true,
"code": "LAUNCH20",
"type": "percent",
"percent_off": 20,
"duration": "once"
}无效时,valid 为 false,reason 说明原因。
创建账单管理门户会话
POST /subscription/portal
为组织对应的客户创建 Stripe 账单门户会话,并返回门户 URL。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_billing。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
return_url | string | 是 | 离开门户后 Stripe 将用户返回到的位置。 |
{ "return_url": "https://app.warmbly.com/billing" }响应
{ "portal_url": "https://billing.stripe.com/p/session/..." }取消订阅
POST /subscription/cancel
取消组织订阅,可在当前周期结束时取消,也可立即取消。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_billing。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
cancel_at_period_end | boolean | 否 | 为 true 时,在当前周期结束时取消,而非立即取消。 |
{ "cancel_at_period_end": true }响应
{ "message": "subscription cancelled" }更改套餐
POST /subscription/change-plan
变更组织套餐,并按比例结算差额。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_billing。需要已选择组织。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
plan_id | uuid | 是 | 目标套餐 ID。 |
proration_behavior | string | 否 | 取值为 create_prorations、always_invoice、none 之一。 |
discount_code | string | 否 | 要应用的折扣码。 |
interval | string | 否 | month 或 year,默认按月。 |
{
"plan_id": "plan_scale...",
"proration_behavior": "create_prorations",
"interval": "year"
}响应
{
"message": "plan changed successfully",
"subscription": { "id": "sub1...", "plan_id": "plan_scale...", "status": "active" }
}预览套餐变更
GET /subscription/preview-change
预览套餐变更的按比例结算结果,不实际应用。
身份验证:仅限会话,API 密钥不可用。组织权限 manage_billing。需要已选择组织。
| 参数 | 位置 | 类型 | 说明 |
|---|---|---|---|
new_plan_id | query | uuid | 目标套餐 ID。 |
响应
按比例结算预览对象,包括明细项目、即时扣费和下次账单金额。
提交企业方案咨询
POST /subscription/enterprise-inquiry
提交企业定价咨询。返回确认信息和新的咨询 ID。
身份验证:仅限会话,API 密钥不可用。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
company_name | string | 是 | 公司名称。 |
contact_name | string | 是 | 联系人姓名。 |
contact_email | string | 是 | 联系人邮件地址。 |
estimated_volume | int | 否 | 预计月度邮件量。 |
team_size | int | 否 | 团队人数。 |
notes | string | 否 | 自由文本备注。 |
{
"company_name": "Acme Inc",
"contact_name": "Alex Rivera",
"contact_email": "[email protected]",
"estimated_volume": 50000,
"team_size": 25
}响应
{
"message": "Thank you! Our team will contact you within 24 hours.",
"inquiry_id": "eiq1..."
}优惠码与推荐奖励
均仅接受会话,需要 manage_billing 组织权限和已选择的组织。产品规则见推荐计划指南。
| 端点 | 返回值 |
|---|---|
GET /subscription/discounts | 组织的促销码兑换历史(code、type、percent_off、duration、redeemed_at)。 |
GET /subscription/referral | 组织的推荐状态和推荐码。 |
POST /subscription/referral | 创建组织推荐码。 |
GET /subscription/referral/attributions | 每次被推荐注册对应一行。status 为 pending(已注册但未付费)、qualified(已完成付费结账)、rewarded 或 void(奖励追回、自我推荐或达到上限)。reward_cents 为整数美分。 |
GET /subscription/referral/earnings | 奖励账本。amount_cents 在发放奖励时为正数,在 30 天窗口内追回时为负数;reason 为 referral_reward 或 referral_clawback:<cause>;balance_after_cents 为累计余额。 |
参考数据
账单功能旁另有两个只读参考端点。与本组其余端点不同,它们接受任意已通过身份验证的凭据,包括 JWT 或 API 密钥;身份验证仅用于防止公开抓取。
列出套餐
GET /plans
返回可用的公开订阅套餐。
身份验证:任意已通过身份验证的调用者,JWT 或 API 密钥均可。
响应
{
"plans": [
{ "id": "plan_pro...", "name": "Pro", "price": 99, "duration": "month", "public": true }
]
}列出时区
GET /timezones
返回控制台选择器使用的时区列表,每项显示名称带有当前 UTC 偏移量。任何设置时区的位置,包括营销活动日程、邮箱和工作区,都接受任意 IANA 名称,因此该列表只是便于选择,并非全部有效值集合。
身份验证:任意已通过身份验证的调用者,JWT 或 API 密钥均可。