KB-49E1 rev 29

Lark Base - Cơ chế liên kết dữ liệu (4 cơ chế)

82 min read Revision 29
larkbitablemechanismss176permanent-referenceincomex

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_synced field 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 FULLdraft.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.

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

  1. Gọi /tables lấy danh sách table_id.
  2. Với mỗi bảng, gọi /fields.
  3. Lọc field có type ∈ {18, 21}.
  4. 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:

  1. Detect qua API (rẻ, tự động): Quét 299 bảng, gọi /fields page_size=1, check is_synced field đầu. → danh sách ứng viên Sync.
  2. 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.
  3. 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 text
  • CurrentValue.$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:

  1. Lặp qua mọi field trong mọi bảng.
  2. Với field type=19 hoặc type=20: parse formula / formula_expression, trích xuất mọi cặp (table_id, field_id) tham chiếu.
  3. Tạo edge: bảng hiện tại → (target_table, target_field).
  4. 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ứa steps[] 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_matched
  • button_clicked (field type 3001)
  • scheduled (cron)
  • webhook_received / feishu_message_received / email_received
  • manual

Các loại action (theo báo cáo)

  • create_record / update_record / delete_record
  • send_notification / send_email
  • call_webhook / call_openapi
  • run_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_searchlark-mcp chỉ expose 9 tool bitable:

  1. bitable_v1_app_create (WRITE)
  2. bitable_v1_appTable_create (WRITE)
  3. bitable_v1_appTable_list (READ)
  4. bitable_v1_appTableField_list (READ)
  5. bitable_v1_appTableRecord_create (WRITE)
  6. bitable_v1_appTableRecord_batchCreate (WRITE)
  7. bitable_v1_appTableRecord_update (WRITE)
  8. bitable_v1_appTableRecord_batchUpdate (WRITE)
  9. 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)

  1. Phase 4A — Lookup/Formula graph:
    • Gọi /fields toàn bộ 299 bảng với capture đầy đủ property (Lookup type 19 + Formula type 20)
    • Parse formula DSL regex tìm bitable::$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)
  1. 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 clientvars 18 lần/18 Base → lấy syncTableIds
    • 18 API call thay vì ~299
  2. Phase 4C — Automation full dump (breakthrough S177):
    • Gọi automation/list 18 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
  3. Phase 4D — View + Formula compiled code:
    • Từ clientvars.table.viewsclientvars.table.formulaInfo
    • Capture 37 views/bảng × 299 bảng ≈ dump view config toàn hệ thống

Điểm mù còn lại (bắt buộc S178 test)

  1. 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
  2. Automation run history + Script action source:
    • Chưa test endpoint /automation/runs hay tương tự
  3. 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

  1. 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

  1. is_synced field meaningDONE S176
  2. is_extend fieldDONE S177: Incomex không dùng. Bỏ qua.
  3. roll_up flagDONE S177: 0=None, 1=SUM, 2=COUNT, 4=MAX. Parse thẳng DSL tốt hơn.
  4. Field type 24 StageDONE S177: property: null qua API. Cần Computer Use.
  5. Field type 3001 ButtonDONE S177: Incomex không có, dùng Checkbox type 7 làm trigger.
  6. 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.
  7. 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.
  8. App meta: GET /apps/{token} — có trả owner, timezone, cipher? Chưa test.
  9. Roles & Permissions API: Lark có — chưa verify. Có thể lộ ai được phép làm gì trên Base.
  10. Dashboards API: Có thể trả config dashboard charts.
  11. Forms API: Bảng có form input từ người ngoài — form metadata.
  12. 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"fldNl87vnX "Kinh nghiệm yêu cầu"), cả 2 đều có name "Không". Nghĩa là:

  • Option id chỉ 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"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-go v1.1.48 — xác nhận struct AppTable, 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 + /fields bảng TTS
    • Base 88 YSIkb8PxOaNaozs2vwalOOcagkf: /fields trê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: /fields trên HT-Nghiệp đoàn (verify is_synced: true)

