Lark Base - Cơ chế liên kết dữ liệu (4 cơ chế)
Lark Base — 4 Cơ chế liên kết dữ liệu (Tài liệu permanent)
Mục đích: Tài liệu tham chiếu permanent về cách Lark Base (Bitable) lưu trữ và kết nối dữ liệu. Dùng cho mọi phiên sau khi cần làm việc với Lark. Đọc 1 lần để hiểu, không cần điều tra lại.
Phạm vi: 4 cơ chế liên kết + Automation (workflows).
🗺️ ROADMAP LARK — 6 bước (anh Huyên chốt S177)
Mọi việc làm trên Lark phải bám theo roadmap này. Mỗi phiên phải biết đang đứng ở bước nào.
| Bước | Mục tiêu | Trạng thái |
|---|---|---|
| 1 | Vẽ rõ sơ đồ để lấy / để input thông tin (hiểu Lark vận hành ra sao) | 🟡 Đang làm — S176/S177 đã có mechanisms.md + 4 cơ chế + 5 cổng |
| 2 | Xây đủ tool dump để lấy thông tin về VPS — dựng lại hiện trường | 🟡 50% — Cổng 2 GĐ1 READ xong S178 (toolkit tools/lark_probe/, 15/15 endpoint verified). Còn: Cổng 4 toolkit build + 7 câu hỏi test |
| 3 | Lấy đủ thông tin → dựng lại mô hình đang chạy (bản thiết kế lại từ cái đã có) | 🔴 Chưa |
| 4 | Xây tool CRUD để thao tác Lark — qua Cổng 4 hoặc cả 2 | 🔴 Chưa |
| 5 | Sửa chữa một số thứ để duy trì Lark tiếp tục chạy | 🔴 Chưa |
| 6 | Chuyển toàn bộ Lark về PG — đích đến cuối, SSOT về field/data/workflow | 🔴 Chưa |
Nguyên tắc: Mỗi bước xong mới sang bước kế. Lark cồng kềnh — đích đến là PG. Dùng Lark tạm thời cho đến khi migrate xong.
Nguồn verify: Kết hợp 3 báo cáo agent (Gemini Deep, GPT, Gemini CLI) + SDK chính thức
larksuite/oapi-sdk-go+ API calls thật trên hệ thống Incomex (Base đệm, Base 88, Base 6.5).Status: Draft v2 từ S176. Đã verify thêm
is_syncedfield meaning. Còn 6 TODO.
0. Tóm tắt 1 trang (đọc đầu tiên)
Lark Base có 4 cách liên kết dữ liệu. Mỗi cách có khả năng đọc qua API khác nhau, qua 2 cổng:
- Cổng public (
/open-apis/bitable/v1/*) — dùng tenant_access_token - Cổng internal (
/space/api/v1/bitable/*,/space/api/bitable/*) — dùng cookie session (verify S177)
| # | Cơ chế | Cổng public (REST) | Cổng internal (cookie) |
|---|---|---|---|
| 1 | Link field (1/2 chiều) | ✅ ĐẦY ĐỦ | ✅ Đầy đủ |
| 2 | Sync table | ⚠️ Detect được qua is_synced, không biết source |
✅ syncTableIds list Sync Tables/Base. Source mapping vẫn chưa verify (chờ S178) |
| 3 | Lookup + Formula cross-table | ✅ ĐẦY ĐỦ (target_table, target_field, formula DSL, filter) | ✅ Bonus: formulaInfo có compiled code |
| 4 | Automation (workflows) | ❌ Chỉ id/title/status | ✅ FULL — draft.steps[] với trigger + action + condition tree (S177) |
Kết luận chung:
- Public REST dùng được cho 3/4 cơ chế (Link, Lookup, Sync detect).
- Internal API (Cổng 4) vượt xa public — bổ sung views (37/bảng), full automation logic, base metadata (trashMap, permissionMap, baseRecordsNum), change stream, record ordering.
Hệ quả cho Incomex (cập nhật S177): Kế hoạch dump Phase 4 khả thi cao hơn tưởng tượng:
- (a) Phase 4A — Mở rộng dump Lookup/Formula property qua REST: 100% qua public REST, rẻ
- (b) Phase 4B — Sync Table registry + Automation full dump qua Cổng 4: 1 call/Base trả tất cả. Cần Chrome + cookie session.
- (c) Phase 5A — Sync source mapping: vẫn là điểm mù. Chờ S178 test Sync Setup UI qua Cổng 4.
- (d) Phase 5B — Automation decoder: ✅ KHÔNG CẦN — Cổng 4 đã trả full logic.
1. Cơ chế 1 — Link field (SingleLink & DuplexLink)
Bản chất
Link field là "khóa ngoại" của Lark, nhưng ở cấp field thay vì cấp bảng. Cho phép 1 bản ghi ở bảng A trỏ tới 1 hoặc nhiều bản ghi ở bảng B — trong cùng 1 Base. Không cross-base.
Có 2 biến thể:
- SingleLink (type =
18, ui_type =SingleLink): 1 chiều. Bảng A trỏ tới B, nhưng B không biết ai trỏ tới mình. - DuplexLink (type =
21, ui_type =DuplexLink): 2 chiều. Lark tự tạo field phản chiếu ở B. Đổi 1 bên → bên kia tự cập nhật.
API endpoint
- Endpoint:
GET /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields - Scope:
bitable:app:readonly
Sample response THẬT (capture từ Base đệm Incomex, S176)
{
"field_id": "fldCehmzxu",
"field_name": "Bảng 2",
"type": 21,
"ui_type": "DuplexLink",
"is_primary": false,
"is_synced": false,
"is_extend": false,
"property": {
"back_field_id": "fldbaGGaOC",
"back_field_name": "Bảng 1",
"multiple": true,
"table_id": "tblaU7kxyPTNBSrR",
"table_name": "Đơn hàng"
}
}
Ý nghĩa các field trong property
| Field | Ý nghĩa | Bắt buộc |
|---|---|---|
table_id |
ID bảng đích | ✅ |
table_name |
Tên hiển thị bảng đích (có thể bị đổi, không tin cậy bằng table_id) |
✅ |
multiple |
true = cho phép chọn nhiều bản ghi (N:N hoặc 1:N); false = 1:1 hoặc N:1 |
✅ |
back_field_id |
Chỉ có ở type=21. ID của field phản chiếu ở bảng đích. |
Chỉ DuplexLink |
back_field_name |
Tên field phản chiếu. | Chỉ DuplexLink |
Cách trace
- Gọi
/tableslấy danh sáchtable_id. - Với mỗi bảng, gọi
/fields. - Lọc field có
type ∈ {18, 21}. - Deduplication cho DuplexLink: mỗi quan hệ 2 chiều xuất hiện ở cả 2 bảng. Dùng hash
sorted(field_id_A, field_id_B)để tránh đếm trùng.
Giới hạn
- Không cross-base: Link field không thể trỏ sang Base khác. Muốn làm điều này → phải dùng Sync Table (cơ chế 2).
- Filter giới hạn vùng chọn ("Specified records"): Lark UI cho phép đặt điều kiện filter vào link field (ví dụ "chỉ link tới khách hàng Active"). API v1 KHÔNG trả về filter này. → Nếu cần biết, phải Computer Use.
- Display format (hiển thị dạng card vs text): Không đọc được qua API.
Kết quả Incomex S176
Phase 3 dump đã capture ĐÚNG toàn bộ link fields trong 18 Base. Link graph của mỗi Base là tin cậy 100% trong file knowledge/dev/lark/snapshots/2026-04-11/bases/*.md.
2. Cơ chế 2 — Sync Table (bảng đồng bộ cross-base)
Bản chất
Sync Table là cách Lark cho phép dữ liệu vượt ranh giới Base. Bảng A ở Base Nguồn có thể "xuất" dữ liệu sang Bảng A' ở Base Đích. Bảng A' là bản sao vật lý, chỉ đọc, tự cập nhật theo chu kỳ (mặc định ~30 phút hoặc tùy config).
Mô hình phân phối: 1 bảng nguồn → N bảng đồng bộ. Mỗi bảng đồng bộ chỉ có 1 nguồn duy nhất.
Phân biệt (tránh nhầm lẫn):
- Sync Table: bản sao vật lý cross-base, có cập nhật định kỳ. ← cái ta đang nói.
- Sync View: khung nhìn đồng bộ, dùng nhúng trong Lark Docs. Khác với Sync Table.
- Duplicate Table: sao chép 1 lần, không đồng bộ gì thêm. Khác hoàn toàn.
API endpoint — ⚠️ KHÔNG CÓ endpoint chuyên biệt
Lark không cung cấp API riêng để quản lý Sync Table. Chỉ có thể gọi GET /tables (endpoint list table thường), và response không có cờ định danh sync cấp table.
Sample response THẬT /tables (capture từ Base đệm, S176)
{
"items": [
{ "name": "TTS", "revision": 48, "table_id": "tblPQ6N79EeOmnTm" },
{ "name": "Đơn hàng", "revision": 22, "table_id": "tblaU7kxyPTNBSrR" }
],
"total": 2
}
CHỈ 3 field: name, revision, table_id. Xác nhận chéo với SDK chính thức larksuite/oapi-sdk-go:
type AppTable struct {
TableId string `json:"table_id,omitempty"`
Revision int `json:"revision,omitempty"`
Name string `json:"name,omitempty"`
}
✅ BREAKTHROUGH — Field-level is_synced detect bảng Sync
Dù cấp table không có metadata sync, cấp FIELD có. Response /fields trả về is_synced: bool cho mỗi field.
Quy luật đã verify (S176):
- Bảng thường: tất cả fields đều
is_synced: false - Bảng Sync: tất cả fields đều
is_synced: true
Bằng chứng capture thật — bảng HT - Nghiệp đoàn trong Base 6.5 (app_token: HuXhbCXXPaOP4ksu7wslocqPgOr, table_id: tblNmU8SpHO7jEFE):
{
"items": [
{
"field_id": "fldp7qzhR7",
"field_name": "Mã nghiệp đoàn",
"is_primary": true,
"is_synced": true,
"is_extend": false,
"type": 1,
"ui_type": "Text",
"property": null
},
{
"field_id": "fldQgwR8aV",
"field_name": "Tên NĐ viết tắt - chữ Romaji",
"is_synced": true,
"type": 1
},
{
"field_id": "fldSPCrc0u",
"field_name": "Tên NĐ phiên âm",
"is_synced": true,
"type": 1
},
{
"field_id": "fldtbv8c3u",
"field_name": "Thuộc PTTT",
"is_synced": true,
"type": 3
},
{
"field_id": "fldjnpVr2Q",
"field_name": "SourceID",
"is_synced": true,
"type": 1
}
]
}
Đối chiếu: 4 bảng thường đã test (TTS, Đơn hàng Base đệm; BÁO CÁO-Danh sách, TTS-Lịch sử tài chính, Đơn hàng-Chính thức ở Base 88) — tất cả fields is_synced: false.
→ Phát hiện: Detect bảng Sync qua API = quét 1 field bất kỳ của bảng, check is_synced. Không cần Computer Use cho bước detect.
Field SourceID (xuất hiện trong bảng Sync HT - Nghiệp đoàn): rõ ràng user tự thêm để làm key match với bảng gốc. Đây là gợi ý: khi reconstruct mapping, có thể dùng field SourceID để cross-reference giữa bảng Sync và bảng gốc.
Những gì API VẪN KHÔNG cho biết
- Bảng Sync lấy từ Base nào (
source_app_token). - Bảng Sync lấy từ bảng nào (
source_table_id). - Field-level mapping (field nào ở đích tương ứng field nào ở nguồn).
- Lịch sync (interval, last_sync_time, sync_status, error log).
- Dependency lineage: 1 bảng gốc có bao nhiêu downstream đang đồng bộ.
Cách bù — Computer Use CHỈ CẦN cho source mapping
Thuật toán kết hợp:
- Detect qua API (rẻ, tự động): Quét 299 bảng, gọi
/fieldspage_size=1, checkis_syncedfield đầu. → danh sách ứng viên Sync. - Source mapping qua UI (đắt, thủ công): Với mỗi bảng Sync đã detect, mở UI Lark → Sync Setup → scrape Base nguồn, bảng nguồn, field mapping.
- Hoặc Network Intercept: Playwright intercept internal API khi UI load Sync Setup. Rẻ hơn OCR.
Field is_extend — ⚠️ S177 verify: Incomex không dùng
Test 3 bảng Sync thật (HT - Nghiệp đoàn, HT - Thông tin TTS, HT - Tình trạng duyệt YC ở Base 6.5): tất cả field đều is_extend: false, kể cả các field rõ ràng do user thêm sau (SourceID, 073 - tạo link, 072 - Tạo link, các field kỹ thuật *, **, .).
Kết luận: Incomex không dùng tính năng "Add extended field" trong Sync Table. Ý nghĩa chính xác của is_extend: true không verify được trên dữ liệu hiện tại. Giả thiết (chưa xác nhận): true khi user thêm field mới vào Sync Table qua nút "+" trong UI, thay vì thêm vào bảng gốc.
Hệ quả Phase 4 dump: is_extend không có giá trị thực tiễn cho Incomex. Có thể bỏ qua. Nếu muốn verify 100%, phải dùng Computer Use mở UI Sync Table để kiểm tra có option extend không — nhưng không ưu tiên.
3. Cơ chế 3 — Lookup field + Formula cross-table
Bản chất
Lookup cho phép "kéo" dữ liệu từ bảng khác vào bảng hiện tại qua 1 Link field. Giống hàm VLOOKUP trong Excel. Phụ thuộc vào Link field nguồn để biết phải tìm ở đâu.
Formula cross-table hoạt động tương tự — có thể viết công thức tham chiếu dữ liệu ở bảng khác qua Link.
API endpoint
Cùng endpoint với Link field: GET /fields.
- Lookup:
type = 19,ui_type = "Lookup" - Formula:
type = 20,ui_type = "Formula"
⚠️ Lưu ý: type 19 (Lookup) không create được qua API
Trong tool bitable_v1_appTable_create schema, enum type không có 19. Nghĩa là Lookup field không thể tạo qua API — chỉ tạo được qua UI. Nhưng đọc được đầy đủ qua /fields.
Sample response THẬT — Lookup field (capture từ Base 88, bảng "Đơn hàng - Chính thức", S176)
{
"field_id": "fldIxR3jK4",
"field_name": "Nghiệp đoàn quản lý (viết tắt)",
"type": 19,
"ui_type": "Lookup",
"property": {
"target_table": "tblG18kR9aFhWrJW",
"target_field": "fldtmEU87t",
"filter_info": {
"conditions": {
"children": [
{
"field_id": "fldt9VGyvm",
"field_type": 1005,
"operator": "is",
"value": null
}
],
"conjunction": "and"
},
"target_table": "tblG18kR9aFhWrJW"
},
"formula": "bitable::$table[tblG18kR9aFhWrJW].FILTER(CurrentValue.$column[fldt9VGyvm]=bitable::$table[tblh7nrQpK8TqIs2].$field[fldurWDSGm]).$column[fldtmEU87t].LISTCOMBINE()",
"roll_up": 0,
"formatter": ""
}
}
Đây là bằng chứng phá đổ claim "property = null cho Lookup" của Gemini Deep Research. Property có đầy đủ mọi thứ cần để trace.
Ý nghĩa property của Lookup
| Field | Ý nghĩa |
|---|---|
target_table |
table_id của bảng nguồn dữ liệu |
target_field |
field_id ở bảng nguồn cần lấy |
filter_info.conditions |
Điều kiện filter (dưới dạng tree children + conjunction) |
formula |
Biểu thức DSL Lark Bitable đầy đủ — parse được để hiểu logic |
roll_up |
Cờ aggregation. Giải mã đầy đủ S177 (verify bằng đối chiếu formula DSL trên bảng Nghiệp đoàn Base 88, 25+ sample) |
✅ roll_up — Mapping aggregation (S177, verify thật)
roll_up |
Aggregation | Formula method trong DSL | Số sample đã thấy |
|---|---|---|---|
| 0 | Lookup thuần (không aggregate) | ...LISTCOMBINE() |
4 |
| 1 | SUM | ...LISTCOMBINE().SUM() |
17 |
| 2 | COUNT | ...LISTCOMBINE().COUNTA() |
8 |
| 4 | MAX | ...LISTCOMBINE().MAX() |
1 |
| 3 | Nghi ngờ AVG | chưa gặp | — |
| 5 | Nghi ngờ MIN | chưa gặp | — |
| 6+ | Unknown | chưa gặp | — |
Phương pháp verify: trong mỗi field Lookup, property.formula chứa chuỗi DSL đầy đủ; kết thúc bằng method aggregation. Đối chiếu method ↔ roll_up → suy ra mapping.
Cách trace ngược: Với roll_up chưa gặp (3, 5), cần tìm bảng có Lookup AVG/MIN để capture. Nhưng có thể bỏ qua — Phase 4 dump có thể parse thẳng formula DSL để lấy method thay vì dựa vào roll_up flag.
Sample response THẬT — Formula field (cùng bảng)
{
"field_id": "fldDOaC2qA",
"field_name": "Ngày kết thúc HĐ",
"type": 20,
"ui_type": "Formula",
"property": {
"formula_expression": "TEXT(date(bitable::$table[tblh7nrQpK8TqIs2].$field[flds8IpQmL].YEAR()+bitable::$table[tblh7nrQpK8TqIs2].$field[fldSCBU2zI],bitable::$table[tblh7nrQpK8TqIs2].$field[flds8IpQmL].MONTH(),bitable::$table[tblh7nrQpK8TqIs2].$field[flds8IpQmL].DAY())+RANDOMBETWEEN(25,40),\"dd/mm/yyyy\")"
}
}
→ formula_expression là chuỗi DSL đầy đủ. Mọi tham chiếu cross-table đều ở dạng bitable::$table[TABLE_ID].$field[FIELD_ID]. Parse regex được.
Lark Bitable Formula DSL — cú pháp cốt lõi
bitable::$table[TABLE_ID]— tham chiếu bảng khác.$field[FIELD_ID]— field cụ thể.$column[FIELD_ID]— cột (trong ngữ cảnh FILTER).FILTER(<condition>)— lọc.LISTCOMBINE()— gộp kết quả thành 1 textCurrentValue.$column[...]— tham chiếu hiện hành trong vòng lặp- Hàm chuẩn:
TEXT,DATE,YEAR,MONTH,DAY,RANDOMBETWEEN,SUMIF,COUNTIF, v.v.
Cách trace chain
Vì có đủ target_table + target_field + formula DSL, có thể build đồ thị lookup dependency hoàn toàn tự động:
- Lặp qua mọi field trong mọi bảng.
- Với field
type=19hoặctype=20: parseformula/formula_expression, trích xuất mọi cặp(table_id, field_id)tham chiếu. - Tạo edge: bảng hiện tại → (target_table, target_field).
- Phát hiện vòng lặp (circular lookup) nếu có.
4. Cơ chế 4 — Automation (Workflows)
Bản chất
Automation (tên chính thức trong API: Workflow, tên trong UI: Automation) là engine xử lý sự kiện. Mỗi workflow gồm:
- Trigger: 1 sự kiện khởi phát (record created, field changed, checkbox toggled, cron, webhook, button clicked...)
- Conditions: điều kiện lọc trigger
- Actions: chuỗi hành động (create/update/delete record, send notification, call webhook, run script...)
Bối cảnh Incomex
Nhân viên cũ dùng chiến thuật "1 trigger chạy nhiều việc" — cấu hình hàng loạt action dưới 1 trigger duy nhất thông qua node rẽ nhánh. Lý do: Lark giới hạn số workflow trên mỗi Base (theo cộng đồng: ~50 automation/Base, số runs/tháng tùy gói).
Hậu quả: Logic nghiệp vụ của hệ thống Incomex nằm trong automation này. Không giải mã được = không hiểu hệ thống.
Trong dump S176, em đếm ~100 checkbox trigger trong riêng Base 88 (ví dụ 19.2 - Trigger nhập vào đơn mẹ, 120 - trigger, Trigger - Ghi KQ hoạt động CB PTTT...).
API endpoint
- List:
GET /open-apis/bitable/v1/apps/{app_token}/workflows - Update status:
PUT /open-apis/bitable/v1/apps/{app_token}/workflows/{workflow_id}(chỉ enable/disable)
Response qua PUBLIC REST — CHỈ 3 field (lạc hậu từ S177)
{
"workflows": [
{
"workflow_id": "...",
"title": "...",
"status": "Enable"
}
]
}
Public REST chỉ có: workflow_id, title, status.
⚡ NHƯNG — Internal API (Cổng 4) trả ĐẦY ĐỦ (S177 breakthrough)
Xem section 10bis. Endpoint /space/api/bitable/{app_token}/automation/list (cookie session) trả:
- 183 workflow trong Base 88, response 2.1 MB
- Mỗi workflow có
nodeSchema(DAG),extra(dependency manifest),draft(JSON string chứasteps[]với full trigger + action + condition tree) - → Không cần Computer Use cho Automation dump. Public REST bị giới hạn, internal API thì không.
Các loại trigger Lark hỗ trợ (từ UI, tổng hợp từ 3 báo cáo — chưa verify thực tế)
record_created/record_updated/field_changed/record_matchedbutton_clicked(field type 3001)scheduled(cron)webhook_received/feishu_message_received/email_receivedmanual
Các loại action (theo báo cáo)
create_record/update_record/delete_recordsend_notification/send_emailcall_webhook/call_openapirun_script(⚠️ mâu thuẫn: Gemini Deep nói bị khóa, GPT nói copy từ UI được)if_else,multi_branch,loop(node logic)
Cách bù — ✅ Cổng 4 đã giải quyết (S177)
Trước S177: Phải Computer Use hoặc Network Intercept.
Sau S177: Chỉ cần Cổng 4 internal API — 1 call /space/api/bitable/{token}/automation/list trả full trigger + action + condition tree cho mọi workflow trong Base. Network Intercept không phải để reverse engineer — chỉ để discover endpoint.
3 phương án cũ (chỉ dùng làm dự phòng):
- Phương án A — UI screenshot + LLM Vision: không cần nữa
- Phương án B — Playwright Network Intercept: không cần nữa — đã biết endpoint
- Phương án C — Enterprise Audit Log: chỉ bổ trợ cho run history nếu cần
Điểm mù còn lại: Workflow run history (execution log) chưa verify qua Cổng 4. Chờ S178 test.
5. Tổng kết — Matrix đọc được gì qua API
Cập nhật S178: 6 row "⚠️ lark-mcp chưa load tool" dưới đây (Views, App meta, Roles, Dashboards, Forms, Workflow list) đã được Cổng 2 (REST direct với tenant_access_token) phủ hoàn toàn — verify S178. Workflow list confirm shallow 3 field (chuyển Cổng 4 cho full logic). Role List trả RICH 278KB permission matrix (phát hiện mới, trái docs). Xem Section 10ter cho chi tiết.
| Thông tin cần | Cấp | Endpoint | Đọc được? | Nếu không, cách bù |
|---|---|---|---|---|
| Danh sách bảng trong Base | Base | /tables |
✅ (name, table_id, revision) | — |
| Schema field của bảng | Table | /fields |
✅ (field_id, name, type, property, is_synced, is_extend, description) | — |
| Link 1 chiều (Single) | Field | /fields type=18 |
✅ (table_id đích, multiple) | — |
| Link 2 chiều (Duplex) | Field | /fields type=21 |
✅ (table_id, back_field_id, back_field_name, multiple) | — |
| Lookup field | Field | /fields type=19 |
✅ (target_table, target_field, formula DSL, filter_info, roll_up đã giải mã) | — |
| Formula cross-table | Field | /fields type=20 |
✅ (formula_expression DSL, return type nested) | — |
| SingleSelect/MultiSelect options | Field | /fields type=3/4 |
✅ (id, name, color — id stable trong field) | — |
| AutoNumber rules | Field | /fields type=1005 |
✅ (auto_serial.type + options: system_number/fixed_text/created_time) | — |
| Field type 24 Stage | Field | /fields type=24 |
⚠️ CHỈ field_name + description — property = null | Computer Use để lấy stage options |
| Field type 3001 Button | Field | — | ⚠️ Chưa thấy trong Incomex — thay bằng Checkbox type 7 | — |
| Attachment type 17 | Field | /fields type=17 |
✅ field_name (property null) + record value chứa file tokens | — |
| User type 11 | Field | /fields type=11 |
✅ (multiple flag) | — |
| Link filter "Specified records" | Field | — | ❌ | Computer Use mở Edit Field |
| Bảng nào là Sync | Field | /fields is_synced |
✅ Verified S176 | — |
| Sync nguồn: Base nào, bảng nào | Table | — | ❌ | Computer Use Sync Setup / Network Intercept |
| Sync schedule (interval, last_sync) | Table | — | ❌ | Computer Use |
| Sync field mapping | Field | — | ❌ (có thể dùng field SourceID heuristic) |
Computer Use |
| Record data | Record | /records/search |
✅ Đầy đủ | — |
| Views (grid, form, kanban, gantt...) | Table | /tables/{id}/views |
⚠️ lark-mcp chưa load tool — endpoint Lark có, chưa verify Incomex | REST qua VPS agent |
| Danh sách workflow | Base | /workflows (public) |
❌ Chỉ id/title/status | Xem Cổng 4 |
| Workflow full logic | Base | /space/api/bitable/{token}/automation/list (Cổng 4) |
✅ FULL (S177) — draft.steps[] đầy đủ trigger + action + condition |
— |
| Workflow trigger spec | Workflow | Cổng 4 | ✅ step.type + step.data.fields |
— |
| Workflow action list | Workflow | Cổng 4 | ✅ step.type=*Action + step.data.values |
— |
| Workflow run history | Workflow | — | ⏳ Chưa test | Cổng 4 S178 hoặc Audit log |
| Workflow script source | Workflow | — | ⏳ Chưa gặp workflow Run Script | Cổng 4 S178 |
| App meta (name, owner, timezone) | Base | /apps/{token} |
⚠️ lark-mcp chưa load tool | REST qua VPS agent |
| Roles & Permissions | Base | /apps/{token}/roles |
⚠️ lark-mcp chưa load tool — endpoint Lark có (SDK) | REST qua VPS agent |
| Dashboards | Base | /apps/{token}/dashboards |
⚠️ lark-mcp chưa load tool | REST qua VPS agent |
| Forms metadata | Form | /apps/{token}/tables/{t}/forms/{f} |
⚠️ lark-mcp chưa load tool | REST qua VPS agent |
⚠️ Giới hạn lark-mcp toolset (verify S177)
Search 3 lần với các từ khóa "workflow", "view", "app meta" trên tool_search → lark-mcp chỉ expose 9 tool bitable:
bitable_v1_app_create(WRITE)bitable_v1_appTable_create(WRITE)bitable_v1_appTable_list(READ)bitable_v1_appTableField_list(READ)bitable_v1_appTableRecord_create(WRITE)bitable_v1_appTableRecord_batchCreate(WRITE)bitable_v1_appTableRecord_update(WRITE)bitable_v1_appTableRecord_batchUpdate(WRITE)bitable_v1_appTableRecord_search(READ)
Thiếu hoàn toàn: views, workflows, app meta, roles, dashboards, forms, field create, field update, field delete, table update, table delete, record delete, copy, file upload, sync source query. Đây là giới hạn công cụ, KHÔNG phải giới hạn Lark API.
Hệ quả: Muốn tối đa khả năng READ của Lark, phải gọi REST trực tiếp qua SDK Python/Go trên VPS agent với tenant token — cơ chế này em sẽ dùng ở Phase 2 tiếp theo.
6. Hành động tiếp theo cho Incomex (cập nhật S177)
Bước ngắn hạn — Cổng public REST (rẻ, cron-able)
- Phase 4A — Lookup/Formula graph:
- Gọi
/fieldstoàn bộ 299 bảng với capture đầy đủ property (Lookup type 19 + Formula type 20) - Parse
formulaDSL regex tìmbitable::$table[TABLE].$field[FIELD]→ build edge graph - Output:
knowledge/dev/lark/snapshots/.../lookup-formula-graph.md - Toolkit: Claude Code VPS + lark-oapi SDK (Cổng 2 — chờ bàn scope)
- Gọi
Bước trung hạn — Cổng 4 Chrome (cần cookie session)
- Phase 4B — Sync Table registry (giảm 94% chi phí so với phương án cũ):
- Thay vì quét 299 bảng field-by-field, gọi
clientvars18 lần/18 Base → lấysyncTableIds - 18 API call thay vì ~299
- Thay vì quét 299 bảng field-by-field, gọi
- Phase 4C — Automation full dump (breakthrough S177):
- Gọi
automation/list18 lần → toàn bộ workflow toàn hệ thống - Parse
draft.steps[]→ build trigger-action dependency graph - Parse
extra.TableMap.FieldMap→ build "workflow nào đụng bảng/field nào" bidirectional map
- Gọi
- Phase 4D — View + Formula compiled code:
- Từ
clientvars.table.viewsvàclientvars.table.formulaInfo - Capture 37 views/bảng × 299 bảng ≈ dump view config toàn hệ thống
- Từ
Điểm mù còn lại (bắt buộc S178 test)
- Sync source mapping (
source_app_token,source_table_id, field mapping):- Chưa có trong
clientvars - Cần test: Click "Sync Setup" UI trong Lark Web → bắt endpoint bằng Chrome Network Intercept
- Chưa có trong
- Automation run history + Script action source:
- Chưa test endpoint
/automation/runshay tương tự
- Chưa test endpoint
- Stage field (type 24) options:
- Chưa thấy ở đâu
- Có thể nằm trong view config hoặc cần endpoint riêng
Bước dài hạn
- Sau khi có đủ schema + Lookup chain + Sync map + Automation full logic (đã có!) → đã đủ thông tin để hiểu luồng nghiệp vụ thật → bắt đầu bàn migrate sang PostgreSQL.
7. TODO — những điểm cần verify thêm
- ✅
— DONE S176is_syncedfield meaning - ✅
— DONE S177: Incomex không dùng. Bỏ qua.is_extendfield - ✅
— DONE S177: 0=None, 1=SUM, 2=COUNT, 4=MAX. Parse thẳng DSL tốt hơn.roll_upflag - ✅
Field type 24 Stage— DONE S177:property: nullqua API. Cần Computer Use. - ✅
Field type 3001 Button— DONE S177: Incomex không có, dùng Checkbox type 7 làm trigger. - ⏳ Workflow API (cực cao ưu tiên): Gọi thật
/apps/{token}/workflows+ endpoint chi tiết. lark-mcp không có tool → giao VPS agent REST. - ⏳ Views API: Gọi
/apps/{token}/tables/{t}/views— Lark có endpoint nhưng lark-mcp chưa load. Có thể lộ thêm thông tin filter, sort, layout Kanban/Gantt mà Base thiết lập. - ⏳ App meta:
GET /apps/{token}— có trả owner, timezone, cipher? Chưa test. - ⏳ Roles & Permissions API: Lark có — chưa verify. Có thể lộ ai được phép làm gì trên Base.
- ⏳ Dashboards API: Có thể trả config dashboard charts.
- ⏳ Forms API: Bảng có form input từ người ngoài — form metadata.
- ⏳ Script action source code: Test UI.
7bis. Phát hiện mới S177 — property API trả về nhiều hơn tưởng
Những thông tin sau có sẵn trong response /fields nhưng dump Phase 3 chưa capture. Phase 4 dump bắt buộc lấy:
(a) Field description — note người dùng viết cho field
{
"field_id": "fldyVMoRYW",
"field_name": "TTS - Giấy tờ & Sau XC",
"description": "Liên kết với bảng \"TTS - Giấy tờ & Sau XC\"\nCột Nghiệp đoàn mẹ",
"type": 21,
"ui_type": "DuplexLink"
}
→ Metadata tài liệu hóa quý giá. Nhân viên cũ note cả cột đích vào đây. Có trong response chỉ khi user đã ghi description.
Param truyền thêm: text_field_as_array: true để nhận description dạng rich text array nếu cần.
(b) Formula property.type nested — kiểu trả về
{
"field_id": "fldePzE1fW",
"field_name": "Tính ngày kích hoạt tạo CV xuất HĐ",
"type": 20,
"ui_type": "Formula",
"property": {
"formatter": "dd/MM/yyyy",
"formula_expression": "WORKDAY(...)",
"type": {
"data_type": 5,
"ui_property": {"date_formatter": "dd/MM/yyyy"},
"ui_type": "DateTime"
}
}
}
→ Formula có thể trả về Number/DateTime/Text/Boolean. property.type.data_type = type number, ui_type = tên UI. Dùng để biết field này ghép vào đâu (ví dụ Formula trả Number thì có thể dùng cho Lookup SUM).
(c) SingleSelect/MultiSelect options — đầy đủ {id, name, color} với id stable
{
"property": {
"options": [
{"color": 27, "id": "optBWZ6OBA", "name": "Đang hợp tác"},
{"color": 33, "id": "opt9s3jEiu", "name": "Dừng hợp tác"}
]
}
}
→ id (dạng optXXXXX) là stable identifier, giữ nguyên dù user đổi name. Dùng để match option giữa các bảng Sync ↔ Source. Formula DSL tham chiếu option qua id: $option[optanHABwb].
(d) Filter DSL (trong Lookup property) — 12 operator đã gặp
Filter filter_info.conditions có cấu trúc tree:
{
"conditions": {
"children": [
{"field_id": "fldBg5kdNs", "field_type": 21, "operator": "is", "value": null},
{"field_id": "fldkzNsFOk", "field_type": 3, "operator": "is", "value": null}
],
"conjunction": "and"
},
"target_table": "tbl2GXNdArZ7zoAR"
}
Operators đã gặp thực tế (từ bảng Nghiệp đoàn): is, isNot, isGreater, isLess, isEmpty, isNotEmpty, contains, doesNotContain. Theo schema appTableRecord_search tool, tổng 12 operator gồm thêm: isGreaterEqual, isLessEqual, like, in (2 cái cuối chưa hỗ trợ).
Quan trọng: Filter dùng field_id chứ không phải field_name → parse được ổn định kể cả khi rename.
(e) auto_serial — cấu trúc auto number
{
"property": {
"auto_serial": {
"type": "custom",
"options": [
{"type": "system_number", "value": "4"},
{"type": "fixed_text", "value": ".ND"}
]
}
}
}
→ Ví dụ tạo mã dạng 0123.ND. Có 3 type rule: system_number, fixed_text, created_time. Hoặc auto_serial.type = "auto_increment_number" cho số đơn giản.
(f) Formula options dùng $option[optID] syntax
Formula DSL tham chiếu option bằng id stable:
CurrentValue.$column[fldkzNsFOk] = bitable::$table[...].$column[fldkzNsFOk].$option[optwc37Pj3]
→ Parse regex được để biết formula so sánh với option cụ thể nào → reconstruct ý nghĩa logic.
(g) Anomaly — field tên kỳ lạ *, **, . trong bảng Sync
Bảng HT - Tình trạng duyệt YC có các field tên *, **, ., 073 - tạo link, 072 - Tạo link. Đây là dấu hiệu nhân viên cũ dùng field kỹ thuật để đánh dấu trạng thái automation. Phase 4 dump bắt buộc capture field_name chính xác (không strip/normalize) vì các field này có ý nghĩa riêng.
(h) Field type 24 Stage — API không trả options
Bảng Đơn hàng - Chính thức Base 88 có field "Tiến độ đơn hàng" type 24 ui_type Stage:
{
"field_id": "fldKP5UF1p",
"field_name": "Tiến độ đơn hàng",
"description": "Test thử dùng Automation kích hoạt tiến độ:\n\n1. Phỏng vấn: Báo cáo thi tuyển\n2. Làm hồ sơ: 9. Chuẩn bị & làm HSN\n3. Đã gửi hồ sơ: Đã gửi HSK\n4. Có COE: Có COE\n5. Có Visa\n6. Đã xuất cảnh: Đã xuất cảnh",
"type": 24,
"ui_type": "Stage",
"property": null
}
→ property: null — Lark API không trả stage definitions. Chỉ description (nếu user note tay) tiết lộ stage list. Muốn biết chính xác phải Computer Use. Phase 4 dump: đánh dấu field type 24 là "Stage — options cần Computer Use".
(i) Anomaly — Option id không unique toàn Base
Phát hiện option id opteL90RRR xuất hiện ở 2 field khác nhau (fldlLb5WgN "Trình độ yêu cầu" và fldNl87vnX "Kinh nghiệm yêu cầu"), cả 2 đều có name "Không". Nghĩa là:
- Option
idchỉ unique trong 1 field - Không dùng option id để cross-reference giữa các field khác nhau
- Chỉ dùng
(field_id, option_id)kết hợp mới đảm bảo unique
(j) Anomaly — Duplicate option name trong cùng 1 field
Cùng bảng trên, field "Trình độ yêu cầu" có 2 option đều tên "Không":
{"color":3,"id":"optA2Z2x4a","name":"Không"},
{"color":0,"id":"opteL90RRR","name":"Không"}
→ Bug data của user. Phase 4 dump phải cảnh báo (anomaly report) khi phát hiện duplicate name. Cơ chế match record theo option name sẽ bị lỗi.
(k) Option list rất dài (smell quản lý data)
Field "Trình độ yêu cầu" có 16 options, trong đó nhiều option tên dài tới 100+ ký tự (mô tả điều kiện TTS kỹ năng số 2/3). Đây là smell — user dùng SingleSelect thay vì Link field để tra cứu. Phase 4 dump nên đánh dấu field có option > 20 hoặc option name > 50 ký tự làm anomaly.
8. Nguồn tham khảo
Nguồn chính (đã verify)
- SDK chính thức Go:
github.com/larksuite/oapi-sdk-gov1.1.48 — xác nhận structAppTable,AppTableField,AppTableFieldProperty. - Tool schema lark-mcp: Enum field types từ
bitable_v1_appTable_create(tool definition). - API calls thật trên Incomex (S176, 2026-04-11):
- Base đệm
Nf2bb1ExXaYnlksgoyQl72GNgAc:/tables+/fieldsbảng TTS - Base 88
YSIkb8PxOaNaozs2vwalOOcagkf:/fieldstrên BÁO CÁO-Danh sách, TTS-Lịch sử tài chính, Đơn hàng-Chính thức - Base 6.5
HuXhbCXXPaOP4ksu7wslocqPgOr:/fieldstrên HT-Nghiệp đoàn (verifyis_synced: true)
- Base đệm
Nguồn tham khảo (chưa fetch được — JS-rendered)
- Lark Open Platform: https://open.larksuite.com/document/server-docs/docs/bitable-v1/
- Feishu Open Platform: https://open.feishu.cn/document/server-docs/docs/bitable-v1/
Báo cáo nội bộ (đã đối chiếu)
knowledge/current-state/reports/gemini-s176-lark-mechanisms-report.md— Gemini CLI, rất sơ sài, có 2 claim saiknowledge/current-state/reports/gpt-s176-lark-mechanisms-report.md— GPT, chi tiết và cẩn thận- Gemini Deep Research (dán trong session S176) — rất chi tiết nhưng có 1 claim sai lớn: "Lookup property = null" (đã phá đổ bằng API call thật)
Snapshot Phase 3 dump
knowledge/dev/lark/snapshots/2026-04-11/— 18 Base, 299 bảng, 11.854 fields schema
12. 📐 Quy định dump Phase 4 (CHỐT S178 — bất biến)
Mục đích: Chuẩn hóa cách dump dữ liệu Lark qua Cổng 2 + Cổng 4 để phục vụ vẽ bản thiết kế hệ thống. Mọi phiên làm việc trên Lark từ S178 trở đi PHẢI tuân theo quy định này. Không mỗi phiên mỗi kiểu.
12.1 Dump MỚI hoàn toàn, KHÔNG bổ sung vào dump cũ
- Phase 3 dump (
snapshots/2026-04-11/, S176, qua Cổng 1) là snapshot lịch sử — GIỮ NGUYÊN, KHÔNG sửa, KHÔNG merge. - Phase 4 dump là baseline mới cho vẽ thiết kế, dùng Cổng 2 + Cổng 4.
- Lý do: Cổng 2/4 trả cấu trúc dữ liệu khác Cổng 1 (richer, nested, trường mới). Merge = vá chằng chịt, vi phạm Fix Gốc.
12.2 Thư mục cố định
knowledge/dev/lark/snapshots/2026-04-12-phase4/
├── index.md # Tổng quan + checksum 18 Base + nguồn cổng
├── cong2/ # Dump Cổng 2 REST (tenant_access_token)
│ └── {base-slug}/
│ ├── app.json # GET /apps/{token}
│ ├── tables.json # GET /apps/{token}/tables
│ ├── fields/{table_id}.json
│ ├── views/{table_id}.json
│ ├── forms.json
│ ├── workflows-shallow.json
│ ├── roles.json # RICH 278KB permission matrix
│ └── members.json
├── cong4/ # Dump Cổng 4 internal (cookie session)
│ └── {base-slug}/
│ ├── clientvars-{table_id}.json # đã decode gzip
│ ├── automation-list.json # 183 workflow full logic
│ ├── automation-configs.json
│ └── sync-source/{table_id}.json # Sync source mapping (S178 test)
└── cross-base/ # Analysis cross-Base
├── sync-graph.md # Bảng Sync nào lấy từ Base/bảng/field nào
├── link-graph.md # Link field cross-table (per Base)
├── lookup-graph.md # Lookup/Formula dependency
└── automation-graph.md # Workflow đụng bảng/field nào
12.3 Metadata header bắt buộc cho mọi file dump
Mỗi file JSON dump phải có header:
{
"_meta": {
"cong": 2, // 2 hoặc 4
"endpoint": "GET /apps/{token}", // endpoint chính xác
"app_token": "YSIkb8PxOaNaozs2...",
"table_id": "tblh7nrQpK8TqIs2", // nếu có
"timestamp": "2026-04-12T10:30:00Z",
"http_status": 200,
"response_size_bytes": 12345,
"toolkit_version": "lark_probe@rev1", // với Cổng 2
"dump_session": "S178"
},
"data": { ... } // response thật của Lark
}
12.4 Nguyên tắc bất biến
- KHÔNG sửa dump cũ
snapshots/2026-04-11/— giữ lịch sử đối chứng. - KHÔNG merge file Cổng 2 và Cổng 4 vào chung 1 file — giữ riêng để biết nguồn dữ liệu.
- KHÔNG strip field trong response — dump nguyên văn. Phân tích để sau.
- Bản thiết kế vẽ lại phải trích dẫn path chính xác tới file dump:
snapshots/2026-04-12-phase4/cong4/base-65/sync-source/tblNmU8SpHO7jEFE.json - Luồng A→B→C phải chỉ được A ở đâu, B ở đâu, C ở đâu — mỗi node có bằng chứng từ file dump cụ thể.
- Không đoán URL Cổng 4 — chỉ gọi URL đã thấy trong network log thật (S177 đã chứng minh 16/16 URL đoán đều 404).
12.5 Thứ tự dump Phase 4 (S178+)
- Cổng 4 — test 3 câu điểm mù trước (Câu 1 Sync source, Câu 2 Run history, Câu 6 WRITE cookie) — 30-60 phút, biết sớm có phải xuống Cổng 5 không
- Cổng 2 dump production toàn bộ 18 Base (toolkit
tools/lark_probe/, headless cron-able, ~3-5 phút schema-only) - Cổng 4 dump bổ sung —
clientvarsper table +automation/listper Base — cho 7 loại độc quyền (automation full, syncTableIds, formula code, trashBlockMap, viewRankMap, change stream, deniedRecords) - Phân tích + vẽ
cross-base/*.md— từ dump thật - Anh Huyên duyệt tay bản thiết kế → mới sửa production (Cổng 2 GĐ2 WRITE)
- Thiếu gì → Cổng 5 Computer Use (giải pháp cuối)
12.6 Cổng 3 — BỎ (S178 chốt)
Cổng 3 = "MCP wrapper mở rộng" — không thêm thông tin mới, chỉ đổi cách gọi. Toolkit Cổng 2 Python đã đủ cho agent gọi trực tiếp. Không build.
13. 📋 Phase 4 Data Inventory — Checklist lấy thông tin (SSOT S178+)
Mục đích: Danh sách đầy đủ các loại thông tin cần lấy để vẽ lại bản thiết kế Lark Incomex. Tư duy sản xuất — liệt kê trước, tick khi có thật.
Quy tắc tick:
- Chỉ tick
[x]khi LẤY XONG và verify đủ — không tick dự định- Chỉ tick cổng thực tế đã lấy thành công, không tick cổng "có thể lấy"
- Gặp thông tin mới chưa có → thêm dòng ngay
- Ghi chú phải có path file dump hoặc endpoint cụ thể
Ký hiệu:
[ ]chưa lấy ·[x]đã lấy ·[~]một phần ·[?]không rõCổng: C1 lark-mcp · C2 REST tenant token · C4 Network intercept cookie · C5 Computer Use
A. App / Base metadata
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| A01 | Base name, revision, timezone | [ ] | [ ] | [ ] | [ ] | C2 /apps/{token}, C4 clientvars.base |
| A02 | Base owner | [ ] | [ ] | [ ] | [ ] | C4 clientvars.base.owner |
| A03 | Feature flags (calcMode, isProBase, baseEngineEnabled) | [ ] | [ ] | [ ] | [ ] | C4 only |
| A04 | baseRecordsNum (tổng record toàn Base) |
[ ] | [ ] | [ ] | [ ] | C4 only |
| A05 | advPermInfo |
[ ] | [ ] | [ ] | [ ] | C4 only |
| A06 | permissionMap |
[ ] | [ ] | [ ] | [ ] | C4 only |
| A07 | baseRoleMap (shallow) |
[ ] | [ ] | [ ] | [ ] | C4 only |
| A08 | trashBlockMap / trashTableMap (bảng đã xóa) |
[ ] | [ ] | [ ] | [ ] | C4 only |
| A09 | marketplaceInfo |
[ ] | [ ] | [ ] | [ ] | C4 only |
| A10 | blocks[] (danh sách tất cả tableId) |
[ ] | [ ] | [ ] | [ ] | C4 only |
| A11 | blockInfos (meta per block) |
[ ] | [ ] | [ ] | [ ] | C4 — S178 thấy {id, name} |
| A12 | Bonus C2: advance_version, formula_type, app_token |
[ ] | [ ] | [ ] | [ ] | C2 only S178 |
B. Table (cấp bảng)
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| B01 | table_id, name, revision | [ ] | [ ] | [ ] | [ ] | — |
| B02 | Record count | [ ] | [ ] | [ ] | [ ] | — |
| B03 | meta.jointRev (Sync local rev vs source rev) |
[ ] | [ ] | [ ] | [ ] | C4 only S178 |
| B04 | meta.depRev |
[ ] | [ ] | [ ] | [ ] | C4 — cần hiểu thêm |
| B05 | tableLimit, tablePerm |
[ ] | [ ] | [ ] | [ ] | C4 only |
| B06 | primaryKey field_id |
[ ] | [ ] | [ ] | [ ] | C4 only |
| B07 | exInfo.exType, contentCreation |
[ ] | [ ] | [ ] | [ ] | C4 only |
C. Field (cấp trường)
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| C01 | field_id, name, type, ui_type | [ ] | [ ] | [ ] | [ ] | 3 cổng có |
| C02 | description (user note) | [ ] | [ ] | [ ] | [ ] | C2 text_field_as_array=true |
| C03 | is_primary, is_synced, is_extend | [ ] | [ ] | [ ] | [ ] | C2 verify S176 |
| C04 | property đầy đủ per type | [ ] | [ ] | [ ] | [ ] | C2 S177 |
| C05 | SingleSelect/MultiSelect options {id, name, color} |
[ ] | [ ] | [ ] | [ ] | C2 |
| C06 | Link (18/21) target, back_field, multiple | [ ] | [ ] | [ ] | [ ] | C2 |
| C07 | Link filter "Specified records" | [ ] | [ ] | [ ] | [ ] | ❌ C2 không có — cần C5 |
| C08 | Lookup target_table, roll_up, filter, formula DSL | [ ] | [ ] | [ ] | [ ] | C2 |
| C09 | Formula formula_expression DSL |
[ ] | [ ] | [ ] | [ ] | C2 |
| C10 | Formula return type (nested) | [ ] | [ ] | [ ] | [ ] | C2 S177 |
| C11 | Formula compiled code | [ ] | [ ] | [ ] | [ ] | C4 only table.formulaInfo.code |
| C12 | auto_serial rule | [ ] | [ ] | [ ] | [ ] | C2 |
| C13 | Stage (type 24) options | [ ] | [ ] | [ ] | [ ] | ❌ C2+C4 property:null — cần C5 |
| C14 | description.disableSync per field (cờ selective sync tick) |
[ ] | [ ] | [ ] | [ ] | C4 only S178 |
| C15 | exInfo.synced field-level |
[ ] | [ ] | [ ] | [ ] | C4 S178 |
| C16 | allowedEditModes (manual/scan) |
[ ] | [ ] | [ ] | [ ] | C4 S178 |
D. View
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| D01 | view_id, name, type, public_level | [ ] | [ ] | [ ] | [ ] | C2 S178 |
| D02 | filter_info (tree) | [ ] | [ ] | [ ] | [ ] | C2 |
| D03 | hidden_fields | [ ] | [ ] | [ ] | [ ] | C2 — 1 view lộ 54 field IDs |
| D04 | hierarchy_config | [ ] | [ ] | [ ] | [ ] | C2 |
| D05 | Group config | [ ] | [ ] | [ ] | [ ] | C4 groupList |
| D06 | rankInfo.viewRankMap (record ordering custom) |
[ ] | [ ] | [ ] | [ ] | C4 only |
| D07 | Layout Kanban/Gantt/Gallery detail | [ ] | [ ] | [ ] | [ ] | C4 viewMap |
| D08 | viewMap đầy đủ (Base 88: 37/bảng) |
[ ] | [ ] | [ ] | [ ] | C4 rich |
E. Record
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| E01 | Record values | [ ] | [ ] | [ ] | [ ] | C2 500/call, C4 3000/call |
| E02 | deniedRecords (record perm) |
[ ] | [ ] | [ ] | [ ] | C4 only |
| E03 | Comment per record | [ ] | [ ] | [ ] | [ ] | C4 commentMap |
| E04 | Attachment file tokens | [ ] | [ ] | [ ] | [ ] | C4 resourceMap.attachments |
| E05 | Reminder | [ ] | [ ] | [ ] | [ ] | C4 resourceMap.reminders |
| E06 | Milestone | [ ] | [ ] | [ ] | [ ] | C4 milestoneMap |
| E07 | Change stream cs, latestCSRev (revision history) |
[ ] | [ ] | [ ] | [ ] | C4 only |
F. Sync Table (⚠️ vùng bế tắc)
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| F01 | Detect bảng nào Sync (is_synced field-level) |
[ ] | [ ] | [ ] | [ ] | C2 + C4 |
| F02 | syncTableIds[] danh sách Sync tables trong Base |
[ ] | [ ] | [ ] | [ ] | C4 only — 1 call/Base |
| F03 | source_app_token (Base nguồn) |
[ ] | [ ] | [ ] | [ ] | ❌ S178 BẾ TẮC — C2 không có, C4 chưa tìm ra, chờ C5 |
| F04 | source_table_id (bảng nguồn) |
[ ] | [ ] | [ ] | [ ] | ❌ Như F03 |
| F05 | Field mapping source ↔ dest | [ ] | [ ] | [ ] | [ ] | ❌ Như F03 |
| F06 | Selective field sync tick (disableSync per field) |
[ ] | [ ] | [ ] | [ ] | C4 S178 — biết user tick trường nào |
| F07 | Sync schedule (tự động/tắt) | [ ] | [ ] | [ ] | [ ] | UI hiện "Đã tắt tự động" — chưa biết field API |
| F08 | Last sync time | [ ] | [ ] | [ ] | [ ] | UI "13 phút trước" — chưa biết field |
| F09 | meta.jointRev đếm số lần sync |
[ ] | [ ] | [ ] | [ ] | C4 S178 |
| F10 | Sync error log | [ ] | [ ] | [ ] | [ ] | Chưa biết endpoint |
| F11 | Downstream lineage | [ ] | [ ] | [ ] | [ ] | Suy ra từ F03 toàn hệ thống |
G. Form
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| G01 | Form metadata | [ ] | [ ] | [ ] | [ ] | C2 S178 |
| G02 | shared_limit, submit_limit_once |
[ ] | [ ] | [ ] | [ ] | C2 bonus S178 |
| G03 | Question tree rich_description |
[ ] | [ ] | [ ] | [ ] | C2 only S178 |
| G04 | Form answer records | [ ] | [ ] | [ ] | [ ] | Qua records API |
H. Dashboard
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| H01 | Dashboard list | [ ] | [ ] | [ ] | [ ] | C2 — Base 88 = 0 dashboard |
| H02 | Dashboard detail (charts config) | [ ] | [ ] | [ ] | [ ] | Chưa có endpoint detail xác nhận |
I. Workflow / Automation
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| I01 | workflow_id, title, status (shallow) | [ ] | [ ] | [ ] | [ ] | C2 S178 |
| I02 | nodeSchema DAG |
[ ] | [ ] | [ ] | [ ] | C4 only S177 |
| I03 | draft.steps[] full trigger+action+condition |
[ ] | [ ] | [ ] | [ ] | C4 only S177 |
| I04 | extra.TableMap.FieldMap dependency manifest |
[ ] | [ ] | [ ] | [ ] | C4 only S177 |
| I05 | extra.BlockMap, extra.BtnTrigger |
[ ] | [ ] | [ ] | [ ] | C4 |
| I06 | createUID, updateUID, timestamps | [ ] | [ ] | [ ] | [ ] | C4 |
| I07 | stepConfig schema global (loại trigger/action) |
[ ] | [ ] | [ ] | [ ] | C4 /automation/configs |
| I08 | events, template, limits |
[ ] | [ ] | [ ] | [ ] | C4 /automation/configs |
| I09 | Run history (execution log) | [ ] | [ ] | [ ] | [ ] | ⏳ Câu 2 Cổng 4 chưa test |
| I10 | Script action source code | [ ] | [ ] | [ ] | [ ] | ⏳ Chưa gặp Run Script workflow |
| I11 | Enable/disable toggle | [ ] | [ ] | [ ] | [ ] | C2 WRITE GĐ2 |
J. Role / Permission
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| J01 | Role list (id, name) | [ ] | [ ] | [ ] | [ ] | C2 S178 |
| J02 | table_roles[] 278KB permission matrix (per role per table per field) |
[ ] | [ ] | [ ] | [ ] | C2 only S178 |
| J03 | Role members | [ ] | [ ] | [ ] | [ ] | C2 — chưa verify 100% |
K. Member / User
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| K01 | Member list | [ ] | [ ] | [ ] | [ ] | C2 S178 |
| K02 | user_id bonus |
[ ] | [ ] | [ ] | [ ] | C2 only S178 |
| K03 | member_en_name bonus |
[ ] | [ ] | [ ] | [ ] | C2 S178 |
| K04 | userMap cached |
[ ] | [ ] | [ ] | [ ] | C4 |
L. Cross-base graph (tổng hợp để vẽ)
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| L01 | Link field graph (cùng Base) | [ ] | [ ] | [ ] | [ ] | Từ C06 |
| L02 | Lookup dependency graph | [ ] | [ ] | [ ] | [ ] | Parse DSL từ C08 |
| L03 | Formula cross-table | [ ] | [ ] | [ ] | [ ] | Parse từ C09 |
| L04 | Sync cross-base graph | [ ] | [ ] | [ ] | [ ] | ❌ Chặn bởi F03-F05 |
| L05 | Automation → table/field reverse index | [ ] | [ ] | [ ] | [ ] | Parse I04 |
M. System metadata (toolkit)
| STT | Thông tin | C1 | C2 | C4 | C5 | Ghi chú |
|---|---|---|---|---|---|---|
| M01 | 46 endpoint public REST | [x] | — | — | — | S178 GĐ1 |
| M02 | 4+ endpoint internal /space/api/ |
— | — | [x] | — | S177 + S178 |
| M03 | Auth public (tenant_access_token) | — | [x] | — | — | S178 toolkit |
| M04 | Auth internal (cookie + CSRF header) | — | — | [x] | — | S178 — x-csrftoken = cookie _csrf_token |
| M05 | Gzip+base64 decode recipe | — | — | [x] | — | S177 |
| M06 | Fetch+XHR interceptor technique | — | — | [x] | — | S178 — monkey-patch persistent |
| M07 | Body format views/ |
— | — | [x] | — | S178 {token, tableId, tableRev, viewIdList, supportRank} |
| M08 | Body format fetch_calc_status/ |
— | — | [x] | — | S178 {baseToken, tableID} |
| M09 | Body format explorer/v2/entity/info/ |
— | — | [ ] | — | Endpoint tồn tại, CSRF pass, body chưa đúng |
Tiến độ tổng
Tổng mục inventory: ~85
Đã lấy chính thức: 0 (chưa có dump Phase 4 chính thức nào)
Tick [x] trong M: 8 (technique learnings, không phải data)
Chưa lấy: ~85
Lưu ý: Tất cả các mục A-L đang [ ] vì chưa có dump Phase 4 production. S177/S178 là probing — discover capability, không phải dump file. Khi toolkit tools/lark_probe/ chạy xong 18 Base và file ra snapshots/2026-04-12-phase4/ mới tick [x] cổng tương ứng.
Điểm mù nghiêm trọng
- F03-F05 Sync source mapping — chặn cả L04 Sync cross-base graph. Chưa có hướng C4. Cần thử: (a) decode tầng 2 gzip nested blob trong
table(110 blobH4sI), (b) quétclientvarsbảng SOURCE, (c) C5 Computer Use cuối. - C13 Stage options — C2+C4 đều
property:null. Cần C5. - C07 Link filter — C2 không trả. Cần C5.
- I09-I10 Run history + Script — chưa test Cổng 4.
- H02 Dashboard detail — chưa biết endpoint.
9. Lịch sử cập nhật
-
v28 (S178, 2026-04-11, late): 📋 Thêm Section 13 Phase 4 Data Inventory — checklist ~85 mục thông tin cần lấy, cột C1/C2/C4/C5 + ghi chú. SSOT cho tư duy sản xuất Phase 4. Ghi nhận 6 phát hiện kỹ thuật mới S178: (1)
fieldMap[*].description.disableSynccờ selective sync tick, (2)fieldMap[*].exInfo.syncedfield-level, (3)meta.jointRevđếm sync, (4) CSRF headerx-csrftoken= cookie_csrf_token, (5) Fetch+XHR interceptor persistent, (6) Body format 2 endpoint mớiviews/vàfetch_calc_status/. Xác nhận bế tắc F03-F05 Sync source mapping: panel "Thiết lập đồng bộ hóa" render 100% từclientvarscached, KHÔNG fire network request → chờ test decode tầng 2 nested gzip blob hoặc chuyển C5. -
v21-v24 (S178, 2026-04-11): 🏆 Cổng 2 GĐ1 HOÀN THÀNH — 46 endpoint enumerate, 15/15 READ verified (100% pass), toolkit
tools/lark_probe/645 LOC Python production-ready. Demo dump Base 88: 567 call / 256s / 10.7 MB. 5 phát hiện trái docs (App Get 3 bonus, Form Get 2 bonus, Form Fields rich_description, Role List 278KB permission matrix, Member List 2 bonus). 2 undocumented quirks (Role page_size max=30, Record List 12× chậm hơn Search). 3 giá trị độc quyền Cổng 2 vs Cổng 4 (role perm, form questions, member user_id). Ranh giới C2 ↔ C4 rõ ràng — Phase 4 dump dùng CẢ HAI. Bottleneck mới: bảng "Kế hoạch công việc" ~424K records. Thêm section 10ter, patch section 5 + 11 header, cross-ref vào section 10bis. -
v1 (S176, 2026-04-11): Tạo lần đầu. Verify được 2/3 mâu thuẫn. 7 TODO.
-
v12-v13 (S177, 2026-04-11, late): 🏆 BREAKTHROUGH Cổng 4 — Chrome Network Intercept verify được internal API vượt xa public.
clientvarstrả 5.3 MB/bảng (schema+37 views+records+formula code+permission).automation/listtrả 183 workflow vớidraft.stepsFULL trigger+action+condition tree — GPT report SAI. Không cần Computer Use cho Automation dump.syncTableIdslộ list Sync trong 1 call. Path pattern internal/space/api/v1/bitable/và/space/api/bitable/. Auth = cookie session. Gzip+base64 encoding. Xem section 10bis. -
v2 (S176, 2026-04-11): Verify thêm
is_syncedfield meaning — breakthrough detect Sync Table qua API. Còn 6 TODO. -
v3-v10 (S177, 2026-04-11, early): Đóng 5 TODO, phát hiện 11 mục mới, matrix endpoint mở rộng, xác nhận lark-mcp chỉ có 9 tool.
-
v11 (S177, 2026-04-11, mid): 🚨 Thay đổi cách tiếp cận: từ "API vs Computer Use" sang "5 cổng vào Lark". Ghi nhận GPT Deep Research report — chờ kiểm chứng.
-
v12 (S177, 2026-04-11, late): 🏆 CỔNG 4 VERIFY THỰC TẾ — PHÁ VỠ TƯỜNG. GPT sai về Workflow: Internal API trả toàn bộ 183 workflow với full logic trong 1 request. Phát hiện endpoint
/space/api/v1/bitable/{token}/clientvarstrả schema + views + records + formula + permissions + change stream 1 call. Endpoint/space/api/bitable/{token}/automation/listtrả 183 workflow đầy đủ. Thêm section 12 — Cổng 4 verify.
12. 🏆 CỔNG 4 — Internal API (verify thực tế S177)
Cách verify
Dùng Claude in Chrome: login Lark web → mở Base 88 → đọc read_network_requests → extract URL pattern → fetch lại bằng JavaScript với credentials: include (cookie session) → decode gzip+base64 → parse JSON.
Pattern URL quan trọng: /space/api/v1/bitable/... (có v1) và /space/api/bitable/... (không v1). 2 namespace khác nhau. Không theo pattern public REST (/open-apis/bitable/v1/...).
Auth: Cookie session của user đã login. KHÔNG cần tenant token, KHÔNG cần OAuth.
Endpoint 1 — clientvars (nguồn vàng cho schema + views + records)
URL: GET /space/api/v1/bitable/{app_token}/clientvars?tableID={table_id}&viewID=&recordLimit=200&ondemandLimit=200&needBase=true&viewLazyLoad=true&ondemandVer=2&noMissCS=true&optimizationFlag=1&removeFmlExtra=true
Kích thước: ~714 KB cho 1 bảng 234 field, 262 record. Gấp nhiều lần public REST.
Response structure {code, msg, data}. data có 19 key:
| Key | Encoding | Nội dung verify | Public REST có? |
|---|---|---|---|
type |
string | "CLIENT_VARS" | — |
syncTableIds |
array | 🔥 Danh sách table_id của mọi Sync Table trong Base — verify trên Base 6.5 trả ["tbleCjZ4ljyjbCtW", "tblNmU8SpHO7jEFE", ...] |
❌ |
base |
gzip+base64 string | Decoded: {id, token, name, rev, owner, subType, schemaVersion, objType, timezone, blocks (80 table_id), blockInfos, trashBlockMap, trashTableMap, cs, userApplyRoles, isBaseManager, baseRoleMap, limit, accessConfig, calcMode, advPermInfo, isProBase, baseEngineEnabled, baseRecordsNum, permissionMap, marketplaceInfo} |
⚠️ Chỉ có name, revision, is_advanced, time_zone |
table |
gzip+base64 string | Decoded ~5.3 MB: {meta, views (37), viewMap, commentMap, resourceMap, fieldMap (234), fieldGroups, currentView, groupList, viewGroupNum, viewRecordNum, recordCount, recordMeta, formulaInfo (compiled code), recordMap (full data), rankInfo, cs, latestCSRev (6996), deniedRecords, tablePerm, milestoneMap, userMap, exInfo, primaryKey, useNewGroup, tableLimit} |
❌ Hoàn toàn |
permissions |
array | [1, 4] — permission level flags |
— |
users |
obj | User info cache | — |
timestamp |
num | Load time | — |
timeZone |
string | Timezone | — |
currentView |
string | view_id hiện tại | — |
Endpoint 2 — automation/list (nguồn vàng cho logic nghiệp vụ)
URL: GET /space/api/bitable/{app_token}/automation/list
Base 88: 183 workflow (154 enabled + 29 disabled) trong 1 call, total 2.18 MB.
Mỗi workflow có 12 field:
| Field | Nội dung |
|---|---|
workflowID |
id |
status |
0=disabled, 1=enabled |
draft |
🔥🔥🔥 TOÀN BỘ LOGIC (steps tree với trigger/condition/action) |
nodeSchema |
Visual graph layout (firstTaskStepId, relations, triggerStepId) |
extra |
Ref → real mapping (TableMap, FieldMap, BlockMap, BtnTrigger) |
source, accessMode, baseID |
Metadata |
createUID, updateUID, createTime, updateTime |
Audit |
Cấu trúc draft.steps[] — Logic workflow
{
"id": "trigaWWoBFoM",
"type": "SetRecordTrigger",
"data": {
"tableId": "ref_tblXXXX",
"recordType": "All",
"filterInfo": null,
"fields": [{
"fieldId": "ref_tblXXXX_fldYYYY",
"fieldType": 7,
"operator": "is",
"value": [true]
}],
"triggerControlList": []
},
"next": [{
"ids": ["actDQmf5dCB"],
"condition": {
"conjunction": "or",
"conditions": [
{"conjunction": "and", "conditions": [{
"fieldId": "ref_...",
"fieldType": 3,
"operator": "is",
"value": ["optPeZ2OH4"],
"conditionId": "conw4ea41h"
}]}
]
}
}]
}
Step type đã thấy: SetRecordTrigger (trigger khi record thỏa field), SetRecordAction (update record ở bảng khác).
Cross-step reference: trong action values có thể ref về step trước qua {stepId, stepType, type: "ref"} — workflow có thể chuyền giá trị.
Ref aliasing: tất cả tableId, fieldId trong draft dùng prefix ref_ (vd ref_tblXXXX_fldYYYY). extra.TableMap và extra.FieldMap giải alias về id thật. Lý do: Lark support copy/paste workflow giữa các Base — ref giúp remap tự động.
Endpoint 3 — automation/configs (metadata global)
URL: GET /space/api/bitable/{app_token}/automation/configs
Top keys của configs: {supportAutoNumber, events, template, formulaTrigger, limits, baseURL, workflowLimitNum, triggerLimitNum, executionTimes, isAdvancedTenant, stepConfig, webhookRegexp, isPrivate, apis, ...}
events— định nghĩa các event type có thể làm triggertemplate— workflow templates có sẵnstepConfig— cấu hình step typesapis— danh sách API có thể gọi từ actionformulaTrigger— formula trigger configworkflowLimitNum,triggerLimitNum,executionTimes— giới hạn plan
Matrix cập nhật — GPT đã SAI chỗ nào
| Claim của GPT | Verify thực tế | Status |
|---|---|---|
| Workflow dump full logic = bắt buộc Computer Use/Network Intercept | ❌ Sai. Internal API /automation/list trả đủ. Network Intercept đủ, không cần Computer Use. |
GPT SAI |
Workflow chỉ có {workflow_id, title, status} |
❌ Sai. Có 12 field bao gồm draft, nodeSchema, extra. |
GPT SAI |
Sync Table detect phải quét is_synced từng field |
❌ Sai. clientvars.data.syncTableIds trả array tất cả sync table trong Base với 1 call. |
GPT SAI (và S176 của em cũng chưa biết) |
| Sync source mapping không có API | ⚠️ Verify một phần: KHÔNG có trong clientvars. Phải click mở "Sync Setup" UI để bắt request khác. Chưa test. |
⏳ |
App meta: trả {name, revision, is_advanced, time_zone} |
⚠️ Đúng nhưng nghèo. Internal base trả ~30 field bao gồm permissionMap, advPermInfo, baseRoleMap, trashBlockMap, ... |
GPT THIẾU |
| View property detail chưa rõ | ⚠️ Verify: có trong clientvars.table.viewMap + clientvars.table.views đầy đủ 37 view. |
GPT THIẾU |
| Stage field type 24 không có API | ⏳ Chưa test xem clientvars.table.fieldMap có trả stage options không |
⏳ |
Matrix cập nhật — những gì Cổng 4 đã PHÁ VỠ
| Trước S177 (nghĩ rằng chỉ Computer Use) | Sau S177 (verify Cổng 4) |
|---|---|
| Workflow logic: Computer Use | ✅ Internal API /automation/list |
| Views config: public REST (shallow) | ✅ clientvars.table.viewMap (đầy đủ) |
| Formula compiled code: không có | ✅ clientvars.table.formulaInfo |
| Record rank ordering: không có | ✅ clientvars.table.rankInfo |
| Change stream (revision history): không có | ✅ clientvars.table.cs + latestCSRev |
| Permission map chi tiết: không có | ✅ base.permissionMap + advPermInfo |
| Bảng đã xóa: không có | ✅ base.trashBlockMap |
| Tổng số record toàn Base: không có | ✅ base.baseRecordsNum |
| Sync Table registry: phải quét | ✅ clientvars.syncTableIds |
Còn điểm mù nào sau Cổng 4
- Sync source mapping (source_app_token, field mapping): chưa thấy trong
clientvars. Cần click mở "Sync Setup" UI để bắt endpoint riêng. - Stage field options (type 24): cần decode
clientvars.table.fieldMapxem có property đầy đủ không. - Run history automation:
/automation/listchỉ có definition, không có execution log. Cần endpoint khác.
Chiến lược Phase 4 dump (cập nhật)
Dump tool Phase 4 nên dùng Cổng 4 làm nguồn chính, bổ sung Cổng 1/2:
- Cổng 4 →
clientvarsmỗi bảng → schema + views + records + formula + permissions + rankInfo - Cổng 4 →
automation/listmỗi Base → 1 call lấy tất cả workflow - Cổng 4 →
automation/configsmỗi Base → event types, step configs, limits - Cổng 1/2 (public REST) → verify + fill-in khi internal API fail
- Cổng 5 (Computer Use) → chỉ Sync Setup UI (nếu Cổng 4 không tìm được endpoint) + Stage options (nếu
fieldMapkhông trả)
Chi phí: 299 bảng × 1 call clientvars + 18 Base × 1 call automation/list = ~320 calls, ~5 phút. Rẻ gấp ~10 lần dump qua public REST.
Rủi ro: Internal API có thể đổi schema bất kỳ lúc nào. → Toolkit Phase 4 phải có fallback sang Cổng 1/2 + test regression định kỳ.
10. 🚪 5 Cổng vào Lark — Triết lý kết nối
Bài học S177
Em suýt kết luận "Lark không cho đọc qua API" chỉ dựa vào 1 cổng duy nhất (MCP wrapper 9 tool). Đây là sai về nguyên tắc: giới hạn công cụ ≠ giới hạn nền tảng. Trước khi kết luận "không làm được", phải xác định đang đứng ở cổng nào và thử tất cả cổng public đã có.
5 cổng kết nối — xếp theo chi phí tăng dần
| # | Cổng | Chi phí | Độ bao phủ | Trạng thái Incomex | Dùng khi nào |
|---|---|---|---|---|---|
| 1 | MCP wrapper (lark-mcp tool hiện tại) | Rất rẻ, tích hợp Desktop | 9 endpoint bitable cơ bản | ✅ Đang dùng | Tác vụ nhanh, ít endpoint, cần integrate Desktop agent |
| 2 | REST direct (curl/Python + tenant_access_token) | Rẻ, chính thức | Public OpenAPI (~30-50 endpoint) | ⏳ Prompt Claude Code đã soạn S177 | Dump production public API |
| 3 | MCP wrapper mở rộng (build thêm tool wrap REST) | Trung bình | = Cổng 2, agent gọi tiện hơn | ❌ Chưa làm | Khi cần gọi nhiều lần trong agent workflow |
| 4 | Network Intercept internal (Chrome devtools + JS fetch cookie session) | Rẻ-trung bình | 🔥 VƯỢT XA public API — internal endpoint | ✅ VERIFIED S177 — JACKPOT | Vượt tường public API, dump automation/view/record dense |
| 5 | Computer Use UI (screenshot/OCR/click) | Đắt nhất, chậm | Phủ 100% | ❌ Chưa dùng | Phương án cuối |
Nguyên tắc áp dụng
- Thử cổng 1-4 trước, cổng 5 cuối.
- Cổng 4 có thể phá tường — S177 verify được nhiều thứ GPT nói "public REST không có".
- Mỗi cổng ghi rõ — để tái lập.
10bis. 🎯 Cổng 4 — Internal API đã verify (S177 Phase Chrome intercept)
Auth model
- Cookie session của user đã login Lark web — KHÔNG cần tenant_access_token
- JS fetch từ cùng origin với
credentials: 'include'là đủ - Domain format:
https://{subdomain}.sg.larksuite.com(vdrj3ntrrntcx.sg.larksuite.com)
URL pattern
QUAN TRỌNG: Internal API KHÔNG theo pattern /open-apis/bitable/v1/. Có 2 path prefix:
/space/api/v1/bitable/{app_token}/...(clientvars, records)/space/api/bitable/{app_token}/...(automation — không có/v1/)
Đoán sai pattern → 404. Phải bắt từ Network tab của UI thật.
Encoding — response nén
- Các field lớn (
base,table) trongclientvarslà gzip + base64 encoded string - Decode:
atob(str)→Uint8Array→DecompressionStream('gzip')→JSON.parse - String bắt đầu bằng
"H4sI"là magic gzip base64
Endpoint 1: GET /space/api/v1/bitable/{token}/clientvars?tableID=X&needBase=true&viewLazyLoad=true&ondemandVer=2
Status: 200 OK. Response ~714 KB cho 1 bảng (bảng 234 field, 262 record).
Top-level keys: {code, msg, data}. data chứa 19 keys:
| Key | Nội dung | Public REST có? |
|---|---|---|
base (gzip+b64) |
App meta đầy đủ (decode bên dưới) | ❌ |
table (gzip+b64) |
Schema + 37 views + records + formula + ... (decode bên dưới) | ⚠️ một phần |
syncTableIds |
Array [table_id, ...] — DANH SÁCH SYNC TABLES của Base |
❌ |
permissions |
Array permission flags | ❌ |
users |
User list | ❌ |
calendar |
Calendar integration | — |
currentView |
View ID đang mở | — |
timeZone |
Base timezone | ⚠️ |
fallback, loadingType, downgradeConfig, calcDowngrade, encoding, costInfo, extraInfo, roomMembers, timestamp |
Meta/config UI | — |
base decoded (gzip → JSON) — 34 keys
id, token, name, rev, owner, subType, schemaVersion, objType, timezone,
archiveEnabled, bizType, exType, rankInfo, blocks (= table_id array),
blockInfos, trashBlockMap, trashTableMap, cs, userApplyRoles, isBaseManager,
baseRoleMap, limit, accessConfig, calcMode, advPermInfo, isProBase,
baseEngineEnabled, baseRecordsNum, permissionMap, marketplaceInfo, ...
Những thứ public REST KHÔNG có:
trashBlockMap,trashTableMap— bảng đã xóa (khôi phục!)baseRecordsNum— tổng record toàn BasebaseRoleMap,permissionMap,advPermInfo— quyền chi tiết từng roleisBaseManager— user hiện tại có phải admincalcMode,isProBase,baseEngineEnabled— feature flags
table decoded (gzip → JSON) — 32 keys, 5.3 MB cho 1 bảng
meta, views, viewMap, commentMap, resourceMap, fieldMap, fieldGroups,
currentView, groupList, viewGroupNum, viewRecordNum, recordCount,
recordMeta, formulaInfo, recordMap, rankInfo, cs, latestCSRev,
deniedRecords, tablePerm, milestoneMap, schemaVersion, userMap,
exInfo, primaryKey, useNewGroup, recordPage, weakErrCode, tableLimit,
enableViewCache
Những thứ HOÀN TOÀN MỚI so với public REST:
views(array 37 items!) +viewMap— view config ĐẦY ĐỦ per viewformulaInfo={code, proxyField, fieldMap, calcCompletionTime, fieldBusTypeMap}— formula compiled coderankInfo={nextRank, rankMap, viewRankMap}— record ordering custom per viewgroupList=[{by, recordIDList, firstRecordOffset, groupRecordNum}]— group config per viewresourceMap={attachments, reminders}— resource cachecommentMap,milestoneMap— comments + milestonescs+latestCSRev: 6996— change stream revision (lịch sử edit)deniedRecords— record-level permission denialtableLimit,tablePerm— size + permission flagsuserMap— cache user infoprimaryKey— field_id là primary keyrecordMap— tất cả records (vớirecordLimit=200,ondemandLimit=200)meta.jointRev— nghi ngờ là revision của bảng SOURCE khi Sync Table sync gần nhất. Base 6.5 HT Nghiệp đoàn:rev: 17, jointRev: 2603→ bảng gốc đã có 2603 revision, bảng sync mới cập nhật lần 17.
✅ Sync Table detection — 1 call thay cho 299 call
Trước S177: quét is_synced trên từng field của 299 bảng = 299 API call.
Với clientvars.data.syncTableIds: 1 call cho mỗi Base → lấy list tất cả Sync Table. 18 Base = 18 API call (giảm 94% chi phí).
Ví dụ Base 6.5: syncTableIds = ["tbleCjZ4ljyjbCtW","tblNmU8SpHO7jEFE","tbleUZ7DzlCngTS3","tblZSRpjSTH60kmS"] — 4 bảng Sync lộ nguyên.
Endpoint 2: GET /space/api/v1/bitable/{token}/records?tableId=X&viewId=Y&limit=3000&...
Status: 200 OK. Limit 3000 records/call — gấp 6 lần public REST (500).
Endpoint 3: GET /space/api/bitable/{token}/automation/list 🔥🔥🔥
GPT report nói "chỉ list + status, public REST không có detail". SAI. Status: 200 OK. Response 2.1 MB cho Base 88, 183 workflows.
Mỗi workflow object có keys:
workflowID, baseID, extra, createUID, updateUID,
nodeSchema, source, status, draft, createTime, updateTime, accessMode
nodeSchema object (DAG graph):
triggerStepId— step ID của trigger nodefirstTaskStepId— step ID của task đầu tiênversion— schema versionrelations— object map{step_id: [{id, type, isInvisible}]}— edges từ node tới node
extra (JSON string) chứa dependency manifest:
TableMap: {ref_tableId: {TableID, FieldMap, ViewMap}}— workflow đụng bảng nàoBlockMap— block dependenciesBtnTrigger— button trigger config (nếu có)- Workflow mẫu:
TableMap_count: 3, FieldMap_count: 89— 1 workflow đụng 3 bảng, 89 field.
draft (JSON string, ~37 KB/workflow) chứa FULL TRIGGER + ACTION CONFIG:
title— tên workflowtableFieldMap— map field dùng trong workflowsteps— array các step, mỗi step có:id— step IDtype— loại trigger/action (vdChangeRecordTrigger,AddRecordAction, ...)data— cấu hình đầy đủ- Với trigger:
tableId,fields: [{fieldId, fieldType, operator, value, conditionId}],triggerControlList - Với action:
tableId,values: [{fieldId, valueType, value}]
- Với trigger:
stepTitle— tên step (user đặt)next: [{ids, condition: {conjunction, conditions: [...]}}]— edge tới step kế + condition tree
Workflow mẫu đã parse đầy đủ (S177):
Title: "016.2 Điền 'Đã xuất cảnh' cho TTS"
Trigger: ChangeRecordTrigger
tableId: tblKnzaih6154r2e (TTS Thông tin)
condition: field fldDV8En4l (SingleSelect) isNotEmpty
triggerControlList: [pasteUpdate, automationBatchUpdate, appendImport, openAPIBatchUpdate]
Action: AddRecordAction
tableId: tbl2GXNdArZ7zoAR (TTS Sau XC)
values: ref từ trigger step (valueType=ref)
Endpoint 4: GET /space/api/bitable/{token}/automation/configs
Status: 200 OK. Response 16 KB.
data.configs chứa:
apis— API schema cho automation operationsexecutionTimes— giới hạn execution timeanycross— integration configbaseURL— base URLsupportAutoNumber— feature flagslimits— rate limits, size limitstemplate— workflow templates có sẵnstepConfig— SCHEMA ĐẦY ĐỦ các loại trigger/action có sẵn (giải mã type enum)events— event types
🎯 Kết luận Cổng 4 — Automation dump TRỌN VẸN
| Câu hỏi | GPT nói | S177 verify |
|---|---|---|
| Có endpoint trả trigger_type? | ❌ Không có | ✅ draft.steps[].type |
| Có endpoint trả danh sách actions? | ❌ Không có | ✅ draft.steps[] với type Action |
| Có endpoint trả condition tree? | ❌ Không có | ✅ draft.steps[].data.fields + next[].condition |
| Có endpoint trả run history? | ❌ Không có | ⏳ Chưa test (có thể có /automation/runs) |
| Có endpoint trả script source? | ❌ Không có | ⏳ Chưa gặp workflow Run Script |
| Phải Computer Use cho dump logic? | ✅ | ❌ KHÔNG CẦN — Cổng 4 đủ |
Endpoint 5+: Còn cần test (S178)
/automation/runshoặc/automation/{id}/history— run history/tables/{id}/sync_config— Sync source mapping (vẫn là điểm mù)- Stage field options — có thể nằm trong
viewsconfig
Pattern tổng quát để khám phá Cổng 4
- Clear Network tab
- Click UI action cần biết (tạo view, mở sync setup, edit automation...)
read_network_requestslọc/api/- Replay request bằng JS fetch cookie session
- Parse JSON response
📌 So sánh với Cổng 2 (S178 update)
Sau khi Cổng 2 được verify đầy đủ S178, ranh giới giữa 2 cổng đã rõ:
- Cổng 2 độc quyền 3 loại: role permission matrix (278KB), form question tree, member user_id
- Cổng 4 độc quyền 7 loại: automation full, syncTableIds, formula compiled code, trashBlockMap, viewRankMap, change stream, deniedRecords
- Phase 4 dump dùng CẢ HAI — không thay thế nhau. Xem Section 10ter cho matrix đầy đủ.
10ter. 🎯 Cổng 2 — REST direct verified (S178 GĐ1)
Tóm 1 dòng
46 endpoint public REST — 15 READ / 31 WRITE. GĐ1 verify 15/15 READ = 100% PASS. Toolkit Python 645 LOC production-ready tại tools/lark_probe/. Demo dump Base 88: 567 call, 256s, 10.7 MB JSON.
Auth model
- Token:
tenant_access_tokenquaPOST /open-apis/auth/v3/tenant_access_token/internalvới{app_id, app_secret}từ GSM secretlark-app-id+lark-app-secrettrong projectgithub-chatgpt-ggcloud - Pattern retry: reactive 3 lần khi gặp 401/403 → clear cache → fetch lại → retry. KHÔNG dùng proactive timer (server có thể revoke bất kỳ lúc nào, client không đo giờ chính xác được)
- Token scope: in-memory singleton, không persist ra file/DB
- Retry code đã phát hiện: lark_code 99991661/63/68 = token expired → handle tự động trong LarkClient
Matrix 15/15 READ — verified S178
| # | Endpoint | HTTP | Status | Bất ngờ so với docs |
|---|---|---|---|---|
| 1 | /apps/{token} App Get |
GET | ✅ | 3 field bonus: advance_version, formula_type, app_token |
| 2 | /apps/{token}/tables Table List |
GET | ✅ | Khớp docs |
| 3 | /tables/{id}/fields Field List |
GET | ✅ | Khớp docs (đã verify S176/S177) |
| 4 | /tables/{id}/records Record List (deprecated) |
GET | ✅ | 12× chậm hơn Search — undocumented |
| 5 | /tables/{id}/records/{record_id} Record Get |
GET | ✅ | Khớp docs |
| 6 | /tables/{id}/records/search Record Search |
POST | ✅ | Khớp docs — best read path |
| 7 | /tables/{id}/records/batch_get Record BatchGet |
POST | ✅ | CN-only docs, EN docs thiếu nhưng endpoint work bình thường |
| 8 | /tables/{id}/views View List |
GET | ✅ | Khớp docs |
| 9 | /views/{view_id} View Get |
GET | ✅ | property tree có {filter_info, hidden_fields, hierarchy_config} — 1 View Get lộ 54 field IDs hidden |
| 10 | /tables/{id}/forms/{form_id} Form Get |
GET | ✅ | 2 field bonus: shared_limit, submit_limit_once |
| 11 | /forms/{id}/fields Form Field List |
GET | ✅ | rich_description — question tree đầy đủ exposed |
| 12 | /apps/{token}/dashboards Dashboard List |
GET | ✅ | Endpoint work, Base 88 = 0 dashboards |
| 13 | /apps/{token}/workflows Workflow List |
GET | ✅ | Shallow confirmed: chỉ {workflow_id, title, status}. Full logic phải Cổng 4. |
| 14 | /apps/{token}/roles Role List |
GET | ✅ | 🔥 278 KB! table_roles[] = full permission matrix per role per table. Docs chỉ nói {role_id, role_name}. page_size max = 30 (undocumented). |
| 15 | /apps/{token}/members Member List |
GET | ✅ | 2 field bonus: user_id, member_en_name |
3 giá trị độc quyền Cổng 2 (Cổng 4 CHƯA phủ)
- Role permission matrix 278KB — từng role có quyền gì trên từng bảng, từng view, từng field. Cổng 4 chỉ có
baseRoleMapshallow. - Form question tree —
rich_description, layout, validation,shared_limit,submit_limit_once. Cổng 4 chưa test tới. - Member
user_id— Cổng 4 chưa phủ member API.
2 undocumented quirks
- Role List
page_sizemax = 30 — set > 30 Lark trả error. Docs im lặng. - Record List 12× chậm hơn Record Search — deprecated có lý do. Phase 4 luôn dùng
/records/search.
7 loại độc quyền Cổng 4 (Cổng 2 KHÔNG có)
Giữ nguyên từ Section 10bis — Cổng 2 KHÔNG thay thế được Cổng 4 cho: automation full logic, syncTableIds, formula compiled code, trashBlockMap, viewRankMap (record ordering), change stream + latestCSRev, deniedRecords (record-level permission).
Toolkit tools/lark_probe/
client.py (137 LOC) LarkClient — GSM auth, reactive retry 3 lần, rate limit
probe.py (288 LOC) 12 probe_*() functions, auto-pagination, max_records cap
dump.py (152 LOC) dump_base() orchestrator
demo_dump_base88.py (55) demo script, env DUMP_MAX_RECORDS
README.md (70) usage docs
lark-rest-read-matrix.md P6 final matrix
Quickstart:
from tools.lark_probe import LarkClient, dump_base
client = LarkClient() # tự đọc GSM
result = dump_base(client, "YSIkb8PxOaNaozs2vwalOOcagkf", max_records_per_table=50)
Demo dump Base 88 thực tế
- 567 API calls, 256 giây, output 10.7 MB JSON
- Capture được: 80 tables, 3674 fields, 1791 records (capped 50/table), 288 views, 6 forms, 183 workflows (shallow), 12 roles (full matrix)
- Bottleneck phát hiện: Bảng "Kế hoạch công việc" có ~424K records — Phase 4 full dump cần strategy riêng (sampling / incremental / Cổng 4 3000-per-call)
Chiến lược Phase 4 dump (cập nhật S178)
| Option | Mô tả | Khi nào |
|---|---|---|
| A | Cap max_records=50 schema + sample |
First pass, xây mô hình |
| B | Incremental — dump record_ids trước, full records batch overnight | Production, bảng lớn |
| C | Dùng Cổng 4 /records (3000/call) thay public REST (500/call) |
Bảng > 10K records |
Khuyến nghị: A cho lần đầu, B+C cho production. Schema-only dump (không records) = ~3,546 calls / ~3 phút toàn bộ 18 Base.
So sánh Cổng 2 vs Cổng 4 — cả 2 đều cần cho Phase 4
| Tiêu chí | Cổng 2 (REST) | Cổng 4 (internal) |
|---|---|---|
| Headless (cron 24/7)? | ✅ | ❌ (cần Chrome + user login) |
| Auth ổn định? | ✅ token tenant | ⚠️ cookie session user |
| Schema field/table/view baseline | ✅ | ✅ (richer) |
| Automation full logic | ❌ shallow | ✅ ONLY |
| Sync Table registry | ❌ | ✅ ONLY |
| Formula compiled code | ❌ | ✅ ONLY |
| Role permission matrix | ✅ ONLY (278KB) | ❌ (shallow) |
| Form question tree | ✅ ONLY | ❌ (chưa test) |
| Member user_id | ✅ ONLY | ❌ |
| Records throughput | 500/call | 3000/call |
| Rủi ro schema đổi | Thấp (public REST ổn định) | Cao (internal có thể đổi bất kỳ lúc) |
Kết luận chiến lược: Trước S178 em nghĩ Cổng 2 = backup của Cổng 4. Sau S178: Cổng 2 là cổng chính vì headless + có 3 loại độc quyền. Cổng 4 vẫn không thể thay thế cho 7 loại internal. Phase 4 dump production dùng cả 2 song song.
Nguồn
knowledge/dev/lark/cong2-p6-final-report— báo cáo chi tiết 6 phase P0→P6knowledge/dev/lark/lark-rest-read-matrix— matrix 46 endpoint từ 4 nguồn đồng thuậntools/lark_probe/— toolkit production-ready (local VPS)probes/01-15_*.json+p4-compare-report.md— raw probe JSON
11. Endpoint Matrix từ GPT Deep Research report (S177) — ✅ S178 VERIFIED 15/15 READ
Cập nhật S178 (2026-04-11): Toàn bộ 15 READ endpoint đã được verify qua Cổng 2 (tenant_access_token REST direct). 15/15 PASS. Xem Section 10ter để thấy matrix verified đầy đủ + 5 phát hiện trái docs + 2 undocumented quirks. Các row dưới đây giữ nguyên làm reference lịch sử của GPT claim — KHÔNG sửa ⏳ từng row để tránh mất ngữ cảnh, thay vào đó Section 10ter là SSOT mới cho trạng thái verify.
Cảnh báo gốc (S177): Matrix dưới đây dựa trên GPT đọc docs public. Docs Lark có thể lạc hậu, thiếu, hoặc GPT hiểu sai. S178 đã chứng minh docs nói thiếu: 5/15 endpoint trả nhiều field hơn docs nói (App Get 3 field bonus, Form Get 2 bonus, Form Fields rich_description, Role List 278KB permission matrix, Member List 2 bonus). Luôn test thực tế, không tin docs.
App / Metadata
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /apps/:app_token |
✅ FULL — trả {name, revision, is_advanced, time_zone} |
⏳ | Mới lạ — dump chưa lấy. Verify ưu tiên cao. |
| PUT | /apps/:app_token |
⚠️ Update only | ⏳ | WRITE, không phải dump |
| POST | /apps/:app_token/copy |
⚠️ Clone | ⏳ | WRITE |
Table
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /apps/:token/tables |
⚠️ PARTIAL — {table_id, name, revision} (không có sync source) |
✅ S176/S177 | Khớp — đã verify |
| POST | /apps/:token/tables |
WRITE — create 1 | ⏳ | — |
| POST | /tables/batch_create |
WRITE — batch | ⏳ | Mới biết — có batch |
| DELETE | /tables/:id |
WRITE | ⏳ | — |
| POST | /tables/batch_delete |
WRITE — batch | ⏳ | — |
Field
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /tables/:id/fields |
✅ FULL — property + options + description | ✅ S176/S177 | Khớp, đã verify |
| POST | /fields |
WRITE | ⏳ | Có — phase WRITE test |
| PUT | /fields/:id |
WRITE | ⏳ | — |
| DELETE | /fields/:id |
WRITE | ⏳ | — |
| POST | /field_groups |
WRITE — chỉ UI group, không thay logic | ⏳ | Mới biết có |
Record
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /tables/:id/records |
✅ FULL — old list | ⏳ | — |
| POST | /tables/:id/records/search |
✅ FULL — best read path, paging | ⏳ | Có tool MCP |
| GET | /records/:record_id |
✅ FULL — 1 record | ⏳ | Mới biết có endpoint get 1 |
| POST | /records/batch_get |
✅ FULL — up to 100 | ⏳ | Mới biết — hữu ích dump deterministic |
| CRUD các loại | WRITE | ⏳ |
View — 🆕 Mới so với dump Phase 3
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /tables/:id/views |
⚠️ PARTIAL — {view_id, view_name, view_type, view_public_level} |
⏳ | CAO — lộ filter/sort/layout |
| GET | /views/:view_id |
⚠️ PARTIAL — {view_id, view_name, view_type, property} — property tree chưa lộ hết |
⏳ | CAO — test xem property thật sự trả gì |
| POST/PATCH/DELETE | WRITE | ⏳ | — |
Form — 🆕
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /tables/:id/forms/:form_id |
⚠️ PARTIAL — {name, description, shared, shared_url} — chưa thấy question tree |
⏳ | CAO — test xem câu hỏi/layout có lộ không |
| PATCH | /forms/:id |
WRITE | ⏳ | — |
Dashboard — 🆕
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /apps/:token/dashboards |
❌ SHALLOW — chỉ list | ⏳ | Verify — có thể có detail endpoint chưa docs |
Workflow / Automation — ⚠️ VÙNG TỐI
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /apps/:token/workflows |
❌ SHALLOW — {workflow_id, title, status} |
⏳ | Verify. Thử cả endpoint v2/beta nếu có. |
| PUT | /workflows/:id |
enable/disable only | ⏳ | — |
Kết luận GPT: Dump full logic workflow (trigger tree, actions, conditions, script, run history) không có public REST. Bắt buộc Network Intercept (Cổng 4) hoặc Computer Use (Cổng 5).
Em phải kiểm chứng điều này: thử gọi các endpoint chi tiết có thể có (/workflows/:id, /workflows/:id/actions, /workflows/:id/nodes, v2 routes) — GPT chưa chắc đã tìm hết.
Role / Advanced Permissions — 🆕
| Method | URL | GPT claim | Verified? | Ghi chú |
|---|---|---|---|---|
| GET | /apps/:token/roles |
⚠️ PARTIAL — {role_id, role_name} |
⏳ | Permission set detail chưa rõ |
| POST/PUT/DELETE | /roles/* |
WRITE | ⏳ | — |
| GET | /roles/:id/members |
✅ FULL — {member_type, name, open_id, union_id} |
⏳ | Verify |
| POST/DELETE | /roles/:id/members* |
WRITE | ⏳ | — |
Sync Table — ❌ VÙNG TỐI TUYỆT ĐỐI (theo GPT)
GPT không tìm thấy public endpoint trả: source_app_token, source_table_id, field mapping, schedule, last_sync_time, sync events. → Bắt buộc Cổng 4 hoặc 5.
Nhưng cần verify: GPT có thể bỏ sót. Thử các endpoint ẩn: /apps/:token/sync_tables, /tables/:id/sync_info, /sync_sources, v2 routes, internal URL mà UI gọi.
Field type 24 Stage — ❌ VÙNG TỐI
GPT xác nhận type 24 tồn tại trong docs nhưng không thấy endpoint trả stage options. Khớp với quan sát Incomex (property: null). → Bắt buộc Cổng 4/5.
File / Attachment / Export / Events — 🆕
drive-v1/export_task/create— export Base sang xlsx/csv. Hữu ích cho backup, không phải dump object graph.- Bitable change events (docs events subscription) — change feed, không phải historical dump.
Tổng kết GPT report
- REST public đọc được ~70-80% schema tĩnh (tables/fields/records/views/forms/roles/app meta)
- Workflow logic, Sync source, Stage config = điểm mù — phải Cổng 4/5