Teams 会把匿名加入者拦在等候室里,而且越来越多地加上验证码。让 bot 登录一个真实的 Microsoft 365 账户就能同时绕开这两点。本文介绍 API、租户配置,以及 Teams 为什么和 Meet 的做法不同。

匿名 bot 加入 Teams 会议时会先进入等候室。必须有人注意到「有人在等待」的提示并放它进来。如果没人操作,bot 就会超时,你什么录像都拿不到。同时,Microsoft 也在不断给访客加入路径添加验证码挑战,而无头浏览器解不了验证码。
登录了真实 Microsoft 365 账户的 bot 可以跳过这一切。没有等候室,没有可能被主持人忽略的提示,也没有验证码。它在参会者列表里的名字来自这个账户。

按 bot 选择启用。不传 teams_config,你的 bot 就继续以访客身份加入,行为不变。
为什么这次不用 SAML
我们用 SAML 发布了已认证的 Google Meet bot,由我们的 API 充当你所拥有的 Workspace 的身份提供方。很自然的问题是:Teams 为什么不这么做?
Microsoft 没有提供对等的路径。要让 Entra ID 接受第三方 IdP,就意味着整个租户的完全联合认证,这比往 Legacy SSO 配置文件里上传一份证书重得多,我们也不会要求客户这么做。所以 Teams 的认证方式和真人一样:在 login.microsoftonline.com 里输入邮箱,再输入密码。
这一个差别决定了其他所有事情:
| Google Meet | Microsoft Teams | |
|---|---|---|
| 机制 | SAML 断言,我们充当 IdP | 用户名和密码 |
| workspace 资源保存什么 | 域名、证书、私钥 | 仅租户域名 |
| 凭据会离开我们的服务器吗? | 不会。我们在服务端签署断言。 | 会。bot 通过 TLS 获取一次。 |
| 存储的密文 | private_key_pem | password,加密存储 |
| 失败形态 | 证书不匹配、用户被停用 | 密码错误、MFA 提示 |
Teams bot 在加入时必须持有真实密码,因为除此之外没有别的办法把密码输进去。它通过一个需要认证的解析 endpoint 获取一次,该 endpoint 以一个短期有效的 session id 为键,形式和 Zoom bot 获取 OBF token 的方式一致。密码在静态存储时用 AES-256-GCM 加密,任何读取 endpoint 都不会返回它。
API
和 Meet 一样的两层结构:一个 workspace 代表一个 Microsoft 365 租户,login 挂在它下面。
POST /v2/teams-workspaces
GET /v2/teams-workspaces
GET /v2/teams-workspaces/:workspace_id
PATCH /v2/teams-workspaces/:workspace_id
DELETE /v2/teams-workspaces/:workspace_id
POST /v2/teams-logins
GET /v2/teams-logins
GET /v2/teams-logins/utilization
GET /v2/teams-logins/:credential_id
PATCH /v2/teams-logins/:credential_id
DELETE /v2/teams-logins/:credential_id这里的 workspace 很轻,因为没有证书要保存。它存在的意义是把同一个租户的账户归到一起,并且可以整体停用:
{
"name": "Contoso Production Tenant",
"domain": "contoso.onmicrosoft.com"
}租户任意一个已验证域名下的账户都可以加进来。
然后是每个账户:
{
"workspace_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
"name": "Teams Bot Pool — Account 1",
"email": "bot1@contoso.onmicrosoft.com",
"password": "...",
"email_group": "bots@contoso.onmicrosoft.com"
}password 只写不读。回读这个 login 时,除密码之外的内容都会返回:
{
"credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"workspace_id": "f0e1d2c3-b4a5-6789-0123-456789abcdef",
"name": "Teams Bot Pool — Account 1",
"email": "bot1@contoso.onmicrosoft.com",
"email_group": "bots@contoso.onmicrosoft.com",
"state": "active",
"last_error_message": null,
"last_error_at": null,
"last_used_at": "2026-08-10T14:22:03.000Z",
"active_session_count": 3,
"extra": null,
"created_at": "2026-08-01T09:12:44.000Z",
"updated_at": "2026-08-10T14:22:03.000Z"
}轮换密码就是用 PATCH 传入新密码。其他都不变,运行中的 bot 也保住自己的名额。
在 bot 上使用
{
"meeting_url": "https://teams.microsoft.com/l/meetup-join/...",
"bot_name": "Recording Bot",
"teams_config": {
"email_group": "bots@contoso.onmicrosoft.com",
"fallback": "anonymous"
}
}teams_config 的解析规则和 meet_config 完全相同:
| 你传入的内容 | 实际行为 |
|---|---|
null(默认值) | 以匿名访客身份加入。 |
{ "email_group": "X" } | 在分组 X 内的活跃 login 之间轮询。 |
{ "credential_id": "Y" } | 固定使用 login Y。 |
| 两者都传,且分组非空 | email_group 优先。 |
只传 { "email_group": "" } | 在团队全部活跃 login 之间轮询。 |
{ "email_group": "", "credential_id": "Y" } | 固定使用 login Y。空分组不算分组,所以 credential_id 生效。 |
池子占满时,fallback: "fail" 返回 FST_ERR_TEAMS_LOGIN_UNAVAILABLE 和一个 503,fallback: "anonymous" 则退回访客加入。对 Teams 来说,"anonymous" 比在 Meet 上更适合做默认值,因为访客加入通常还是能成功的,只是更慢、更不可靠。
一个账户能同时跑多少个 bot?
这个问题被反复问到,因为大家都见过 Teams 提示:你只能在一个会议中处于活动状态,其余的必须保持等待。
那个限制是按客户端会话算的,不是按账户算的。 它来自一个 Teams 应用实例只拥有一套音频栈。Microsoft 公布的限制页面并没有对每个用户的并发会议数设上限,而且他们自己给出的「同时参加两个会议」的建议就是用第二台设备或第二个浏览器配置文件。
每个 MeetingBaaS bot 都是独立的 pod,有自己的浏览器,会自己重新登录一次 login.microsoftonline.com。每个 bot 都是一个单独的客户端会话。一个账户可以同时支撑多个位于不同会议中的 bot。
每个 login 的默认上限是 20 个并发会话,GET /v2/teams-logins/utilization 会按这个上限报告:
{
"logins_total": 3,
"logins_active": 3,
"logins_invalid": 0,
"concurrent_sessions": 41,
"concurrent_capacity": 60,
"utilization_pct": 68,
"by_email_group": [
{ "email_group": "bots@contoso.onmicrosoft.com", "logins": 3, "concurrent": 41, "capacity": 60 }
]
}不过真正的天花板不是 Teams,而是 Entra ID。同一个账户从 N 个不同的 pod IP 同时登录 N 次,正是基于风险的条件访问专门要标记的模式;一旦触发,你会收到无头浏览器无法应答的 MFA 挑战。如果你已经接近饱和,先试着调高单账户的上限,再考虑开通更多账户;但要在单个账户上分别测到 5、10、20,观察有没有登录失败,不要一步跳到很高的数字。
租户配置
这部分需要格外小心,动手改之前请先把整节读完。
使用专用租户
在一个独立的租户里创建 bot 账户,例如 bot1@contoso-bots.onmicrosoft.com。Business Basic 就够了。
已认证的 bot 需要一些账户级设置,而这些设置不该应用到真人登录的租户上。单独的租户能把影响范围压到零。这不是锦上添花,而是下面所有步骤的前提。
确保 bot 账户不会收到 MFA 提示
新建租户默认开启安全默认值(Security Defaults),会在首次登录时强制弹出「保护你的账户安全」注册页。无头浏览器过不去,而这正是 Teams 登录失败最常见的原因。
有两种解决路径,选哪一种取决于你的租户:
专用 bot 租户,没有真人账户。 在整个租户范围内关闭安全默认值:entra.microsoft.com → 标识(Identity)→ 概述(Overview)→ 属性(Properties)→ 管理安全默认值(Manage security defaults)→ 已禁用(Disabled)。
租户里有真人账户。 保持 MFA 开启。把 bot 账户放进一个组,然后只把这个组从「要求 MFA」的条件访问策略中排除。这需要 Entra ID P1。配置更慢,但安全卫生要好得多。
无论走哪条路,都要再检查一下 bot 账户的旧版每用户 MFA 已禁用,并关掉身份验证方法注册推广活动,免得它以后又把提示带回来。
你要达到的最终状态是:用 bot 账户登录时依次出现邮箱、密码、「保持登录状态?」,中间不夹带任何其他页面。
先手工登录一次
在普通浏览器里手动登录每个 bot 账户,把 Microsoft 弹出的各种首次运行插页处理干净。道理和 Meet 那边的 Workspace 引导步骤一样:第一次登录无论如何都是交互式的,那就赶在 bot 需要这个账户之前把它做掉。
注册 workspace 和 login
curl -X POST https://api.meetingbaas.com/v2/teams-workspaces \
-H "x-meeting-baas-api-key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "contoso-bots.onmicrosoft.com"}'然后每个账户执行一次 POST /v2/teams-logins。
失败时是什么样子
如果一次登录失败的方式注定会继续失败,这个 login 就会被置为 invalid 并停止参与分配。有三种原因:
| 原因 | 含义 |
|---|---|
| 凭据错误 | 密码变更了,或者创建时就输错了。 |
| 需要 MFA | 安全默认值、某条条件访问策略,或者一次风险检测正在挑战这个账户。 |
| 超时 | Microsoft 响应慢,或者流程卡住了。 |
超时被当作临时问题处理,不会让任何东西失效。另外两种会,并且会记录 last_error_message、last_error_at,以及包含触发它的 bot 的 failure_data。
修复原因之后再重新启用:
PATCH /v2/teams-logins/:credential_id
{ "state": "active" }如果某个账户用了好几周一直正常,然后突然开始报「需要 MFA」,这几乎总是基于风险的条件访问在对你的并发量作出反应,而不是密码被改了。在动手重置任何东西之前,先去 Entra 里看登录 logs。
删除仍有会话在进行的 login 会返回 409。名额在 bot 进程退出时释放,包括崩溃退出的情况,所以一个死掉的 pod 不会永久占用容量。
相关阅读
- 已认证的 Google Meet Bot —— 同样的问题,用 SAML 解决,以及两者为何不同
- Meeting BaaS API 参考文档
如果你正在估算自己的业务量需要多少个账户,或者拿不准条件访问排除方案能不能通过安全团队那一关,我们很乐意和你聊聊。