☰
n8n连接飞书日历报错99991679?权限Scope与Token排查指南
2026/10/13 10:35:06 网站建设 项目流程

在n8n里把飞书日历接进自动化流程,第一回跑就撞上HTTP 400,响应体给了code:99991679和一句Unauthorized.,这事我印象太深了。当时第一反应是某个参数格式不对,毕竟400嘛,可把请求体翻了个遍也没看出毛病。后来弄明白,这个错误码跟参数关系不大,它真正想说的是:你调用的这个接口,不在当前令牌的权限范围内。

这篇文章就把这套排查过程完整写下来——从错误码含义、飞书token机制,到n8n里该配什么凭证、后台该怎么开通权限,再到用curl做最小化复现,一步步定位。不管你是刚开始接触n8n和飞书API,还是在已有流水线里撞上这个报错,按这个顺序查基本都能解决。

1. 先看报错本身:HTTP 400是表象,99991679才是关键

先还原一下报错现场。在n8n的HTTP Request节点里请求飞书日历接口,执行结果直接进了ERROR,点开错误详情看到状态码400,响应体大致长这样:

{ "code": 99991679, "msg": "Unauthorized.", "data": null, "request_id": "73f0a8b1-xxxx-xxxx-xxxx" }

很多人看到400的第一反应就是请求参数、JSON格式、content-type不对,这个方向不能说是错的,但在飞书的错误码体系里,HTTP状态码只是一个粗糙的分组,真正要读的是业务code。99991679在飞书的错误码表里对应的就是Unauthorized,触发条件基本集中在权限层:当前访问令牌所拥有的scope不包含你要调用的这个URI。

所以碰到这个响应,第一步绝对不是去改参数,而是把三样东西抄下来:

  • 请求的完整接口URL(尤其注意是哪个calendar_id)
  • Authorization头里的token字符串长什么样(是t-开头的tenant token还是user token)
  • 响应里的request_id(后面真要找平台支持排查时,这个最有用)

1.1 为什么HTTP状态码是400而不是401

正常REST接口里,token没带或者token无效通常返回401,Unauthorized这个文案也经常跟401绑定。但飞书在这一点上做得很"统一":大量业务型错误都用400承载,再靠code细分。我观察下来,这种设计对调用方的好处是错误码体系很清晰,坏处就是像我当初那样,被400误导去排查参数。

记住一个判断原则:如果响应体里出现code字段,就不要再盯着HTTP状态码猜原因,code才是业务层的答案。99991679这种以9999开头的大段位错误码,基本都指向权限体系,跟参数格式化没太大关系。继续在时间戳格式、日期字符串上折腾,只会浪费时间。

1.2 参数型400和权限型99991679怎么区分

为了后面排查不跑偏,这里直接列一个区分表:

特征普通参数错误99991679 权限错误
msg内容invalid param / 参数错误Unauthorized.
触发位置请求体字段、时间格式等令牌授权范围
修改请求参数能否解决通常能不能
正确的动作修JSON结构换token或加scope

拿日程创建来举例:如果把start_time写成了"2025-01-01"而不是时间戳格式,返回的msg会直接指出字段问题,不会轻飘飘来一句Unauthorized.。所以看到Unauthorized.,排查方向就该往"授权"两个字上走。

2. 飞书日历接口的令牌体系:token类型不对是第一个雷区

飞书API里的token分两大类,很多人第一次接触时会混在一起,我先把区别放出来。

2.1 tenant_access_token和user_access_token的区别

对比项tenant_access_tokenuser_access_token
代表身份应用本身具体用户
获取方式app_id + app_secret 直接换走OAuth授权码流程
有效期约2小时约2小时,可配合refresh_token刷新
典型用途应用维度数据访问个人维度数据
日历接口适用性多数不适用日历相关接口主要认这个

日历是典型的用户维度资源:你的日程、你的主日历、别人共享给你的日历,都挂在具体用户身份下面。飞书日历接口在权限要求上,标注的往往也是"需要用户授权"类型的权限。换句话说,你在n8n里如果图省事,直接用app_secret换来的tenant_access_token填进Authorization头,跑去查某个用户的日历,撞上99991679是大概率事件。

