做飞书多维数据表格自动化的同学,大概率都有过这种体验:Python 代码明明写得很顺,逻辑也简单,结果一跑就给我甩个 permission denied ,或者干脆返回一个“应用无权限”的提示。这时候你才会发现,真正花时间的不是调接口,而是把飞书那一堆权限配置理清楚。

这篇教程是系列的第二篇,上一篇已经把 Python 调飞书 API 读写多维表格的主流程走了一遍,这一篇我单独把“设置多维数据表格的权限”这件事拆开揉碎讲清楚。适合两种人看:一种是刚接触飞书开放平台、正准备从零接入的小白;另一种是代码已经写完、但一直被权限报错卡住的开发者。


1. 先搞明白:多维数据表格的权限到底卡在哪

飞书多维数据表格的权限,并不像我们平时理解的那样“勾一个开关就行了”。很多同学第一次接的时候,以为在开发者后台把 bitable 权限勾上,接口就能通,结果现实会给你上一课。

其实飞书开放平台的权限体系是分层的,至少包含下面几层:

  • 应用层面的权限开关 :也就是 scope 权限,决定你这个应用能不能调用某个 API。
  • 文档层面的协作者授权 :多维表格本身是文档,你得把应用机器人加进文档的协作者列表里,否则就算应用有权限开关,它也进不去那张表。
  • 用户身份与租户身份的区别 :你用 tenant_access_token 调接口,代表的是应用所在企业;你用 user_access_token 调接口,代表的是某个具体用户。两者能访问的数据范围完全不一样。
  • 版本发布与审核 :你改了权限之后,必须发布新版本,企业管理员审核通过之后,新的权限才会真正生效。

我习惯用一个类比来解释这几层关系:你的应用是一个访客, scope 权限就是它的“门禁卡”,告诉这套系统它能进入哪些大门;而文档授权是“业主开门”,即使访客有了门禁卡,业主不让他进房间,他依然只能站在门外。把这层关系想明白,后面遇到的绝大多数权限问题都能定位。

所以如果你勾了权限还是报错,先不要怀疑代码,按照这个顺序排查:应用是否发布了新版本、表格文档是否把应用机器人加为了协作者、当前用的 token 身份是否是预期身份。

1.1 权限体系分层速览

为了方便对照,我把层级和关键检查点整理成了一张表:

层级 作用对象 关键配置 容易忽略的点
应用 scope 权限 开放平台应用 在权限管理中勾选对应 API 权限 勾选后必须发布新版本
文档协作者授权 多维表格文档 把应用机器人添加到文档成员中 只给表格链接但未授权,仍然报无权限
Token 身份 请求凭证 选择 tenant 还是 user token 两个 token 看到的范围差异很大
云空间权限 多维表格所在目录 确认所在文件夹对应用可见 云空间限制也会导致表格不可读
管理员审核 企业后台 发布版本后需审核通过 部分企业开启了强制审核,需人工通过

这五个层级,任何一个有问题,你在 Python 端收到的报错可能都差不多。所以我在遇到权限问题时,基本都是按这个表从上往下一个个对。

1.2 为什么已经勾选了权限还是报错

这是我在各种技术群里看到出现频率最高的问题。明明后台权限已经勾上了,为什么还报 94010000 类似这样的权限错误?

答案通常落在两个地方。

第一个是 没有发布新版本 。飞书的权限配置并不是“保存即生效”,你勾选权限之后,还需要创建一个应用版本并发布。如果企业开启了管理员审核,还得等管理员通过。在这个流程走完之前,应用真正运行时使用的还是旧版本的权限配置。

第二个是 表格文档没有授权给应用 。我一开始也在这里栽过跟头,当时以为权限开关开了就万事大吉,结果用 Python 反复请求都是 403。后来才发现,得多维表格文档右上角的分享按钮里,把“应用机器人”添加为协作者,并且给“可编辑”权限。这一步不做,应用连读取表格元数据都会失败。


2. 创建自建应用并拿到凭证

在配置任何权限之前,你首先得有一个应用。飞书开放平台的企业自建应用,相当于给你的自动化脚本发了一个身份凭证。这个身份凭证由 App ID 和 App Secret 共同组成,后面获取访问令牌全靠它。

2.1 进入开发者后台并创建应用

打开飞书开放平台,登录你的企业管理后台账号,在“开发者后台”里选择“企业自建应用”,点击创建应用。名称可以随便起,比如“多维表助手”,图标可以先用默认的,后面再换。

