Tencent Lexiang Knowledge Base is an enterprise-level knowledge management platform that provides knowledge bases, team collaboration, document management, AI assistant, and other features.
Data Model
- Team: Top-level organizational unit; a team can contain multiple knowledge bases (Spaces)
- Space (Knowledge Base): A container for knowledge, belonging to a team, containing multiple entries (Entry), with
root_entry_idas the root node - Entry (Item/Knowledge): A content unit in a knowledge base, which can be a page, folder, or file, supporting a tree structure (parent_id)
- File: An entry of the attachment type, such as PDF, Word, images, etc.
Hierarchical relationship:Team -> Space -> Entry(树形结构,root_entry_id 为根)
URL Rules
When generating a knowledge base link,you must use the lexiangla.com domain + company_from parameter, otherwise the link cannot be opened.
Prohibited formats:
https://csig.lexiangla.com/pages/{id}(missing company_from, cannot be opened)https://lexiang.tencent.com/wiki/{id}(internal address, inaccessible externally)
Correct link format:
| Resource Type | URL Format |
|---|---|
| Team Homepage | https://lexiangla.com/t/{team_id}/spaces?company_from={company_from} |
| Knowledge Base | https://lexiangla.com/spaces/{space_id}?company_from={company_from} |
| Knowledge Item | https://lexiangla.com/pages/{entry_id}?company_from={company_from} |
company_fromThe parameter is the enterprise identifier, and its value differs for different enterprises. It can be extracted from Lexiang links previously shared by the user, or the user can be asked on first use.
Preferentially use the links field in the API response; if the API does not return a complete link, concatenate it according to the above rules (do not forget the company_from parameter)。
Credential Configuration
Required Credentials
| Credential | Description | Required |
|---|---|---|
LEXIANG_APP_KEY |
Lexiang Open Platform App Key | Yes (to obtain Token) |
LEXIANG_APP_SECRET |
Lexiang Open Platform App Secret | Yes (to obtain Token) |
LEXIANG_STAFF_ID |
Employee Identity Identifier | Yes (required for write operations) |
Credential Loading Priority
- Cached valid Token(
~/.config/lexiang/token, reused directly within the 2-hour validity period, no app_key needed) - Environment Variables(
LEXIANG_APP_KEY/LEXIANG_APP_SECRET) - openclaw.json(
~/.openclaw/openclaw.jsonofskills.entries.lexiang.envfield) - credentials file(
~/.config/lexiang/credentials, JSON format) - Interactive guidance(automatically guides the user for input when stdin is available, and persists it to the credentials file after verification)
Initialization (load credentials + obtain Token)
Execute the scripts/init.sh script to automatically handle credential loading and Token acquisition:
source scripts/init.sh
# 之后可使用 $LEXIANG_TOKEN 和 $LEXIANG_STAFF_ID
On first use, if there are no configured credentials, the script will automatically guide the user to enter the App Key, App Secret, and Staff ID in the terminal, and after verification passes, automatically save them to ~/.config/lexiang/credentials, so there is no need to enter them again later.
Non-interactive environments (such as CI/CD), the script will output clear configuration prompts and format examples to help users manually create configuration files.
Tokens are valid for 2 hours, with a rate limit of 20 requests per 10 minutes. The script will automatically cache to ~/.config/lexiang/token。
Manually configure the credentials file
mkdir -p ~/.config/lexiang
cat > ~/.config/lexiang/credentials << 'EOF'
{
"app_key": "your_app_key",
"app_secret": "your_app_secret",
"staff_id": "your_staff_id"
}
EOF
chmod 600 ~/.config/lexiang/credentials
API Call Basics
Request headers
# 读操作
-H "Authorization: Bearer $LEXIANG_TOKEN"
-H "Content-Type: application/json; charset=utf-8"
# 写操作(额外需要)
-H "x-staff-id: $LEXIANG_STAFF_ID"
Interfaces requiring x-staff-id
All write operations (create/update/delete), AI search/Q&A, permission settings
Format for creating a knowledge node
Use the JSON:API specification format, and specify via relationships the knowledge base and parent node it belongs to:
{
"data": {
"type": "kb_entry",
"attributes": {"entry_type": "page", "name": "标题"},
"relationships": {
"space": {"data": {"type": "kb_space", "id": "SPACE_ID"}},
"parent_entry": {"data": {"type": "kb_entry", "id": "PARENT_ID"}}
}
}
}
General limits
- Rate limit: most interfaces 3000 requests/minute
- Permission requirements: must be within the AppKey's authorization scope
Core workflow
1. Parse the Lexiang URL
When a user provides a Lexiang link, you can use scripts/parse_url.sh to automatically parse and obtain the detailed information needed by the API:
source scripts/init.sh
source scripts/parse_url.sh "https://lexiangla.com/spaces/{space_id}?company_from=xxx"
# 之后可使用以下环境变量:
# $LEXIANG_PARSED_TYPE - 资源类型: space / entry / team
# $LEXIANG_PARSED_ID - 资源 ID
# $LEXIANG_PARSED_SPACE_ID - 知识库 ID
# $LEXIANG_PARSED_TEAM_ID - 团队 ID
# $LEXIANG_PARSED_NAME - 资源名称
# $LEXIANG_PARSED_ROOT_ENTRY_ID - 根节点 ID
Supported URL formats:
| URL format | Parse result |
|---|---|
https://lexiangla.com/spaces/{space_id}?... |
Knowledge base information (including root_entry_id, team_id) |
https://lexiangla.com/pages/{entry_id}?... |
Document information (automatically look up the knowledge base and team it belongs to) |
https://lexiangla.com/t/{team_id}/spaces |
Team information |
For entry-type URLs, the script will look upward through the parent chain to find the root entry, and then look up the knowledge base in reverse via team → spaces. Lookup results are automatically cached to ~/.config/lexiang/space_cache.json。
2. Query knowledge
# 获取团队列表
curl "https://lxapi.lexiangla.com/cgi-bin/v1/kb/teams?limit=20" \
-H "Authorization: Bearer $LEXIANG_TOKEN"
# 获取知识库列表
curl "https://lxapi.lexiangla.com/cgi-bin/v1/kb/spaces?team_id={team_id}&limit=20" \
-H "Authorization: Bearer $LEXIANG_TOKEN"
# 获取知识列表
curl "https://lxapi.lexiangla.com/cgi-bin/v1/kb/entries?space_id={space_id}&limit=20" \
-H "Authorization: Bearer $LEXIANG_TOKEN"
# 获取文档内容(HTML 格式)
curl "https://lxapi.lexiangla.com/cgi-bin/v1/kb/entries/{entry_id}/content?content_type=html" \
-H "Authorization: Bearer $LEXIANG_TOKEN"
3. Create a document
Comparison of the three methods:
| Method | Pros | Cons | Recommended scenario |
|---|---|---|---|
| Upload a Markdown file | Simple and efficient | Creates a file type, cannot be edited online | Archiving, bulk import |
| Markdown → Online document | Preserves formatting + can be edited online | Requires a conversion step | Preferred when the user requests an editable online document |
| Manually build with the block interface | Precise control over each block | Complex, error-prone | Programmatically modify existing document content |
Method A: Upload a Markdown file(creates a file type, cannot be edited online)
source scripts/init.sh
bash scripts/upload_file.sh ./document.md SPACE_ID [PARENT_ENTRY_ID]
Method B: Markdown → Online document(creates a page type, can be edited online) ⭐ Recommended
Use the scripts/md_to_page.py script to automatically parse Markdown into blocks and write them into an online document:
source scripts/init.sh
# 创建新 page 并写入
python3 scripts/md_to_page.py ./document.md --space-id SPACE_ID --parent-id PARENT_ID --name "文档标题"
# 写入已有 page
python3 scripts/md_to_page.py ./document.md --entry-id ENTRY_ID
# 追加模式(不清空已有内容)
python3 scripts/md_to_page.py ./document.md --entry-id ENTRY_ID --append
Script features:
- Automatically parses headings, paragraphs, lists, code blocks, quote blocks, horizontal rules, etc.
- Quote blocks (>) are automatically converted to callout highlight blocks(the API does not support quote, so callout is used to simulate it)
- Inline styles: bold, italic, inline code, links
- Automatically submit in batches (each batch ≤20 top-level blocks) to avoid oversized requests
- Automatically retry block by block on failure
- When updating an existing page, it is cleared before writing by default(non-
--appendmode), to avoid duplicate content
Note: The image block cannot be created via API; image references in Markdown will be ignored. If you need images, please insert them manually in the online document.
4. AI Search and Q&A
# AI 搜索
curl -X POST "https://lxapi.lexiangla.com/cgi-bin/v1/ai/search" \
-H "Authorization: Bearer $LEXIANG_TOKEN" \
-H "x-staff-id: $LEXIANG_STAFF_ID" \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"query": "搜索关键词"}'
# AI 问答(research=true 使用专业研究模式)
curl -X POST "https://lxapi.lexiangla.com/cgi-bin/v1/ai/qa" \
-H "Authorization: Bearer $LEXIANG_TOKEN" \
-H "x-staff-id: $LEXIANG_STAFF_ID" \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"query": "问题内容", "research": false}'
Key considerations for using the block API
For scenarios that require using the online document block API, note the following points (for detailed examples, see references/api-blocks.md):
- Creating a new document does not pass parent_block_id: directly insert content into the page root node
- List block field names differ from type names:
bulleted_listuse thebulletedfield,numbered_listuse thenumberedfield - Heading block fields must match:
h1useheading1,h2useheading2, nottext - Nested block's
childrenandblock_idare declared only on the block itself; do not put them at the top level of the payload: nested blocks (callout/table/toggle, etc.) establish parent-child relationships through the block's ownblock_id+childrenfield.Do notpasschildrenat the top level of the payload—childrenat the top level of the payload will cause the API to promote these blocks to the first batch of child nodes at the page root, disrupting the order in thedescendantarray, causing nested blocks to appear at the beginning of the page (rather than rendering at their actual positions in the document) - quote is not in the list of types supported by the API: the API's valid
block_typedoes not includequote. Markdown blockquotes (>) should be converted tocallout(highlight block) to simulate.scripts/md_to_page.pyhas automatically handled this conversion - Nested types not supported:
h1-h5、code、image、attachment、video、divider、mermaid、plantuml - The image block cannot be created via API: the blocks API does not support
imageblock_type, so images cannot currently be inserted into documents via the API. When uploading an MD file containing image references, local image paths will remain as-is (to be updated after the API supports it) - Use for folder type
folder: when creating a folder,entry_typemust usefolder(notdirectory) - The name of a file type must include an extension: e.g.
文档标题.md、image.png - When there are many blocks, they must be submitted in batches: it is recommended to have no more than 20 top-level blocks per batch.
scripts/md_to_page.pyhas built-in batching logic descendantThe API uses append semantics, not overwrite:POST blocks/descendantwill append new blocks at the end of the page,will notclear existing content. When updating an existing page, you must first retrieve all block IDs and delete them one by one (DELETE blocks/{block_id}), otherwise each write will produce duplicate content.scripts/md_to_page.pyIn non---appendmode, built-in clearing logic is already included
Troubleshooting common errors
| Error message | Cause | Solution |
|---|---|---|
必须指定员工账号 |
Missing x-staff-id | Add -H "x-staff-id: $LEXIANG_STAFF_ID" |
data.attributes.entry_type 不能为空 |
Malformed request | Use the JSON:API specification format |
entry_type Invalid value |
Used directory |
Change to folder |
content_type 不能为空 |
Missing parameter | Add ?content_type=html |
| List content is empty | Incorrect field name | For unordered lists use bulleted, for ordered lists use numbered |
| Nested block creation failed | Missing association | Ensure children + block_id are paired |
| file name is missing an extension | name field has no extension | Add .md、.png and other extensions |
| Upload API 404 | Legacy path | Use /v1/kb/files/upload-params |
| Property setting silently fails | value passed the option key instead of the text value | In the value array, pass the option'sdisplay text(e.g. "互联网参考"), not the key (e.g. c0jp3b6qyh). Passing the key returns 200 but the value is empty |
| Property setting 400 error | Incorrect request body format | Must use JSON:API format:{"data":{"type":"kb_entry","attributes":{"属性ID":{"value":["选项文本"]}}}} |
| PATCH rename 404 | File type entries do not support PATCH rename | After a file type entry is created, its name cannot be modified through the API; the correct file name must be used at upload time (upload_file.sh will use the file's local file name) |
| Duplicate page content | POST blocks/descendant has append semantics |
Before updating a page, you must first clear the existing blocks (GET children → DELETE one by one), or use md_to_page.py 's default mode (automatically clears then writes). Only use --append |
| Nested blocks (callout/table) end up at the beginning of the page | Passed children parameter at the top level of the payload |
Do notpass childrenat the top level of the payload. The parent-child relationship of nested blocks is established only through the block's own block_id + children at the top level of the payload children will cause the API to promote the declared blocks to the first batch of child nodes at the page root, disrupting the order |
Experience case
Cross-space knowledge base migration
Scenario: Migrate all content (folder structure + files + online documents) of one knowledge base (Space) to another knowledge base.
Core challenge: LXP API does not havea native move/copy API; you must manually traverse → rebuild the directory → download/upload.
Migration process:
- Traverse the source knowledge base directory structure:
GET /kb/entries?space_id=SOURCE&limit=50, obtaining folders and sub-entries layer by layer - Create folders with the same names in the target knowledge base:
POST /kb/entries,entry_type: "folder" - Handle entries separately by type:
filetype: obtain the download link fromdata.links.download→ download locally → re-upload to the target folder through the three-step upload process (obtain credentials → upload to COS → create entry)pagetype: obtain HTML through the content API → parse into blocks → write to the target page
Pitfall log:
| Issue | Cause | Solution |
|---|---|---|
| page content is empty | No content blocks were written after creating the page entry.POST /kb/entries only creates an empty page shell; content must be written separately through the blocks API |
Use GET /kb/entries/{id}/content?content_type=html to obtain the source page HTML, parse it, then use POST /kb/page/entries/{id}/blocks/descendant to write to the target page |
| Upload credential parsing error | Bucket/Region in the response's options level, rather than the object level;key/state/auth at the object level |
Upload credential response structure:options.Bucket、options.Region、object.key、object.state、object.auth.Authorization、object.auth.XCosSecurityToken |
| Wrong file download link retrieved | The file download link is in data.links.download, rather than included in |
Prefer to get it from data.links.download ,included the kb_file link in as a fallback |
| Filename missing extension | Entry name field does not necessarily contain an extension |
Parse the extension from the download URL's path (os.path.splitext(urlparse(url).path)[1]), and append it to the end of the filename |
Key points for HTML → Blocks conversion:
Lexiang online documents' HTML uses lx-* class names to identify block types (such as <p class="lx-p">、<h2 class="lx-h2">). When converting:
- Paragraph (
p)→{"block_type": "p", "text": {"elements": [...]}} - Heading (
h1-h5)→{"block_type": "h2", "heading2": {"elements": [...]}}(note that the field name isheading{N}nottext) - Inline styles are mapped to
<b>/<i>/<u>/<span style="...">viatext_style - Empty paragraphs can be skipped and not written
- When there are many blocks, write them in batches (recommended ≤ 20 blocks per batch)
Custom attribute settings
Scenario: Set custom attributes for knowledge entries (such as "content nature" and "content source") to make it easier for the Agent to filter and retrieve by type.
Correct API call method:
# 获取条目当前属性值
curl "https://lxapi.lexiangla.com/cgi-bin/v1/kb/entries/{entry_id}/properties/values" \
-H "Authorization: Bearer $LEXIANG_TOKEN"
# 设置属性值(必须使用 JSON:API 格式)
curl -X PUT "https://lxapi.lexiangla.com/cgi-bin/v1/kb/entries/{entry_id}/properties/values" \
-H "Authorization: Bearer $LEXIANG_TOKEN" \
-H "x-staff-id: $LEXIANG_STAFF_ID" \
-H "Content-Type: application/json; charset=utf-8" \
-d '{"data":{"type":"kb_entry","attributes":{"属性ID":{"value":["选项显示文本"]}}}}'
Pitfall log:
| Problem | Cause | Solution |
|---|---|---|
| PUT returns 200 but the attribute value is empty | value passed the option's key (such as c0jp3b6qyh) |
Change to passing the option'sdisplay text(such as "互联网参考") |
PUT returns 400 data/data.attributes 不能为空 |
The request body used the {"properties": [...]} format |
Must use JSON:API format:{"data":{"type":"kb_entry","attributes":{...}}} |
| Difference between attribute ID and option key | The attribute ID is the UUID of the attribute itself, and the option key is the identifier of the option | GET /kb/properties/{id} Get attribute details and option list |
Online document updates cause duplicate content (severe)
Scenario: When using md_to_page.py --entry-id to update an existing online document, the page content is duplicated 2~4 times.
Root cause analysis:
The Blocks API's POST /kb/page/entries/{id}/blocks/descendant isappendsemantics—it does not overwrite existing content, but appends new blocks at the end of the page. The script initially had no clearing logic, so each run appended the full content to the existing page. Combined with retries after partial failures in the first write, the page eventually accumulated 580 blocks (normally it should be ~147).
Timeline of when the problem surfaced:
- First write: some blocks failed because the
quotetype was not supported → retried after fixing - Second write: all succeeded, but at this point the content was already duplicated
- Several more writes were triggered during subsequent debugging → the content expanded to 4 times
Fix measures:
md_to_page.pyAddedclear_page_blocks(): in non---appendmode, delete all existing root blocks on the page before writing- Use parallel deletion(
ThreadPoolExecutor(max_workers=10)) + multiple rounds of looping, ensuring that even a large number of blocks can be cleared within a reasonable time - The API return format differs from expectations(see the table below for pitfalls)
Pitfall Log:
| Problem | Cause | Solution |
|---|---|---|
| Updating an existing page causes duplicate content | Blocks descendant The API has append semantics and will not overwrite existing content |
Before updating, first call GET blocks/children to get all block IDs, then DELETE each one to clear it, and then write the new content.--append The parameter skips clearing |
When clearing, str has no attribute get |
blocks/children the API returns {"data": {"blocks": [...]}} instead of {"data": [...]} |
Use resp["data"]["blocks"] to get the block list, not resp["data"] |
| The block ID field name is wrong | The unique identifier field of a block is block_id, not id |
When deleting, use block["block_id"] |
| Deleting one by one is too slow (580 blocks timed out) | Serial HTTP requests, about 200ms each | Switch to ThreadPoolExecutor(max_workers=10) parallel deletion, looping for multiple rounds until cleared |
| Nested blocks (callout) end up at the very beginning of the page | The payload passed children: ["callout-xxx"]at the top level, and the API promoted it to the first batch of child nodes at the page root |
Do notpass childrenat the top level of the payload. The parent-child relationship of nested blocks only needs to be established through the block's own block_id + children , and the API will render in the order of the descendant array |
Core Lessons:
Operations on remote APIs must be idempotent. Before writing content, if it is not append mode, you must first confirm whether the target is empty—you cannot assume the API will automatically overwrite. This kind of problem does not occur in local file operations (
writedefaults to overwrite), but it is extremely common in API scenarios.When integrating a new API for the first time, you must verify: Is the interface's semantics "overwrite" or "append"? Will retries after failure produce side effects?
The semantics of API parameters cannot be taken for granted。
descendantIn the request body, thechildrenparameter appears to be used to declare nested block relationships, but its actual meaning is "specify the first-level child blocks of the page root node"—it changes the rendering position of blocks. The parent-child relationship of nested blocks (callout/table) should only be declared on the block itself (block_id+children), and does not need and should not be repeatedly declared at the top level of the payload.When API behavior does not meet expectations, first conduct small-scale controlled experiments (passing/not passing a certain parameter), then apply it to the full data。
Detailed API Reference
Consult the following reference files as needed to obtain complete interface documentation:
| File | Content | Search keywords |
|---|---|---|
references/api-contact.md |
Contacts management (member/department CRUD) | contact, user, department, staff |
references/api-team-space.md |
Team and knowledge base management | team, space, permissions, subject |
references/api-entries.md |
Knowledge node CRUD and permissions | entry, entries, page, directory, file |
references/api-blocks.md |
Online document block interface (create/edit block content) | block, descendant, paragraph, table, list |
references/api-other.md |
Tasks/attributes/logs/AI/assets/export/SSO | task, property, log, ai, search, qa, upload, sso |
HTTP error codes
| Status code | Description |
|---|---|
| 200/201 | Success |
| 204 | Deleted successfully |
| 400 | Invalid request parameters |
| 401 | Token is invalid or expired |
| 403 | No permission |
| 404 | Resource does not exist |
| 429 | Rate limit exceeded |