feishu-doc-writer

📁 alextangson/feishu_skills 📅 6 days ago
10
总安装量
7
周安装量
#30549
全站排名
安装命令
npx skills add https://github.com/alextangson/feishu_skills --skill feishu-doc-writer

Agent 安装分布

github-copilot 7
codex 7
kimi-cli 7
gemini-cli 7
cursor 7
amp 7

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 heading1heading9 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 (usually document_id itself 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
![](url) 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

  1. Create document: POST /open-apis/docx/v1/documents with { "folder_token": "<token>", "title": "Title" } -> returns document_id
  2. Build Block array: Convert full content to Block JSON
  3. Batch write: POST .../documents/{doc_id}/blocks/{doc_id}/children?document_revision_id=-1 with all blocks
  4. Container blocks (optional): For Callout etc., get block_id from 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