Sending an ordinary file (not image/video) with a Feishu bot requires two steps: first upload the file to obtain a file_key, then use the file_key to send the message.
If a local image is sent through the regular message / media path and the user sees /root/...png path text in Feishu instead of the image itself, do not keep retrying the same method; directly switch to this skill'sstable image upload script。
Quick Process
Method 1: Use the script (recommended)
python3 scripts/send_file.py <file_path> <open_id> <app_id> <app_secret> [file_name]
Parameter description:
file_path: the file path to send (HTML/PDF/ZIP/code files, etc.)open_id: the recipient's open_id (obtained from the chat_id field of inbound_meta, format isuser:ou_xxx, take theou_xxxpart)app_id: Feishu App ID (fromopenclaw.jsonofchannels.feishu.appIdread)app_secret: Feishu App Secret (fromopenclaw.jsonofchannels.feishu.appSecretread)file_name: optional, custom file name (if not filled, the original file name is used)
Quickly obtain configuration:
# 获取 app_id 和 app_secret
grep -A 2 '"feishu"' /root/.openclaw/openclaw.json | grep -E '(appId|appSecret)'
Complete example:
python3 /root/.openclaw/workspace/skills/feishu-send-file/scripts/send_file.py \
/root/myfiles/report.html \
<USER_OPEN_ID> \
<YOUR_APP_ID> \
<YOUR_APP_SECRET> \
report.html
AI assistant usage example:
# 当需要发送文件给用户时,直接调用脚本
exec(f"""
python3 /root/.openclaw/workspace/skills/feishu-send-file/scripts/send_file.py \\
{file_path} \\
{user_open_id} \\
{app_id} \\
{app_secret} \\
{custom_filename}
""")
Method 2: Manual two steps
Step 1 - Upload file:
TOKEN=$(curl -s -X POST "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" \
-H "Content-Type: application/json" \
-d '{"app_id":"<APP_ID>","app_secret":"<APP_SECRET>"}' | python3 -c "import json,sys; print(json.load(sys.stdin)['tenant_access_token'])")
FILE_KEY=$(curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/files" \
-H "Authorization: Bearer $TOKEN" \
-F "file_type=stream" \
-F "file_name=<文件名>" \
-F "file=@<文件路径>" | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['file_key'])")
Step 2 - Send message:
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"receive_id\":\"<OPEN_ID>\",\"msg_type\":\"file\",\"content\":\"{\\\"file_key\\\":\\\"$FILE_KEY\\\"}\"}"
Stable image sending (added 2026-03-15)
When to stop forcing the use of message + media
If you pass a local image path (especially /root/myfiles/...) to the Feishu message pipeline and the user sees in Feishu:
📎 /root/myfiles/xxx.png- or plain local path text
then it means this timeit was not actually sent as an image. Do not keep retrying the same parameter combination.
Reason (real-world version)
- A real Feishu image message is not "stuffing a local path into the message sender"
- The correct way is:first upload to
im/v1/images, obtainimage_key, then sendmsg_type=image - The OpenClaw / Feishu plugin may, in some local path scenarios (especially
/root/myfiles/...), be unable to complete the local media upload pipeline, and then automatically degrade to sending the path text out - Therefore:
messageIdreturning success does not equal the user actually seeing the image
Success criteria
There is only one success criterion: the user actually sees the image itself in Feishu.
If what is echoed back is path text, it is considered a failure.
Recommended script (stable version)
python3 scripts/send_image.py <image_path> <open_id> <app_id> <app_secret> [domain]
Example:
python3 /root/.openclaw/workspace/skills/feishu-send-file/scripts/send_image.py \
/root/myfiles/generated-images/demo.png \
<USER_OPEN_ID> \
<YOUR_APP_ID> \
<YOUR_APP_SECRET>
If it is the international version Lark:
python3 scripts/send_image.py <image_path> <open_id> <app_id> <app_secret> lark
The difference from ordinary files
- Ordinary file:
im/v1/files->file_key->msg_type=file - Image:
im/v1/images->image_key->msg_type=image
Do not mix these two pipelines.
Notes
- Ordinary file: must go through this skill's two-step process; directly using the
filePathparameter will only display the path - Image: in theory the
messagetool'smediaThe parameter can work; but if the user sees path text instead of the image itself, immediately switch toscripts/send_image.py - Do not mistake the
/root/myfiles/...local path echo as a successful send receive_id_type=open_idcorresponds to an individual user; for group chat usechat_idand replace the type- For Feishu file_type use
streamapplies to all ordinary file types