2.2 scope是什么,它和token是什么关系

scope可以理解成"令牌随身携带的权限标签"。用户点授权时,应用申请了哪些权限,授权页就会展示哪些;用户同意后,飞书签发的user_access_token里就只包含这些scope。之后你拿这个token调接口,飞书会先检查token里的scope是否覆盖当前URI。

打个比方:token是门禁卡,scope就是这张卡上开通的门禁区域。99991679这句话的真正含义是——门禁系统发现你手里这张卡没有这个门的权限,于是把你拦在门外。

所以排查一定要记住:token类型对不对,和scope全不全,是两件事,但都会导致同一个错误码。这也是为什么很多人在n8n里来回试token、刷新、重新认证,折腾半天还报一样的错——因为问题可能根本不在token本身,而在应用后台的权限配置。

2.3 scope和token在OAuth里是怎么组装在一起的

OAuth授权URL里会带一个scope参数,n8n的OAuth2凭证里也有一个Scope输入框。浏览器打开授权页后,飞书根据这个scope参数展示权限列表,用户同意后飞书返回的code去换token,换出来的token就带上了这些scope。

这里有一个很阴的细节:如果你先把应用接好了、用户也授权过了,之后才去开发者后台追加日历权限,那么原来已经签发的token不会自动带上新scope。也就是说,权限改了,旧的user_access_token仍然缺日历权限,调用还是99991679。要解决只能重新走一遍OAuth授权,让飞书签发一颗带新scope的token。

3. 从n8n配置出发的排查链路:三个最隐蔽的坑

这一章专门讲n8n侧。我的经验是,n8n本身很少有问题,出问题的大多在凭证类型、授权顺序和token续期方式上。

3.1 用OAuth2凭证,还是手填Bearer

如果你用的是现成的飞书节点,直接配置它自带的凭证类型即可;如果和我一样用HTTP Request节点自己封装,强烈建议选OAuth2方式的凭证,不要图省事在Header里写死一个Bearer。

n8n上OAuth2凭证的关键配置项可以对应到飞书的参数:

  • Authorization URL填写飞书的授权端点,类似 https://open.feishu.cn/open-apis/authen/v1/authorize
  • Access Token URL填写换token的端点,类似 https://open.feishu.cn/open-apis/authen/v1/access_token
  • Scope填入日历相关权限标识(以开发者后台权限列表里的编号为准,常见的是带冒号的字符串)
  • Client ID和Client Secret分别填应用的app_id和app_secret

这里最容易忽略的是重定向URL。n8n会生成一个回调地址,你需要把这个地址完整配置到飞书开发者后台的"重定向URL"里,配置不一致的话授权流程根本走不完,那又是另一个报错了。配置完成后,在n8n里点一下连接授权,浏览器会跳到飞书授权页,这次授权同意之后,n8n才能拿到合法的user_access_token。

3.2 授权顺序比想象中更关键

我踩过的一个典型顺序坑:先在n8n里连接了飞书OAuth,把工作流跑通了,后来发现日历权限没加全,跑到开发者后台把scope补上、发布版本,满以为万事大吉,结果n8n再跑还是99991679。

原因就是上一章说的,旧token不自动继承新scope。正确顺序是先确认开发者后台的权限全部开通并发布生效,然后再去n8n里做OAuth连接。如果已经连过了,就断开重连,强制执行一次新的授权。别嫌麻烦,这一步能省下后面大量的排错时间。

3.3 token过期和"写死Bearer"的隐患

飞书的访问令牌有效期基本是2小时左右。如果你在n8n里手动把token填进Header,那这个工作流就是个定时炸弹:刚配置好能用,两小时后就因为token过期报错。而且过期token调用接口同样可能返回99991679(也有些场景返回其他token失效类错误码),很容易跟scope问题混淆。