有一点提示:创建一个应用时选择的“可用范围”会影响谁能使用这个应用。如果你只是自己调试,选择“仅部分成员”并把你自己加进去就够了。如果选成“全体成员”,流程上不会有错,但在企业场景下容易牵扯到不必要的审批流程。

创建完成之后,进入应用详情页,你会看到左侧有一排菜单。这里我建议先把两样东西记下来:

  • App ID :应用的唯一标识,形如 cli_xxxxxxxxxxxx 。
  • App Secret :应用的密钥,形如一串随机字符串。

这两个值后面在 Python 代码里要用到,建议直接存到环境变量或者本地配置文件中,不要硬编码在代码里提交到仓库,不然哪天代码泄露,整个应用的凭证就全完蛋了。

2.2 拿到凭证后先做一次连通性测试

拿到凭证之后,不要急着去写业务逻辑。先用最简短的代码验证一下能不能通过接口获取到访问令牌,这个步骤能帮你把“网络问题”“参数错误”“凭证失效”这些基础问题提前挡在门外。

我一般会直接用 requests 库来测:

import requests

url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
payload = {
    "app_id": "cli_xxxxxxxxxxxx",
    "app_secret": "你的_app_secret"
}

resp = requests.post(url, json=payload)
print(resp.json())

如果返回结果里有 "code": 0 和 "tenant_access_token" ,说明凭证没问题;如果返回 "code": 10003 之类的错误码,说明 App ID 或 App Secret 不对,回开发者后台重新核对一下。

这一步很重要,因为后面所有 API 调用都要带着这个 token 走,如果 token 都拿不到,后面配置再多权限也白搭。


3. 按最小权限原则配置多维数据表格权限

飞书开放平台对多维数据表格开放的 API 权限,在权限管理页面里通常以 bitable 开头。打开应用的“权限管理”页面,搜索“多维数据表格”或者“bitable”,会看到一列权限开关。这里我建议遵循最小权限原则:只需要“读取”就只开读权限,需要“写入”再开读写权限,不要图省事把权限全勾上。

3.1 常见 bitable 权限范围和适用场景

权限标识 权限能力 适用场景
bitable:app 查看、创建、编辑多维数据表格 常用,适合读写表格和记录
bitable:app:readonly 只读访问多维数据表格 只做数据查询和分析
bitable:app:manage 管理多维数据表格 需要创建新表、删除表时使用
bitable:app:write 写入多维数据表格记录 只做数据写入时可以单独选

我自己在做数据同步脚本的时候,通常只开 bitable:app ,这个权限能覆盖查看表格元数据、读取记录、新增记录、更新记录这几类常规操作,既省事又不会引入多余的权限风险。

如果只是写个定时任务,把某个外部系统的数据推送到飞书表格里,那其实 bitable:app:write 就够了,连读权限都不用开。

3.2 发布版本并等待权限生效

勾选权限之后,页面右上角会提示你“创建版本并发布”。这一步新手容易忽略,觉得权限开好了就能用了。实际上飞书的权限体系会在运行时校验应用版本,你必须:

  1. 在“版本管理与发布”页面填写版本号,比如 1.0.0 。
  2. 填写版本说明,比如“开通多维表格读写权限”。
  3. 点击“申请发布”,提交给企业管理员审核。

如果企业后台没有设置强制审核,发布后会立即生效;如果有管理员审核流程,你需要去企业管理后台帮自己通过一下,或者在群里喊管理员放行。

这里有个经验供参考:在开发调试阶段,我会把应用可用范围设置成仅自己,并且选择无需管理员审核的发布模式;等到代码稳定、需要给业务团队用的时候,再申请正式发布并走审核流程。这样开发效率会高很多。

3.3 把表格文档授权给应用,这是最关键的一步

这一步在整个教程里最容易踩坑,也是我见过最多人翻车的地方。

打开你要操作的那张多维数据表格,在页面右上角点击“分享”按钮。弹窗里除了可以添加成员,还有一个搜索框可以搜索“应用”。你需要搜索你刚刚创建的那个应用的名称,比如“多维表助手”,然后把它添加为协作者。

添加的时候,角色一定要选“可编辑”。如果你只给了“可阅读”,那么 Python 端执行写入操作时会被拒绝;如果你想做的是数据查询,那给“可阅读”也够用。

