Warmbly文档
端点参考

账户与组织

身份验证、会话、个人资料与安全设置、组织管理、团队和订阅计费。

本组接口涵盖个人账户及其所属工作区相关功能:登录、会话管理、个人资料和安全设置(通知偏好、双因素身份验证、通行密钥)、组织管理(成员、邀请、自定义角色)、成员团队分组,以及账单。

这里几乎所有端点都仅接受会话,依赖绑定到具体用户的 JWT,API 密钥无法访问。有两类例外:团队端点同时接受 JWT 和 API 密钥;公开身份验证端点,包括登录、注册、刷新、密码重置、通行密钥登录和双因素验证,本身无需会话,因为它们用于创建会话。下方各端点均明确标注身份验证要求。

所有错误响应采用共享封装(error、message、code、request_id)。参阅错误码。

身份验证

这些端点公开开放,无需会话。登录和注册分两步:首次调用通过邮件发送一次性代码,并返回短期签名 session 令牌;第二次调用确认代码,签发正式令牌对。部署启用验证码时,启动调用和两个密码重置调用都需要验证码:浏览器提供 Cloudflare Turnstile 令牌,原生应用提供App Attest 证明。

开始登录

POST /auth/login

向账户邮件地址发送登录代码,并返回签名会话令牌,供确认步骤使用。

身份验证:公开端点,API 密钥不可用。

请求体

字段类型必填说明
emailstring是账户邮件地址。
passwordstring是账户密码。
turnstilestring必填,除非发送了 app_attestCloudflare Turnstile 令牌。
app_attestobject否原生应用的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 密钥不可用。

请求体

字段类型必填说明
sessionstring是/auth/login 返回的令牌。
codestring是邮件中的一次性代码。
turnstilestring否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 密钥不可用。

请求体

字段类型必填说明
sessionstring是/auth/register 返回的令牌。
codestring是邮件中的一次性代码。
turnstilestring否Cloudflare Turnstile 令牌。

响应

{
  "code_required": false,
  "token": { "access_token": "…", "refresh_token": "…", "access_token_expires_at": "…" }
}

刷新令牌

POST /auth/refresh

使用有效刷新令牌换取新的令牌对。

身份验证:公开端点,API 密钥不可用。

请求体

字段类型必填说明
refresh_tokenstring是有效且未过期的刷新令牌。
{
  "refresh_token": "eyJhbGc..."
}

响应

新的令牌对,结构与登录确认成功响应相同(access_token、access_token_expires_at、refresh_token、refresh_token_expires_at)。

每次刷新都会轮换令牌对:保存新刷新令牌,丢弃旧令牌。已经交换过的刷新令牌会返回 401 并被拒绝。如果在交换后超过两分钟再次提交,视为重复使用,整个会话将结束,向该会话签发的所有令牌停止工作。两分钟内则视为两个调用者同时刷新,仅拒绝此次请求。

开始重置密码

POST /auth/reset-password

地址存在账户时,通过邮件发送重置代码。始终返回 200 OK,不泄露地址是否存在。

身份验证:公开端点,API 密钥不可用。

请求体

字段类型必填说明
emailstring是账户邮件地址。
turnstilestring必填,除非发送了 app_attestCloudflare Turnstile 令牌。
app_attestobject否原生应用的App Attest 证明,替代 turnstile 发送。

响应

200 OK,响应体为空。

确认重置密码

POST /auth/reset-password/confirm

使用邮件重置代码设置新密码,代码携带在签名 session 令牌中。重置令牌只能使用一次,并绑定到请求重置时的密码:无论通过本端点、登录后的修改,还是运维重置,只要密码被写入,此前签发的所有重置令牌都会返回 401,无论自身剩余有效期多长。

身份验证:公开端点,API 密钥不可用。

请求体

字段类型必填说明
sessionstring是与该邮件地址绑定的重置会话令牌。
passwordstring是新密码。
turnstilestring必填,除非发送了 app_attestCloudflare Turnstile 令牌。
app_attestobject否原生应用的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 密钥不可用。

请求体

字段类型必填说明
sessionstring是开始步骤返回的会话值。
credentialobject是浏览器返回的原始 WebAuthn 断言(PublicKeyCredential)。

响应

令牌对,结构与登录确认成功响应相同。

验证双重身份验证登录

POST /auth/2fa/verify

