Build, send, and handle Feishu interactive cards.
Base URL: https://open.feishu.cn/open-apis/im/v1
Use cases: morning report confirmation, alert handling, task confirmation, status update
Authentication and Token retrieval
From the feishu_skills root directory, execute the shared script:
TOKEN="$(./scripts/get_feishu_token.sh)"
Use for all request headers Authorization: Bearer ${TOKEN}。
If the business API returns an invalid token, expired token, or 401, force refresh and retry the original request only once:
TOKEN="$(./scripts/get_feishu_token.sh --force-refresh)"
Environment variables:
FEISHU_APP_IDFEISHU_APP_SECRET
Local cache: ./.feishu_token_cache.json(reuse directly if not expired; refresh 5 minutes in advance by default)
Send card
POST /open-apis/im/v1/messages?receive_id_type=chat_id
{
"receive_id": "<chat_id>",
"msg_type": "interactive",
"content": "<card_json_string>"
}
⚠️ content must be stringified JSON.
Card structure
{
"config": {"wide_screen_mode": true},
"header": {
"title": {"tag": "plain_text", "content": "标题"},
"template": "blue"
},
"elements": [
{"tag": "div", "text": {"tag": "lark_md", "content": "**加粗**"}},
{"tag": "action", "actions": [
{"tag": "button", "text": {"tag": "plain_text", "content": "确认"}, "type": "primary", "value": {"action": "confirm"}}
]}
]
}
Common elements
| Element | Description | Example |
|---|---|---|
div |
Text block | {"tag":"div","text":{"tag":"lark_md","content":"文本"}} |
hr |
Divider | {"tag":"hr"} |
action |
Button group | See below |
note |
Note | {"tag":"note","elements":[{"tag":"plain_text","content":"备注"}]} |
Button
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {"tag": "plain_text", "content": "确认"},
"type": "primary",
"value": {"action": "confirm", "data": "extra_info"}
}
]
}
Button type: default / primary / danger
Selector
{
"tag": "select_static",
"placeholder": {"tag": "plain_text", "content": "请选择"},
"options": [
{"text": {"tag": "plain_text", "content": "选项1"}, "value": "opt1"},
{"text": {"tag": "plain_text", "content": "选项2"}, "value": "opt2"}
],
"value": {"key": "select_result"}
}
Callback handling
After the user clicks a button, Feishu sends a WebSocket event:
{
"type": "card.action.trigger",
"action": {
"value": {"action": "confirm", "data": "extra_info"}
}
}
After handling the callback, the card can be updated:
PATCH /open-apis/im/v1/messages/{message_id}
Best practices
- Buttons carry value(used for callback identification)
- Card update instead of new message(reduce chat flooding)
- Use danger type for dangerous operations