还有一种情况是表格嵌在云空间文件夹里,文件夹本身对应用不可见。这种问题通常表现为:应用有权限、文档也分享给应用了,但 API 返回“文档不存在”。如果遇到这种情况,需要检查一下这张表所在的云空间或者文件夹是否也对该应用可见。

做完文档分享这一步,我再强调一下: 这一步和 scope 权限缺一不可 。只有 scope 没有文档授权,接口报无权限;只有文档授权没有 scope,接口同样报无权限。两者是“与”的关系,不是“或”的关系。


4. Python 端接入代码解析与验证

配置完权限之后,你终于可以回到 Python 这边做真正的验证了。我这里提供一个完整的最小示例逻辑:获取 token → 读取表格 → 往表格加记录。

4.1 获取 tenant_access_token 的标准写法

在飞书 API 体系中, tenant_access_token 代表“企业自建应用身份”。也就是说,你的脚本以应用的身份去操作表格,而不是以某个员工的个人身份。很多后台自动化的场景都用它,因为它不依赖某个用户是否在线。

获取 token 的接口路径是固定的,值得注意的坑是:token 有有效期,通常是 2 小时,建议在代码里把 token 缓存下来,快过期时再重新获取。频繁获取 token 不仅没有意义,还容易触发频控。

import requests
import time

APP_ID = "cli_xxxxxxxxxxxx"
APP_SECRET = "你的_app_secret"

cache = {}

def get_tenant_access_token():
    now = time.time()
    if cache.get("expire_at") and now < cache["expire_at"]:
        return cache["token"]
    
    resp = requests.post(
        "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
        json={"app_id": APP_ID, "app_secret": APP_SECRET},
        timeout=10,
    )
    data = resp.json()
    if data.get("code") != 0:
        raise RuntimeError(f"获取 token 失败: {data}")
    
    cache["token"] = data["tenant_access_token"]
    cache["expire_at"] = time.time() + data["expire"] - 60
    return cache["token"]

这段代码里减掉的 60 秒是保险机制,防止 token 刚到手就过期。实际项目中可以直接用一个全局变量保存,没必要引额外的缓存库。

4.2 用代码读取多维数据表格的表信息和记录

拿到 token 之后,读取表格的第一步是拿到表格的 app_token 。 app_token 就是多维表格文档的识别标识,一般在多维表格的链接里就能看到。比如链接长这样 https://xxx.feishu.cn/base/xxxxx ,那一串 xxxxx 就是 app_token 。

我们还不知道表 ID 时,可以调用接口获取表格下有哪些数据表:

def list_tables(app_token):
    token = get_tenant_access_token()
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json; charset=utf-8",
    }
    resp = requests.get(url, headers=headers, timeout=10)
    return resp.json()

如果返回的 code 是 0,并且 data.items 里有数据,那就说明你已经拿到了数据表列表。这个接口非常能检验前面那些权限配置是否正确,因为如果你漏了文档分享授权,这一步就会报权限错误。

获取到 table_id 之后,再调用记录查询接口来读取表格里的具体行数据:

def list_records(app_token, table_id):
    token = get_tenant_access_token()
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records"
    headers = {"Authorization": f"Bearer {token}"}
    resp = requests.get(url, headers=headers, timeout=10)
    return resp.json()

响应里的 data.items 就是多行记录,每条记录的字段名对应多维表格里的列名。不过要提醒一句:飞书把字段值封装得比较深,日期、人员、附件这类类型返回的数据结构和纯文本不一样。排查数据格式的时候,建议先拿一条记录打印出来看看。

4.3 用代码往表格写入数据

读取没问题之后,再来验证写入。写入记录用的是 POST 请求,注意传参格式是 records 列表,每个元素是一个 fields 字典,字段名必须和多维表格里的列名完全一致。

def add_records(app_token, table_id, rows):
    token = get_tenant_access_token()
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json; charset=utf-8",
    }
    payload = {"records": [{"fields": row} for row in rows]}
    resp = requests.post(url, headers=headers, json=payload, timeout=10)
    return resp.json()

调用时这样传参:

rows = [
    {"姓名": "张三", "状态": "已完成"},
    {"姓名": "李四", "状态": "待处理"},
]
result = add_records("你的_app_token", "你的_table_id", rows)
print(result)

如果返回结果里 code 是 0,说明写入成功。这里有个非常常见的问题:字段名和表格里的列名不完全一致,比如多了个空格或者用了别名,就会出现“字段不存在”之类的报错。建议在调试阶段先把表格表头截图或者复制出来,照着写字段名。