推荐的做法是让token始终动态获取。一种办法是直接用n8n的OAuth2凭证,它内部会处理刷新逻辑;另一种办法是把工作流拆成两步:第一步发请求换取或刷新token,第二步把返回的token用表达式拼到后面的飞书日历请求里。这样起码token不会过期到不可用。

4. 飞书开放平台后台检查:权限列表、版本发布与用户授权状态

如果n8n凭证配置看起来没问题,那就该去飞书开发者后台核对应用侧的权限状态了。很多99991679最终都定位在这一层。

4.1 在权限管理里把日历相关权限补全

打开开发者后台 → 应用 → 权限管理,搜索"日历""日程""calendar"等关键字,把看到的只读和读写类日历/日程权限都勾上。注意飞书的权限标识经常升级换代,老的在个别场景下可能保留、新的另行命名,所以不要凭记忆勾,直接在权限中心搜一遍最稳妥。

勾选完成后,需要确认权限状态从"可用"变成"已开通"。如果只是能勾选但没进入开通状态,说明还没走完发布流程,后面第4.2节说的就是这个。

4.2 权限变更后必须重新发布版本

这是重灾区中的重灾区。飞书的应用权限配置不是改完立即生效的,它需要走一次"创建版本 → 提交发布 → 管理员审核"的流程。企业自建应用通常由管理员直接审核,审核通过后,新配置的权限才会被真正打进应用的可信配置里。

我见过不少情况:开发者后台的开关全都亮了,但版本还是旧版本,于是线上调用一直99991679。检查方法很简单——在后台的"版本管理与发布"页面看最新版本是否已经"已发布",如果不是,创建一个新版本重新发布。有些时候你以为自己发布过了,其实只保存了草稿,这种现象很常见。

4.3 用户的授权状态和可用范围

如果应用已发布、权限也开了,还是报99991679,再看两个东西:

  • 应用的"可用范围"是否包含你要操作的用户。如果用户不在可用范围里,授权流程可能能走通,但签发的token根本无法访问日历数据。
  • 这个用户本人是否完成了OAuth授权。日历是用户维度数据,每个人都需要单独授权。有些人是在权限配置完成之前授权的,需要重新走一遍授权。

我一直建议在n8n的OAuth凭证上做一次"重新连接/重新认证"操作,让用户的授权状态强制刷新,这一步能解决掉不少权限变更后遗留的问题。

5. 用curl做最小化复现:把问题从n8n里剥离出来

当n8n和后台检查都做完还报错,下一步就是绕过n8n,直接在命令行用curl复现一次。这个环节价值在于:它能把"n8n配置问题"和"飞书应用问题"彻底分离开。curl通了,问题肯定在n8n侧;curl也报错,那问题在飞书侧,n8n只是背锅。

5.1 先验证app_id和app_secret本身没问题

用下面的请求换取一个tenant_access_token,注意这里用的是internal端点,自建应用走这个:

curl -X POST 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' \ -H 'Content-Type: application/json' \ -d '{"app_id":"cli_你的应用ID","app_secret":"你的应用密钥"}'

正常响应里会有tenant_access_token和过期时间expire:

{ "code": 0, "msg": "ok", "tenant_access_token": "t-xxxxxxxx", "expire": 7200 }

如果这一步就报错,说明app_id/app_secret本身有问题,跟日历无关,先去后台换密钥再排查。

5.2 用tenant token直接调日历接口看看反应

curl -X GET 'https://open.feishu.cn/open-apis/calendar/v4/calendars?page_size=20' \ -H 'Authorization: Bearer t-xxxxxxxx'

这里返回99991679一点都不奇怪,因为tenant token默认碰不了用户日历。这一步的意义是确认错误码的复现路径:如果连"获取日历列表"都报99991679,说明当前token的scope里完全没有日历权限,或者token类型本身不对路。

5.3 走一遍用户授权,用user_access_token再测

如果确认接口需要用户身份,就不要再拿tenant token硬试了。构造一个授权URL,让用户在浏览器里点一次:

https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_你的应用ID&redirect_uri=你的回调地址&scope=calendar:calendar.event&state=任意字符串

