feishu-doc-writer
npx skills add https://github.com/alextangson/feishu_skills --skill feishu-doc-writer
Agent 安装分布
Skill 文档
Feishu Document Writer
Reference spec for writing content to Feishu (Lark) cloud documents via the Docx API. Feishu docs use a Block tree model â raw Markdown is not accepted.
Document (block_type=1, Page)
+-- Heading1 Block (block_type=3)
+-- Text Block (block_type=2)
+-- Callout Block (block_type=19)
| +-- Text Block
| +-- Bullet Block
+-- Image Block (block_type=27)
+-- Divider Block (block_type=22)
Preferred Approach: Convert API
Feishu provides an official Markdown -> Blocks conversion endpoint:
POST /open-apis/docx/v1/documents/{document_id}/convert
{
"content": "# Title\n\nBody text\n\n- Item 1\n- Item 2\n\n> Quote",
"content_type": "markdown"
}
Pros: No manual Block JSON construction. Handles most standard Markdown. Limitation: Does not support Feishu-specific blocks (Callout, etc.) â use manual Block creation for those.
Block Type Reference
| block_type | Name | JSON Key | Notes |
|---|---|---|---|
| 1 | Page | page |
Document root |
| 2 | Text | text |
Paragraph |
| 3-11 | Heading1-9 | heading1–heading9 |
Headings |
| 12 | Bullet | bullet |
Unordered list (each item = separate block) |
| 13 | Ordered | ordered |
Ordered list |
| 14 | Code | code |
Code block (with style.language enum) |
| 15 | Quote | quote |
Blockquote |
| 17 | Todo | todo |
Checkbox item (with style.done) |
| 19 | Callout | callout |
Highlight box (Feishu-specific, container block) |
| 22 | Divider | divider |
Horizontal rule |
| 27 | Image | image |
Two-step: create placeholder, then upload |
| 31 | Table | table |
Table |
| 34 | QuoteContainer | quote_container |
Quote container |
Create Blocks API
POST /open-apis/docx/v1/documents/{document_id}/blocks/{block_id}/children?document_revision_id=-1
Headers:
Content-Type: application/json
Authorization: Bearer <tenant_access_token>
Body:
{
"children": [ ...Block array... ],
"index": 0
}
block_id: Parent block ID (usuallydocument_iditself for root)index: Insert position (0 = beginning, -1 or omit = end)
Block JSON Examples
Text
{
"block_type": 2,
"text": {
"elements": [{
"text_run": {
"content": "Paragraph text here",
"text_element_style": { "bold": false, "italic": false }
}
}]
}
}
Heading
{ "block_type": 3, "heading1": { "elements": [{ "text_run": { "content": "H1 Title" } }] } }
{ "block_type": 4, "heading2": { "elements": [{ "text_run": { "content": "H2 Title" } }] } }
Bullet / Ordered List
{ "block_type": 12, "bullet": { "elements": [{ "text_run": { "content": "List item" } }] } }
{ "block_type": 13, "ordered": { "elements": [{ "text_run": { "content": "Numbered item" } }] } }
Each list item is a separate Block.
Code Block
{
"block_type": 14,
"code": {
"elements": [{ "text_run": { "content": "console.log('hello');" } }],
"style": { "language": 23, "wrap": false }
}
}
Common language enums: PlainText=1, JavaScript=23, Python=40, TypeScript=49, Go=20, Shell=46, SQL=47, Java=22, Rust=44, C=12, CSS=17, HTML=21, Docker=19.
Callout (Feishu-specific highlight box)
Callout is a container block â create it first, then add child blocks inside.
// Step 1: Create callout as document child
{ "block_type": 19, "callout": { "background_color": 3, "border_color": 3, "emoji_id": "star" } }
// Step 2: POST .../blocks/{callout_block_id}/children
{ "children": [{ "block_type": 2, "text": { "elements": [{ "text_run": { "content": "Highlight text" } }] } }] }
Color enums: Red=1, Orange=2, Yellow=3, Green=4, Blue=5, Purple=6, Grey=7.
Divider
{ "block_type": 22, "divider": {} }
Image (two-step)
Step 1: Create placeholder block { "block_type": 27, "image": {} }
Step 2: Upload via POST /open-apis/drive/v1/medias/upload_all
- multipart/form-data: file, file_name, parent_type="docx_image", parent_node=<image_block_id>
Text Styling
Apply styles via text_element_style in text_run:
| Property | Type | Effect |
|---|---|---|
bold |
bool | Bold |
italic |
bool | Italic |
strikethrough |
bool | Strikethrough |
underline |
bool | Underline |
inline_code |
bool | Inline code |
text_color |
int | Text color (same enum as callout colors) |
background_color |
int | Background color |
link.url |
string | Hyperlink |
Multiple text_run elements in one block = mixed styles in one paragraph.
Markdown to Block Mapping
| Markdown | block_type | JSON Key |
|---|---|---|
# H1 |
3 | heading1 |
## H2 |
4 | heading2 |
### H3 |
5 | heading3 |
| Paragraph | 2 | text |
- item |
12 | bullet |
1. item |
13 | ordered |
| Code fence | 14 | code |
> quote |
15 | quote |
- [ ] todo |
17 | todo |
--- |
22 | divider |
 |