使用需双因素验证的登录返回的一次性 pending_token,加上 TOTP 或恢复码,换取正式会话。

身份验证:公开端点,API 密钥不可用。

请求体

字段类型必填说明
pending_tokenstring是/auth/login/confirm 返回的 pending_token。
codestring是当前有效的 TOTP 代码,或未使用过的恢复码。
{
  "pending_token": "eyJhbGc...",
  "code": "123456"
}

响应

令牌对,结构与登录确认成功响应相同。

第三方账户登录(原生应用)

三个端点均公开开放,API 密钥不可用。服务商验证的身份视为强身份验证,因此没有邮件代码步骤;首次登录会创建账户、组织和试用。

端点请求体用途
GET /auth/providers无本部署支持的原生登录选项,使移动应用无需重新构建即可适配自托管后端。返回 {apple, google} 对象,包含 enabled 和公开的 client_id。
POST /auth/appleidentity_token(必填)、first_name、last_name交换“通过 Apple 登录”的身份令牌,使用 Apple 密钥验证。Apple 只会将姓名提供给应用,因此首次登录时应将姓名传入,用于预填。
POST /auth/googleid_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/keyskey_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_idstring是已注册的密钥标识符,base64 编码。
assertionstring是DCAppAttestService.generateAssertion 返回的 base64 断言。
challengestring是从 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表单 POSTApple 将浏览器返回到此地址。由于流程请求邮件地址权限范围,Apple 使用 response_mode=form_post,因此以表单提交而非重定向方式到达;拒绝情况也接受 GET。
GET /auth/oidc/callback无OpenID Connect 服务商将浏览器返回到此地址。
POST /auth/sso/exchangecode 和 binding,均必填将任意回调返回的一次性交接代码换取会话。POST /auth/oidc/exchange 是同一端点的旧名称。
POST /auth/sso/linkpending_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 密钥不可用。

参数位置类型说明
idpathuuid要撤销的会话 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_namestring是1 至 50 个字符。
last_namestring是1 至 50 个字符。
{
  "first_name": "Alex",
  "last_name": "Rivera"
}

完成新手引导

PATCH /auth/me/onboarding

保存初始设置问卷,包括姓名和可选的用户角色回答。返回 204 No Content。

身份验证:仅限会话,API 密钥不可用。

请求体