Nguồn tham khảo (chưa fetch được — JS-rendered)

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 sai
  • knowledge/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

  1. KHÔNG sửa dump cũ snapshots/2026-04-11/ — giữ lịch sử đối chứng.
  2. 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.
  3. KHÔNG strip field trong response — dump nguyên văn. Phân tích để sau.
  4. 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
  5. 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ể.
  6. 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+)

  1. 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
  2. Cổng 2 dump production toàn bộ 18 Base (toolkit tools/lark_probe/, headless cron-able, ~3-5 phút schema-only)
  3. Cổng 4 dump bổ sungclientvars per table + automation/list per Base — cho 7 loại độc quyền (automation full, syncTableIds, formula code, trashBlockMap, viewRankMap, change stream, deniedRecords)
  4. Phân tích + vẽ cross-base/*.md — từ dump thật
  5. Anh Huyên duyệt tay bản thiết kế → mới sửa production (Cổng 2 GĐ2 WRITE)
  6. 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:

  1. Chỉ tick [x] khi LẤY XONG và verify đủ — không tick dự định
  2. Chỉ tick cổng thực tế đã lấy thành công, không tick cổng "có thể lấy"
  3. Gặp thông tin mới chưa có → thêm dòng ngay
  4. 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] S178x-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 [ ]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

  1. 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 blob H4sI), (b) quét clientvars bảng SOURCE, (c) C5 Computer Use cuối.
  2. C13 Stage options — C2+C4 đều property:null. Cần C5.
  3. C07 Link filter — C2 không trả. Cần C5.
  4. I09-I10 Run history + Script — chưa test Cổng 4.
  5. 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.disableSync cờ selective sync tick, (2) fieldMap[*].exInfo.synced field-level, (3) meta.jointRev đếm sync, (4) CSRF header x-csrftoken = cookie _csrf_token, (5) Fetch+XHR interceptor persistent, (6) Body format 2 endpoint mới views/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ừ clientvars cached, 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. clientvars trả 5.3 MB/bảng (schema+37 views+records+formula code+permission). automation/list trả 183 workflow với draft.steps FULL trigger+action+condition tree — GPT report SAI. Không cần Computer Use cho Automation dump. syncTableIds lộ list Sync trong 1 call. Path pattern internal /space/api/v1/bitable//space/api/bitable/. Auth = cookie session. Gzip+base64 encoding. Xem section 10bis.

  • v2 (S176, 2026-04-11): Verify thêm is_synced field 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}/clientvars trả schema + views + records + formula + permissions + change stream 1 call. Endpoint /space/api/bitable/{token}/automation/list trả 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.TableMapextra.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 trigger
  • template — workflow templates có sẵn
  • stepConfig — cấu hình step types
  • apis — danh sách API có thể gọi từ action
  • formulaTrigger — formula trigger config
  • workflowLimitNum, 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

  1. 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.
  2. Stage field options (type 24): cần decode clientvars.table.fieldMap xem có property đầy đủ không.
  3. Run history automation: /automation/list chỉ 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:

  1. Cổng 4clientvars mỗi bảng → schema + views + records + formula + permissions + rankInfo
  2. Cổng 4automation/list mỗi Base → 1 call lấy tất cả workflow
  3. Cổng 4automation/configs mỗi Base → event types, step configs, limits
  4. Cổng 1/2 (public REST) → verify + fill-in khi internal API fail
  5. Cổng 5 (Computer Use) → chỉ Sync Setup UI (nếu Cổng 4 không tìm được endpoint) + Stage options (nếu fieldMap khô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

  1. Thử cổng 1-4 trước, cổng 5 cuối.
  2. Cổng 4 có thể phá tường — S177 verify được nhiều thứ GPT nói "public REST không có".
  3. 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 (vd rj3ntrrntcx.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) trong clientvarsgzip + base64 encoded string
  • Decode: atob(str)Uint8ArrayDecompressionStream('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, trashTableMapbảng đã xóa (khôi phục!)
  • baseRecordsNum — tổng record toàn Base
  • baseRoleMap, permissionMap, advPermInfo — quyền chi tiết từng role
  • isBaseManager — user hiện tại có phải admin
  • calcMode, 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 view
  • formulaInfo = {code, proxyField, fieldMap, calcCompletionTime, fieldBusTypeMap}formula compiled code
  • rankInfo = {nextRank, rankMap, viewRankMap}record ordering custom per view
  • groupList = [{by, recordIDList, firstRecordOffset, groupRecordNum}] — group config per view
  • resourceMap = {attachments, reminders} — resource cache
  • commentMap, milestoneMap — comments + milestones
  • cs + latestCSRev: 6996 — change stream revision (lịch sử edit)
  • deniedRecords — record-level permission denial
  • tableLimit, tablePerm — size + permission flags
  • userMap — cache user info
  • primaryKey — field_id là primary key
  • recordMaptất cả records (với recordLimit=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 node
  • firstTaskStepId — step ID của task đầu tiên
  • version — schema version
  • relationsobject 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ào
  • BlockMap — block dependencies
  • BtnTrigger — 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 workflow
  • tableFieldMap — map field dùng trong workflow
  • stepsarray các step, mỗi step có:
    • id — step ID
    • typeloại trigger/action (vd ChangeRecordTrigger, 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}]
    • 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 operations
  • executionTimes — giới hạn execution time
  • anycross — integration config
  • baseURL — base URL
  • supportAutoNumber — feature flags
  • limits — rate limits, size limits
  • templateworkflow templates có sẵn
  • stepConfigSCHEMA ĐẦ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/runs hoặ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 views config

Pattern tổng quát để khám phá Cổng 4

  1. Clear Network tab
  2. Click UI action cần biết (tạo view, mở sync setup, edit automation...)
  3. read_network_requests lọc /api/
  4. Replay request bằng JS fetch cookie session
  5. 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_token qua POST /open-apis/auth/v3/tenant_access_token/internal với {app_id, app_secret} từ GSM secret lark-app-id + lark-app-secret trong project github-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ủ)

  1. 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ó baseRoleMap shallow.
  2. Form question treerich_description, layout, validation, shared_limit, submit_limit_once. Cổng 4 chưa test tới.
  3. Member user_id — Cổng 4 chưa phủ member API.

2 undocumented quirks

  1. Role List page_size max = 30 — set > 30 Lark trả error. Docs im lặng.
  2. 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→P6
  • knowledge/dev/lark/lark-rest-read-matrix — matrix 46 endpoint từ 4 nguồn đồng thuận
  • tools/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

8. Nguồn tham khảo