27 | image (two-step) |
**bold** |
— | text_element_style.bold: true |
*italic* |
— | text_element_style.italic: true |
`code` |
— | text_element_style.inline_code: true |
~~strike~~ |
— | text_element_style.strikethrough: true |
[text](url) |
— | text_element_style.link.url |
| (no MD equivalent) | 19 | callout (Feishu-specific) |
Concurrency & Ordering (Critical)
Problem: Concurrent Block creation API calls produce random ordering.
Solution A: Single Batch Request (Recommended)
Put all blocks in one children array, single API call:
{
"children": [
{ "block_type": 3, "heading1": { "elements": [{"text_run": {"content": "Title"}}] } },
{ "block_type": 2, "text": { "elements": [{"text_run": {"content": "Paragraph 1"}}] } },
{ "block_type": 22, "divider": {} },
{ "block_type": 4, "heading2": { "elements": [{"text_run": {"content": "Section 2"}}] } }
],
"index": 0
}
Solution B: Serial Writes with Index
For long content requiring multiple requests, execute serially with explicit index:
Request 1: index=0, write block A
Request 2: index=1, write block B (wait for A to succeed)
Request 3: index=2, write block C (wait for B to succeed)
Solution C: Collect-Then-Write (Recommended)
LLM outputs complete Markdown -> Conversion layer -> Single API batch write
Never let the LLM write one paragraph at a time with concurrent API calls.
Complete Write Flow
- Create document:
POST /open-apis/docx/v1/documentswith{ "folder_token": "<token>", "title": "Title" }-> returnsdocument_id - Build Block array: Convert full content to Block JSON
- Batch write:
POST .../documents/{doc_id}/blocks/{doc_id}/children?document_revision_id=-1with all blocks - Container blocks (optional): For Callout etc., get
block_idfrom step 3 response, then add children
Custom Callout Syntax
Since Markdown has no Callout equivalent, use this custom markup:
:::callout{color=yellow emoji=bulb}
Highlight content here.
Supports **bold**, *italic*, and lists.
:::
| Param | Values | Default | Purpose |
|---|---|---|---|
color |
red, orange, yellow, green, blue, purple, grey | yellow | Background & border |
emoji |
Any Feishu emoji_id (bulb, star, warning, fire) | bulb | Left icon |
border |
Same as color values | Same as color | Border color (override) |
Common templates:
:::callout{color=yellow emoji=bulb}
**Key Insight**: The most important takeaway
:::
:::callout{color=red emoji=warning}
**Warning**: Common misconception
:::
:::callout{color=green emoji=check}
**Action Item**: What to do next
:::
Rate Limits & Constraints
- Max blocks per batch: ~50 recommended
- Long articles: Split by H2/H3 sections, 200-500ms between batches
- Always use
document_revision_id=-1(latest version) - Token validity: ~2 hours, cache and refresh before expiry
Authentication
curl -X POST 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal' \
-H 'Content-Type: application/json' \
-d '{ "app_id": "<app_id>", "app_secret": "<app_secret>" }'
Schema Pitfalls (Battle-tested)
- No Markdown tables in write ops â use bullet lists instead (prevents schema errors)
- No nested code blocks inside lists â Feishu schema validation is strict on nesting depth
- Callout is a container â always requires a two-step create (container first, then children)
- Each list item = separate Block â don’t try to put multiple items in one block
References
- Create Blocks API: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document-block-children/create
- Block Data Structure: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/data-structure/block
- Convert API: https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document/convert
- Extended API reference: See
FEISHU_API_HANDBOOK.mdin workspace root