Install
openclaw skills install feishu-doc-writeFeishu (Lark) Document API writing spec. Converts Markdown content to Feishu Block structures and writes to cloud docs. Handles concurrency ordering. Use when syncing articles, creating document blocks, or writing long-form content to Feishu docs.
openclaw skills install feishu-doc-writeReference 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)
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 | 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 |
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 (usually document_id itself for root)index: Insert position (0 = beginning, -1 or omit = end){
"block_type": 2,
"text": {
"elements": [{
"text_run": {
"content": "Paragraph text here",
"text_element_style": { "bold": false, "italic": false }
}
}]
}
}
{ "block_type": 3, "heading1": { "elements": [{ "text_run": { "content": "H1 Title" } }] } }
{ "block_type": 4, "heading2": { "elements": [{ "text_run": { "content": "H2 Title" } }] } }
{ "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.
{
"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 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.
{ "block_type": 22, "divider": {} }
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>
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 | 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) |
Problem: Concurrent Block creation API calls produce random ordering.
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
}
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)
LLM outputs complete Markdown -> Conversion layer -> Single API batch write
Never let the LLM write one paragraph at a time with concurrent API calls.
POST /open-apis/docx/v1/documents with { "folder_token": "<token>", "title": "Title" } -> returns document_idPOST .../documents/{doc_id}/blocks/{doc_id}/children?document_revision_id=-1 with all blocksblock_id from step 3 response, then add childrenSince 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
:::
document_revision_id=-1 (latest version)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>" }'
FEISHU_API_HANDBOOK.md in workspace root