用户同意后,浏览器会跳转到redirect_uri,地址栏里会出现code参数。拿到code再换user_access_token:

curl -X POST 'https://open.feishu.cn/open-apis/authen/v1/access_token' \ -H 'Content-Type: application/json' \ -d '{"app_id":"cli_你的应用ID","app_secret":"你的应用密钥","grant_type":"authorization_code","code":"回调地址里的code参数"}'

然后用换回来的user_access_token重新调日历列表:

curl -X GET 'https://open.feishu.cn/open-apis/calendar/v4/calendars?page_size=20' \ -H 'Authorization: Bearer u-xxxxxxxx'

5.4 基于curl结果做判定

测试动作返回情况结论
tenant token 调日历列表99991679意料之中,别继续;需要user token
user token 调日历列表code为0飞书侧没问题,回n8n查凭证和授权
user token 调日历列表还是99991679问题在scope或版本发布,回后台检查
任意token请求其他code按对应错误码另查,与本文场景不同

这一套测完,问题基本能定位到具体层级。我自己做的时候习惯把每一步的响应体完整存下来,尤其是request_id,因为如果真的得找平台支持排查,request_id是必不可少的线索。

6. 解决之后:验证完整流程与后续工作流设计建议

错误码最终消失只是第一步。我建议顺便把下面这几件事也做了,免得同一个坑换个姿势再踩一遍。

6.1 正常返回时的响应长什么样

日历日程接口正常返回时,code字段是0,msg是success。比如获取日程列表时能看到日程的summary、start_time等字段:

{ "code": 0, "msg": "success", "data": { "has_more": false, "items": [ { "event_id": "event_xxxx", "summary": "项目周会", "start_time": { "timestamp": "1710000000", "timezone": "Asia/Shanghai" } } ] } }

具体字段以飞书当前版本文档为准,但code=0这个形态是一致的。如果你的响应里不是这个形态,大概率还没走到正轨。

6.2 calendar_id不要手猜,用列表接口取

再提醒一个和权限耦合的坑:URL里的calendar_id。有些人在浏览器里看到日历ID字符串就直接抄过来用,结果因为那个日历根本不是当前token可访问的,出现权限类报错。正确做法是先调"获取日历列表"接口,从返回的items里拿到当前用户真正可见的calendar_id,再拿它去查日程。如果业务上要操作别人共享的日历,还得确认对方确实给了权限。

6.3 工作流里给99991679做个分支处理

n8n里建议在飞书请求节点后面加一个IF或者Switch节点,判断响应里的code。如果code等于99991679,不要假装重试,直接发一条提醒——大概率是权限变更或token需要重新授权,人工介入处理比自动重试几百次更快。

我自己的习惯是把授权、刷新token、取日历列表、取日程做成独立的几个节点链,任何一个环节挂了,错误信息里都能明确看到是哪一步。把"获取token"和"实际业务请求"耦合在同一个节点里,排查时非常痛苦。

6.4 一个关于curl验证的补充技巧

如果你在n8n里看到其他9999段的错误码,不要照搬本文的排查流程——先查飞书官方错误码表,把code的含义翻译出来再动手。盲目冲去配置里翻权限,有时候会浪费一个下午。99991679这个码的特征足够鲜明,只要你确认了它,那权限方向就是你唯一要去认真盘查的。

按我踩坑的经验来说,99991679这个错,十次里有七八次是scope没配上或者版本没发布,剩下两三次是token类型不对,真跟"参数格式"关系不大。排查顺序理顺以后,五分钟就能定位:先确认token类型,再核对scope,最后用curl绕过n8n复现一次。至于n8n那边,还是那句老话:能自动刷新的OAuth2凭证,就别自己手写Bearer。

最后再分享一个小技巧:每次在n8n里跑飞书相关节点时,我会在错误信息里把request_id摘出来记到日志里。等哪天连续报错需要对照分析时,这些request_id能帮你快速判断是应用整体权限问题,还是某个用户token偶发问题。省下的时间,都够多配两条工作日历了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询