获取应用令牌并发送消息
从自建应用鉴权开始,向应用可用范围内的一位成员发送文本。
← 返回平台功能接入准备
- 自建应用启用机器人能力,开通 im:message:send_as_bot,并发布/启用版本。
- 目标成员在应用可用范围内;open_id 必须属于当前应用。
准备环境变量
示例值需要替换为自己的凭证或资源 ID。代码在本机或服务端运行;密钥不应放进公开网页。
FEISHU_ACCESS_TOKEN- 先运行应用消息场景的获取凭证操作,将响应 access_token 放入服务端环境变量;不是 App Secret。
export FEISHU_ACCESS_TOKEN='replace_with_tenant_access_token'以上 export 适用于 macOS / Linux 的终端,在同一终端内运行后续代码。Python 示例保存为 request.py 后执行 python3 request.py。
调用步骤
- 01
开发者后台 → 凭证与基础信息取得 App ID 和 Secret,替换第一步请求体。
- 02
获取令牌后,将响应 access_token 保存为 FEISHU_ACCESS_TOKEN。
- 03
从 API 调试台的成员选择器、消息事件或获授权的通讯录接口取得用户 open_id,替换 receive_id。
- 04
运行发送消息操作;如果改发群消息,将 receive_id_type 改为 chat_id,receive_id 改成群 ID,并先把应用机器人加入该群。
获取自建应用令牌
当前 OAuth 端点支持 JSON,本例选 client_secret_post。先替换 body 中两个假凭证;不可同时添加 Basic 认证。
https://accounts.feishu.cn/oauth/v3/token请求头
Content-Typeapplication/json
参数说明
参数表可左右滑动,查看完整说明。
| 字段 / 位置 | 类型 / 必填 | 说明 |
|---|---|---|
grant_type请求体 | string必填 | 固定 client_credentials。 |
client_id请求体 | string必填 | 本例认证方式必填,开发者后台 → 凭证与基础信息中的 App ID。 |
client_secret请求体 | string必填 | 本例认证方式必填,同页 App Secret,仅在服务端保存。 |
scope请求体 | string可选 | 可传空格分隔、已开通的应用身份权限;省略时按租户已开通权限签发。 |
调用代码
替换示例 ID 与请求体内容后运行。cURL 会报 HTTP 错误,业务状态还需按下方返回示例核对。
cat > request-body.json <<'JSON'
{
"grant_type": "client_credentials",
"client_id": "cli_replace_with_app_id",
"client_secret": "replace_with_app_secret"
}
JSON
# 执行后核对返回 JSON 的业务状态;HTTP 200 仍可能包含业务错误。
curl --silent --show-error --fail-with-body \
--request POST \
--url "https://accounts.feishu.cn/oauth/v3/token" \
--header "Content-Type: application/json" \
--data-binary @request-body.json
import json
import os
import re
import urllib.request
import urllib.error
def expand(value):
return re.sub(r"\$\{([A-Z][A-Z0-9_]*)\}",
lambda match: os.environ[match.group(1)], value)
url = expand("https://accounts.feishu.cn/oauth/v3/token")
headers = {
"Content-Type": expand("application/json"),
}
body = {
"grant_type": "client_credentials",
"client_id": "cli_replace_with_app_id",
"client_secret": "replace_with_app_secret",
}
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
request = urllib.request.Request(url, data=data, headers=headers, method="POST")
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.load(response)
except urllib.error.HTTPError as error:
print(f"HTTP {error.code}: {error.read().decode('utf-8', errors='replace')}")
raise SystemExit(1)
print(json.dumps(result, ensure_ascii=False, indent=2))
if isinstance(result, dict) and "code" in result and result["code"] != 0:
raise SystemExit("平台返回业务错误,请结合下方排错说明检查响应")
// Node.js 22+,保存为 request.mjs 后运行 node request.mjs
function expand(value) {
return value.replace(/\$\{([A-Z][A-Z0-9_]*)\}/g, (_, name) => {
if (!process.env[name]) throw new Error(`缺少环境变量 ${name}`);
return process.env[name];
});
}
const url = expand("https://accounts.feishu.cn/oauth/v3/token");
const headers = Object.fromEntries(
Object.entries({
"Content-Type": "application/json"
}).map(([key, value]) => [key, expand(value)])
);
const body = {
"grant_type": "client_credentials",
"client_id": "cli_replace_with_app_id",
"client_secret": "replace_with_app_secret"
};
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
signal: AbortSignal.timeout(30000),
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
const result = JSON.parse(text);
console.log(JSON.stringify(result, null, 2));
if (Object.hasOwn(result, "code") && result["code"] !== 0) {
throw new Error('平台返回业务错误,请结合下方排错说明检查响应');
}
返回示例
按官方字段整理的节选示意,使用虚构 ID,不是实测结果。保存 access_token,并以 expires_in 安排续期。 以下为结构示意,非本站实测结果。
{
"code": 0,
"access_token": "replace_with_returned_jwt",
"expires_in": 7200,
"token_type": "Bearer",
"scope": "im:message:send_as_bot"
}常见错误与排查
20002应用密钥不正确- 确认 App ID、App Secret 属于同一个已启用自建应用。
20068申请了不允许的 scope- 先完成权限开通和发布;只传支持应用身份的 scope。
调用注意
- 有效期以 expires_in 为准,最长 2 小时;缓存复用,不要每个业务请求都取令牌。
- 新版返回 access_token,不是历史版本的 tenant_access_token;JWT 存储预留至少 4 KB。
发送应用文本消息
content 是 JSON 字符串,与 Webhook 的 content 对象不同。
https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id请求头
AuthorizationBearer ${FEISHU_ACCESS_TOKEN}Content-Typeapplication/json; charset=utf-8
参数说明
参数表可左右滑动,查看完整说明。
| 字段 / 位置 | 类型 / 必填 | 说明 |
|---|---|---|
receive_id_typeQuery | string必填 | 接收者类型;本例 open_id。还支持 chat_id、user_id、union_id、email;使用 user_id 另需相应字段权限。 |
receive_id请求体 | string必填 | 与 receive_id_type 对应的 ID,替换为当前应用下的真实 open_id。 |
msg_type请求体 | string必填 | 本例 text;图片/文件需先上传获取消息资源 key。 |
content请求体 | string必填 | 将消息内容对象序列化成字符串,本例为包含 text 的 JSON 字符串。 |
uuid请求体 | string可选 | 最长 50 字符;同一值在 1 小时内至多发送成功一次。相同业务重试可复用,新消息必须换值。 |
调用代码
替换示例 ID 与请求体内容后运行。cURL 会报 HTTP 错误,业务状态还需按下方返回示例核对。
cat > request-body.json <<'JSON'
{
"receive_id": "ou_replace_with_user_open_id",
"msg_type": "text",
"content": "{\"text\":\"接入测试:应用消息已送出\"}"
}
JSON
# 执行后核对返回 JSON 的业务状态;HTTP 200 仍可能包含业务错误。
curl --silent --show-error --fail-with-body \
--request POST \
--url "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id" \
--header "Authorization: Bearer ${FEISHU_ACCESS_TOKEN}" \
--header "Content-Type: application/json; charset=utf-8" \
--data-binary @request-body.json
import json
import os
import re
import urllib.request
import urllib.error
def expand(value):
return re.sub(r"\$\{([A-Z][A-Z0-9_]*)\}",
lambda match: os.environ[match.group(1)], value)
url = expand("https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id")
headers = {
"Authorization": expand("Bearer ${FEISHU_ACCESS_TOKEN}"),
"Content-Type": expand("application/json; charset=utf-8"),
}
body = {
"receive_id": "ou_replace_with_user_open_id",
"msg_type": "text",
"content": "{\"text\":\"接入测试:应用消息已送出\"}",
}
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
request = urllib.request.Request(url, data=data, headers=headers, method="POST")
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.load(response)
except urllib.error.HTTPError as error:
print(f"HTTP {error.code}: {error.read().decode('utf-8', errors='replace')}")
raise SystemExit(1)
print(json.dumps(result, ensure_ascii=False, indent=2))
if isinstance(result, dict) and "code" in result and result["code"] != 0:
raise SystemExit("平台返回业务错误,请结合下方排错说明检查响应")
// Node.js 22+,保存为 request.mjs 后运行 node request.mjs
function expand(value) {
return value.replace(/\$\{([A-Z][A-Z0-9_]*)\}/g, (_, name) => {
if (!process.env[name]) throw new Error(`缺少环境变量 ${name}`);
return process.env[name];
});
}
const url = expand("https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id");
const headers = Object.fromEntries(
Object.entries({
"Authorization": "Bearer ${FEISHU_ACCESS_TOKEN}",
"Content-Type": "application/json; charset=utf-8"
}).map(([key, value]) => [key, expand(value)])
);
const body = {
"receive_id": "ou_replace_with_user_open_id",
"msg_type": "text",
"content": "{\"text\":\"接入测试:应用消息已送出\"}"
};
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
signal: AbortSignal.timeout(30000),
});
const text = await response.text();
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
const result = JSON.parse(text);
console.log(JSON.stringify(result, null, 2));
if (Object.hasOwn(result, "code") && result["code"] !== 0) {
throw new Error('平台返回业务错误,请结合下方排错说明检查响应');
}
返回示例
按官方字段整理的节选示意,使用虚构 ID,不是实测结果。保存 data.message_id,后续回复、更新或撤回会用到。 以下为结构示意,非本站实测结果。
{
"code": 0,
"msg": "success",
"data": {
"message_id": "om_example",
"chat_id": "oc_example",
"msg_type": "text",
"body": {
"content": "{\"text\":\"接入测试:应用消息已送出\"}"
}
}
}常见错误与排查
230002机器人不在群内- 把当前应用机器人加入目标群。
230006未启用机器人能力- 在开发者后台启用机器人并发布。
230013目标用户不在机器人可用范围内- 调整应用版本可用范围,确认用户在职及应用已启用。
230020触发发送频率限制- 按业务队列减速并退避重试。
调用注意
- 接口限额为 1000 次/分钟、50 次/秒,且可能叠加业务频控。
- 成功需同时满足 HTTP 状态和 code=0;记录 log_id 方便定位,勿记录凭证。
注意事项
- 第一步只适用于企业自建应用;商店应用的跨租户令牌流程不同。
核验来源
本站按官方资料整理本场景的必要调用步骤与代码,覆盖范围以本页列出的接口为准。官方资料用于追溯规范及查看变更。
- 01自建应用获取 tenant_access_token(当前 OAuth 版本)核验日期 2026-10-15
- 02发送消息核验日期 2026-10-15