字段类型必填说明
first_namestring是1 至 50 个字符。
last_namestring是1 至 50 个字符。
referral_sourcestring是取值为 reddit、x、facebook、google、other 之一。
rolestring否取值为 founder、sales、marketing、agency、recruiter、other 之一。
team_sizestring否取值为 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_passwordstring是现有密码。
new_passwordstring是新密码。
{
  "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 密钥不可用。

请求体

字段类型必填说明
preferencesobject是完整偏好设置对象。每个分类包含 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 密钥不可用。

参数位置类型说明
limitqueryint最多返回的条目数,默认 50。
unreadquerystring1 或 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 密钥不可用。

参数位置类型说明
idpathuuid通知 ID。

响应

{ "ok": true }

注册设备令牌

POST /auth/me/device-tokens

注册用于移动推送的 APNs 设备令牌。用户允许通知后,移动应用会自动调用;采用插入或更新,因此每次启动时重复调用也是安全的。如果令牌已由其他账户注册,会转移给调用者,表示设备更换了账户。启用通知的投递遵循上方偏好中的 push 通道,并在服务端批量处理:一段静默期后的首个事件立即推送,批处理窗口内的后续事件合并为一条摘要。

身份验证:仅限会话,API 密钥不可用。

请求体

字段类型必填说明
tokenstring是十六进制 APNs 设备令牌。
platformstring否目前仅支持 ios,也是默认值。
environmentstring否production(默认),或沙盒构建使用 development。
{ "token": "a1b2c3...", "platform": "ios", "environment": "production" }

响应

{ "ok": true }

删除设备令牌

DELETE /auth/me/device-tokens/:token

移除调用者的一个设备令牌。移动应用在退出登录时调用,使设备停止接收该账户的推送。

身份验证:仅限会话,API 密钥不可用。

参数位置类型说明
tokenpathstring要移除的十六进制设备令牌。

响应

{ "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 密钥不可用。

请求体

字段类型必填说明
codestring是身份验证器当前的 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 密钥不可用。

请求体

字段类型必填说明
codestring是当前 TOTP 或恢复码。

响应

{
  "recovery_codes": ["a1b2-c3d4", "e5f6-g7h8", "..."]
}

关闭双重身份验证

DELETE /auth/2fa

关闭双因素身份验证。请求体需要当前 TOTP 或恢复码;不匹配时返回 400,错误码为 two_fa_invalid_code。

身份验证:仅限会话,API 密钥不可用。

请求体

字段类型必填说明
codestring是当前 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 密钥不可用。

请求体

字段类型必填说明
namestring否便于识别的通行密钥标签。
credentialobject是浏览器返回的原始 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 密钥不可用。

参数位置类型说明
idpathuuid凭据 ID。

请求体

字段类型必填说明
namestring是新标签。

删除通行密钥

DELETE /auth/passkey/credentials/:id

移除已保存的通行密钥。返回 204 No Content。

身份验证:仅限会话,API 密钥不可用。

参数位置类型说明
idpathuuid凭据 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 密钥不可用。

请求体

字段类型必填说明
confirmationstring是必须等于账户邮件地址。
reasonstring否可选的自由文本原因。
{
  "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 密钥不可用。

请求体(可选)

字段类型必填说明
reasonstring否可选的自由文本原因。

响应

{ "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 密钥不可用。

响应

字段类型说明
statestringtrusted、watch、restricted 或 suspended。
restrictedbooleanrestricted 和 suspended 时为 true,发送量降低。
suspendedboolean完全停止发送时为 true。
reasonstring有摘要时,提供通俗说明。
{
  "state": "restricted",
  "restricted": true,
  "suspended": false,
  "reason": "imported list is 30% invalid; disposable signup domain"
}

有意不返回状态背后的检测证据。这些信息供运维人员使用;向被检测方提供触发判断的精确信号,会帮助其规避后续检查。各状态含义及可能触发它们的信号见工作区发送限制状态。

创建组织

POST /organization

创建由调用者拥有的新组织。返回 201 Created。

身份验证:仅限会话,API 密钥不可用。

请求体

字段类型必填说明
namestring是1 至 255 个字符。
timezonestring否工作区时区,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 密钥不可用。

参数位置类型说明
idpathuuid要切换到的组织。

响应

{
  "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。

请求体

字段类型必填说明
namestring否新的工作区名称。
slugstring否新的工作区短标识。
presence_show_onlineboolean否为 false 时,实时服务不跟踪任何成员,没有人显示为在线。
presence_show_activityboolean否为 false 时,仍显示在线状态,但移除查看和编辑详情。
timezonestring否工作区时区,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。

请求体

字段类型必填说明
emailstring是受邀人的邮件地址。
role_idsuuid[]否要分配的工作区角色,合计至少一个。
role_iduuid否单角色简写,与 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。

参数位置类型说明
idpathuuid成员用户 ID。

请求体

字段类型必填说明
role_idsuuid[]否新分配的角色集合,合计至少一个。
role_iduuid否单角色简写,与 role_ids 合并。
{ "role_ids": ["r2...", "r3..."] }

响应

更新后的成员对象,结构与成员列表条目相同。

移除成员

DELETE /organization/members/:id

将成员从组织移除。该成员的会话会立即停止选中此组织,因此下一次需要工作区的请求会返回 400,直到其切换到其他工作区。该成员在工作区内授予的所有 OAuth 应用授权都会撤销。API 密钥保留,因为它们属于工作区,而非创建它们的成员。

身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。

参数位置类型说明
idpathuuid成员用户 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。

请求体

字段类型必填说明
namestring是角色名称。
descriptionstring否角色说明。
colorstring否角色标签的十六进制颜色。
permissionsint否角色授予的组织权限位掩码。
{
  "name": "Sales rep",
  "description": "Run campaigns, view contacts",
  "color": "#22c55e",
  "permissions": 4352
}

更新自定义角色

PATCH /organization/roles/:id

编辑自定义角色。变更会传播到所有获分配该角色的成员,权限读取无需 JOIN 查询。

身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。

参数位置类型说明
idpathuuid角色 ID。

请求体

所有字段均可选,nil 字段保持不变。

字段类型必填说明
namestring否新名称。
descriptionstring否新说明。
colorstring否新的角色标签颜色。
permissionsint否新的权限位掩码。

删除自定义角色

DELETE /organization/roles/:id

移除自定义角色。返回 204 No Content。

身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。

参数位置类型说明
idpathuuid角色 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。

参数位置类型说明
idpathuuid邀请 ID。

响应

{ "message": "invitation cancelled" }

GET /organization/invitations/:id/link

为待接受邀请生成可分享的接受令牌,便于团队管理员复制有效的接受链接。邀请令牌只保存摘要,因此每次调用都会签发新的链接令牌,此前复制的链接失效;邮件中的链接仍可同时使用。

身份验证:仅限会话,API 密钥不可用。组织权限 manage_team。

参数位置类型说明
idpathuuid邀请 ID。

响应

{ "token": "inv_token_abc123" }

转移所有权

POST /organization/transfer-ownership

将组织所有权转移给另一位成员。

身份验证:仅限会话,API 密钥不可用。组织权限 transfer_ownership。

请求体

字段类型必填说明
new_owner_user_iduuid是要提升为所有者的成员用户 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 密钥不可用。需要已选择组织,且仅限所有者。

请求体

字段类型必填说明
confirmationstring是必须等于组织名称。
reasonstring否可选的自由文本原因。
{ "confirmation": "Acme Outbound" }

取消删除组织

DELETE /organization/current/danger-zone/delete

取消待执行的组织删除。

身份验证:仅限会话,API 密钥不可用。需要已选择组织。

请求体(可选)

字段类型必填说明
reasonstring否可选的自由文本原因。

响应

{ "message": "deletion cancelled" }

提交提高限额的申请

POST /organization/:orgId/limit-requests

提交提高组织某项限额的申请。提交时由服务端记录当前实际生效值。返回 201 Created。

没有发送套餐的工作区,包括免费工作区或 Warmup 套餐工作区,只能申请 max_email_accounts;其他字段返回 400 bad_request,因为工作区开始发送前,这些限额都不适用。每日发送量已不受限的工作区申请 daily_campaign_limit 时,也返回 400 bad_request,因为批准某个数值反而会施加上限,而不是提高上限。

身份验证:仅限会话,API 密钥不可用。

参数位置类型说明
orgIdpathuuid组织 ID。

请求体

字段类型必填说明
fieldstring是限额字段,例如 max_email_accounts、daily_campaign_limit。
requestedint是申请的值,必须大于当前实际生效限额。
reasonstring是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 密钥不可用。

参数位置类型说明
orgIdpathuuid组织 ID。

响应

{ "data": [ { "id": "lr1...", "field": "max_email_accounts", "status": "pending", "requested": 50 } ] }

取消限额申请

DELETE /limit-requests/:id

取消待处理的限额申请。仅限提交者。返回 204 No Content。

身份验证:仅限会话,API 密钥不可用。

参数位置类型说明
idpathuuid限额申请 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 必须且只能提供一个。

字段类型必填说明
tokenstring有条件必填/invite 链接中的机密令牌。
invitation_iduuid有条件必填调用者待接受列表中的邀请 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。需要已选择组织。

请求体

字段类型必填说明
namestring是1 至 255 个字符。
colorstring否十六进制颜色,默认 #94a3b8。
{ "name": "West coast", "color": "#0ea5e9" }

响应

创建的团队对象,成员初始为空。

获取团队

GET /teams/:id

返回单个团队及其成员。

身份验证:权限范围 READ_CRM · 组织权限 view_contacts。需要已选择组织。

参数位置类型说明
idpathuuid团队 ID。

更新团队

PATCH /teams/:id

部分更新团队名称或颜色。nil 字段保持不变。

身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。

参数位置类型说明
idpathuuid团队 ID。

请求体

字段类型必填说明
namestring否新名称。
colorstring否新颜色。

响应

更新后的团队对象。

删除团队

DELETE /teams/:id

删除团队。返回 204 No Content。

身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。

参数位置类型说明
idpathuuid团队 ID。

添加团队成员

POST /teams/:id/members

将已有组织成员加入团队,返回更新后的团队。

身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。

参数位置类型说明
idpathuuid团队 ID。

请求体

字段类型必填说明
user_iduuid是成员用户 ID,必须已属于该组织。
{ "user_id": "7b88..." }

响应

更新后的团队对象,包含新成员。

移除团队成员

DELETE /teams/:id/members/:userId

从团队移除成员。返回 204 No Content。

身份验证:权限范围 WRITE_CRM · 组织权限 manage_team。需要已选择组织。

参数位置类型说明
idpathuuid团队 ID。
userIdpathuuid成员用户 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_idstring是要订阅的 Stripe 价格 ID。
success_urlstring是结账成功后的重定向目标。
cancel_urlstring是用户取消时的重定向目标。
discount_codestring否要应用的折扣码。
{
  "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 密钥不可用。需要已选择组织。

请求体

字段类型必填说明
codestring是折扣码。
plan_iduuid否用于计算折后金额的目标套餐。
{ "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_urlstring是离开门户后 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_endboolean否为 true 时,在当前周期结束时取消,而非立即取消。
{ "cancel_at_period_end": true }

响应

{ "message": "subscription cancelled" }

更改套餐

POST /subscription/change-plan

变更组织套餐,并按比例结算差额。

身份验证:仅限会话,API 密钥不可用。组织权限 manage_billing。需要已选择组织。

请求体

字段类型必填说明
plan_iduuid是目标套餐 ID。
proration_behaviorstring否取值为 create_prorations、always_invoice、none 之一。
discount_codestring否要应用的折扣码。
intervalstring否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_idqueryuuid目标套餐 ID。

响应

按比例结算预览对象,包括明细项目、即时扣费和下次账单金额。

提交企业方案咨询

POST /subscription/enterprise-inquiry

提交企业定价咨询。返回确认信息和新的咨询 ID。

身份验证:仅限会话,API 密钥不可用。

请求体

字段类型必填说明
company_namestring是公司名称。
contact_namestring是联系人姓名。
contact_emailstring是联系人邮件地址。
estimated_volumeint否预计月度邮件量。
team_sizeint否团队人数。
notesstring否自由文本备注。
{
  "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 密钥均可。

另请参阅

本页目录

身份验证开始登录请求体响应确认登录请求体响应开始注册请求体响应确认注册请求体响应刷新令牌请求体响应开始重置密码请求体响应确认重置密码请求体响应开始通行密钥登录响应完成通行密钥登录请求体响应验证双重身份验证登录请求体响应第三方账户登录(原生应用)App Attest(原生应用)浏览器登录会话退出登录退出所有设备上的登录列出会话响应撤销其他会话撤销指定会话个人资料与账户获取当前用户响应更新个人资料请求体完成新手引导请求体上传头像响应移除头像修改密码请求体响应通知偏好与通知列表获取通知偏好响应更新通知偏好请求体响应列出通知响应将所有通知标为已读响应将指定通知标为已读响应注册设备令牌请求体响应删除设备令牌响应双重身份验证双重身份验证状态响应开始启用双重身份验证响应确认启用双重身份验证请求体响应重新生成恢复码请求体响应关闭双重身份验证请求体响应通行密钥开始注册通行密钥响应完成注册通行密钥请求体响应列出通行密钥响应重命名通行密钥请求体删除通行密钥账户高风险操作获取账户高风险操作状态响应安排删除账户请求体响应取消删除账户请求体(可选)响应WebSocket 连接初始化生成 WebSocket 令牌响应组织获取工作区发送限制状态响应创建组织请求体响应列出我所属的组织响应切换组织响应获取当前组织响应更新当前组织请求体响应获取组织限额响应列出成员响应邀请成员请求体响应更新成员角色请求体响应移除成员响应列出自定义角色响应创建自定义角色请求体更新自定义角色请求体删除自定义角色列出待接受的邀请响应取消邀请响应获取邀请链接响应转移所有权请求体响应上传组织头像响应移除组织头像获取组织高风险操作状态响应安排删除组织请求体取消删除组织请求体(可选)响应提交提高限额的申请请求体响应列出限额申请响应取消限额申请邀请(供受邀者使用)列出我尚未接受的邀请响应接受邀请请求体响应团队列出团队响应创建团队请求体响应获取团队更新团队请求体响应删除团队添加团队成员请求体响应移除团队成员订阅与计费获取订阅响应获取订阅及其限额响应获取试用状态响应获取功能状态响应创建结账会话请求体响应验证折扣码请求体响应创建账单管理门户会话请求体响应取消订阅请求体响应更改套餐请求体响应预览套餐变更响应提交企业方案咨询请求体响应优惠码与推荐奖励参考数据列出套餐响应列出时区另请参阅