另外,批量创建接口单次最多支持的记录数有限,一次传几百行很容易触发限流。我通常每次控制在 100 条以内,分批写入,中间加个 time.sleep(0.5) ,这样既稳定又不容易被频控封禁。


5. 常见问题与权限排查实战技巧

既然这篇文章的重点是权限,那这一部分我就集中写权限相关的报错和排查经验。下面这些都是我在真实项目里遇到过、也帮别人排查过的问题,整理出来给你当速查表。

5.1 高频报错速查表

报错信息或现象 大概率原因 解决方向
permission denied / 权限不足 scope 未勾选或版本未发布 回权限管理重新勾选,发布新版本
app not found / 应用不存在 App ID 填错或应用被停用 核对凭证,确认应用状态
document not found 表格未授权给应用,或云空间不可见 检查文档分享,检查云空间目录
写入时报 field not found 字段名和表格列名不一致 复制表头核对字段名
返回 code: 99991 等频控码 请求频率过高 减少单次批处理量,加延时
读取人员字段为空 权限不足读取部分字段类型 确认人员字段是否需要单独开权限
版本发布后仍然报旧权限错误 发布流程未走完或管理员未审核 后台确认发布状态和审核状态
使用 user_access_token 才能访问 该表格对应用机器人的授权丢失 重新通过文档分享添加应用为协作者

这张表不敢说覆盖所有情况,但覆盖了 80% 的新手报错。

5.2 一套亲测有效的排查顺序

我遇到权限问题时的排查顺序,基本是下面这样的,按照这个顺序走,很少会白折腾:

  1. 先确认 token 能不能拿到 。如果 tenant_access_token 获取失败,直接查凭证和网络,不用看后面的权限。
  2. 再确认 app_token 和 table_id 是否正确 。很多“无权限”其实只是资源标识写错了。
  3. 用开发者后台的“接口调试”工具直接试一次 。飞书开放平台提供了在线调试能力,你可以在里面用当前应用的 token 直接调接口。如果在线调试能通,你的权限配置就是没问题的;如果在线调试都不通,那问题出在应用配置上,和 Python 无关。
  4. 检查文档分享 。确认应用机器人已经加到多维表格协作者里,而且角色是“可编辑”。
  5. 检查版本发布状态 。确认最新版本已经发布,并且审核通过。
  6. 最后才怀疑代码 。代码层面的问题通常不是权限,而是字段名、请求格式或者参数拼写。

这套顺序帮我节省了大量时间。如果你自己排查没有头绪,建议也按这个顺序来,别一上来就在代码里打日志,那样往往越查越乱。


6. 最后分享几个实在的经验

写到这里,这篇教程的主体内容基本讲完了。关于权限配置这件事,我还想从个人经验角度补充几点:

第一, 权限配置永远比代码更容易出错 。很多同学一遇到报错就怀疑是不是 requests 写法不对,实际上飞书 API 的接口封装很规范,反倒是权限这种“看不见摸不着”的配置,出了问题最难定位。所以我的习惯是,每次新写一个自动化脚本,先花十分钟把权限检查一遍,再开始写代码。

第二, 调试阶段尽量用最小权限 。只开当前功能需要的权限,不要想着“反正都接了,干脆全开”。权限越多,版本审核时管理员越容易卡你,后续如果应用被安全审计,全量权限也是减分项。按最小权限来,对谁都有好处。

第三, token 缓存一定要做 。我看到很多教程里没有提这一点,但实际项目里如果没有 token 缓存,脚本每次运行都要重新获取 token,一方面慢,另一方面容易被限流。这篇文章里的示例已经写了缓存逻辑,你可以直接参考。

第四, 线上出问题先看版本状态,别看代码 。我自己有一次改了权限配置,本地怎么调都通,一发到线上就报权限错误。后来才发现线上跑的应用版本没更新。从那以后,我每次改完权限,都会先确认目标环境里应用的实际版本号。

多维数据表格的权限配置,说到底就是两个词:一个是“授权”,一个是“发布”。把这两个词牢记在心里,再配合上面的排查顺序,飞书 API 调用这条路基本就通畅了。下一篇教程,我会继续写如何对表格里的数据进行复杂的筛选和更新,包括如何用 Python 实现增量同步、如何批量更新已有记录,如果你正在做类似的事情,到时候可以接着看。

更多推荐