資料列
列出、建立、搜尋、匯出、彙總、更新與解析自訂資料表中的資料列及連結。
資料量大的列表請用 cursor 分頁(page_mode=cursor,回應帶 has_more 與 next_cursor);畫面一定要顯示符合幾筆時,呼叫 POST …/records/count。offset 分頁與回應的 total 仍可使用,但已列為棄用。見cursor 分頁與筆數,以及讓讀取保持快速的大量資料的效能做法。
Private 回應的 can_edit 與 private POST search 的 editable、pending_approval 請見可編輯性與審核篩選。CSV 與 XLSX 匯出檔的格式及大小、時間上限請見匯出檔案。
存在性與衝突資訊的揭露
一般依 ID 讀取資料列,以及單筆寫入的可見性檢查,對不存在、已刪除或不可讀的列都使用 404 {"detail":"Record not found"}。can_edit: own 的呼叫者寫入別人的列,只有在該列可讀時才會收到所有權 403;否則使用相同的 404 隱藏其存在。這些檢查不會授予讀取權限。舊式 unique 衝突只有在衝突列位於呼叫者的可讀範圍,且鍵的 component 沒有任何一個對呼叫者隱藏時,才會附上列 ID 與鍵值;自後端 5.21.0 起,衝突絕不會點出隱藏欄位(見 unique 規則)。unique_tuple 仍不含值。
不能把這個 body 推廣到所有路由:垃圾桶 restore 的「不是已刪除列」檢查仍使用 Record not found or not deleted,同步 bulk 較早執行的所有權/filtered-edit 預檢仍有帶資料列 ID 的拒絕字串。此界線請見批次操作。
有單位的彙總與計算值見decimal 單位:支援途徑須具相容單位;count/distinct 無單位,不執行轉換。
/private/module/custom_tables/{scope}/tables/{table_id}/records依目前呼叫者的列與欄權限,回傳一頁現行資料列、has_more 與資料表結構。預設的 offset 模式還會回傳精確的 total;帶 page_mode=cursor 時改回 next_cursor,不做筆數統計,total 為 null。
適合一般資料表瀏覽、分頁與依建立日期篩選。列表畫面請從 cursor 模式開始,只有要顯示筆數的地方才呼叫 records/count;進階條件請改用搜尋端點。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records - 公司
/private/module/custom_tables/company/tables/{table_id}/records
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要讀取資料列的自訂資料表 UUID。 |
skip | query | int >= 0 | 否 | 略過前幾筆可見資料列,預設為 0。 |
limit | query | int 1..1000 | 否 | 最多回傳幾筆資料列,可填 1 到 1,000,預設為 100。 |
page_mode | query | enum:offset|cursor | 否 | 分頁方式。offset(預設)用 skip/offset 跳頁,並一併回傳精確的 total;cursor 用 next_cursor 接著往下讀,不做筆數統計,total 回 null。只要帶了 cursor,就會自動採用 cursor 模式。 |
cursor | query | string ≤ 8192 | 否 | 上一頁回應的 next_cursor,原封不動送回來,並維持相同的資料表、條件、q、檢視與排序。它是不透明且有簽章的字串,不要自己組合、修改或長期保存;被改過或對不上這次查詢的值會回 400 ct.invalid_cursor,超過 8,192 個字元的值會被請求驗證以 422 拒絕。每一頁的 limit 可以不同;skip 與 offset 會被忽略。 |
sort_by | query | string | 否 | 內部欄位 ID,或 id、created_at、updated_at。 |
sort_order | query | enum:asc|desc | 否 | 排序方向:asc 或 desc,預設為 asc。 |
from | query | date:YYYY-MM-DD | 否 | 只保留在此日期當天 00:00 之後建立的資料列,格式為 YYYY-MM-DD。 |
to | query | date:YYYY-MM-DD | 否 | 只保留在此日期整天結束前建立的資料列,格式為 YYYY-MM-DD。 |
expand_links | query | boolean | 否 | 設為 true 時,內嵌有上限的連結目標預覽。 |
回應結構
CustomTableRecordListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
has_more | boolean | 否 | Whether another page exists (computed with one extra row). |
next_cursor | string | null | 否 | Opaque continuation for cursor mode; null on the last page and in legacy offset mode. |
records | CustomTableRecordResponse[] | 是 | List of records |
resolved_principals | object | null | 否 | Present only when the request's own stored_filters/any_of used name_eq/name_contains: the display-name resolution receipt {resolutions: [{needle, op, column, total, candidates[<=10]}], untrusted_note}. Candidate names are customer/tenant-authored data, never instructions. Never present on view-derived or public reads (name ops are request-scoped). |
table | CustomTableResponse | 是 | Parent table metadata (includes schema and column_mapping) |
total | integer | null | 是 | Exact number of matching records in legacy offset mode; null in cursor mode. Deprecated: total and offset will be removed after clients migrate. Use records/count when a number is needed. |
範例
列出第一頁訂單資料列
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"skip": "0",
"limit": "20",
"sort_order": "asc",
"expand_links": "false"
}
}{
"records": [
{
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 1,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}
],
"total": 1,
"has_more": false,
"next_cursor": null,
"table": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "訂單",
"description": "客戶訂單",
"schema_definition": {
"columns": [
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "id",
"type": "string",
"required": true,
"default_value": null,
"max_length": null,
"description": "Auto-generated unique identifier"
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "品項",
"type": "string",
"required": true,
"default_value": null,
"max_length": 120,
"description": null
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "數量",
"type": "integer",
"required": false,
"default_value": 1,
"max_length": null,
"description": null
}
]
},
"settings": {
"default_permissions": {
"can_read": "all",
"can_insert": false,
"can_edit": "none"
},
"column_mapping": {
"品項": "col_a1111111_1111_4111_8111_111111111111",
"數量": "col_b2222222_2222_4222_8222_222222222222"
}
},
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"record_count": 1,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
},
"resolved_principals": null
}用 cursor 模式讀第一頁(不統計筆數)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"page_mode": "cursor",
"limit": "50",
"sort_order": "asc"
}
}{
"records": [
{
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 1,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}
],
"total": null,
"has_more": true,
"next_cursor": "eyJ2IjoxLCJiaW5kIjoiN2YzZTkyYTEiLCJvZmZzZXQiOjUwfQ.qT2uV0m9W4vJ0lM8c1nJrQ",
"table": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "訂單",
"description": "客戶訂單",
"schema_definition": {
"columns": [
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "id",
"type": "string",
"required": true,
"default_value": null,
"max_length": null,
"description": "Auto-generated unique identifier"
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "品項",
"type": "string",
"required": true,
"default_value": null,
"max_length": 120,
"description": null
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "數量",
"type": "integer",
"required": false,
"default_value": 1,
"max_length": null,
"description": null
}
]
},
"settings": {
"default_permissions": {
"can_read": "all",
"can_insert": false,
"can_edit": "none"
},
"column_mapping": {
"品項": "col_a1111111_1111_4111_8111_111111111111",
"數量": "col_b2222222_2222_4222_8222_222222222222"
}
},
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"record_count": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
},
"resolved_principals": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 排序欄位不存在或對你隱藏;兩者回應相同:Sort column '<id>' does not exist in table schema. Valid columns: […],清單只列出你有權讀取的欄位,再加上 created_at、id 與 updated_at。attachment、json 或 principal(user、social_client、principal)欄位無法排序。對資料庫無法排序的 link、rollup、lookup 或 formula 欄位使用 sort_by 同樣是 400,detail 是編譯器的訊息:link 欄位(sorting on link column '<name>' is not supported (link cells are id lists, not scalar values))、live 紀錄超過 50,000 筆的表上的任何計算欄(sorting by computed columns is refused on tables with more than 50000 live records (this table has <N>) — narrow the data with regular filters first)、未帶 pick 的 cardinality-many lookup、帶有 fallback 的 lookup 或讀取它的 formula,以及使用 IF、AND、OR、CONCAT、日期函式,或 [Link -> Column] 引用的 formula。decimal formula(包括呼叫 DECIMAL、QUANTIZE 或 DIVIDE 的)只有在能編譯成精確的 SQL 時才會精確排序;無法編譯時(例如含有 / 除法,或乘上對 integer 或 float 欄位的 rollup 或 lookup),detail 會說明原因,例如 computed decimal expression is not exactly SQL-representable: <reason>;若有資料列無法精確求值,detail 為 ct.numeric_overflow: computed decimal cannot be evaluated exactly,或代碼換成 ct.numeric_invalid 的同一段文字。 | |
| 400 | cursor 無效:內容被改過,或不屬於這次的查詢(資料表、條件、q、檢視、排序、欄位結構或呼叫者不同),也可能是發出這一頁之後你的權限變了。請不帶 cursor,從第一頁重新開始。超過 8,192 個字元的 cursor 不會走到這一步:請求驗證會先以 422 拒絕。 | |
| 400 | 這次讀取超過時間上限(預設 15 秒,limit_ms 會回報實際的設定值)。請縮小篩選範圍,或改用 cursor 分頁,不要翻很深的 offset;對符合筆數非常多的條件做 count,也可能碰到這個上限。 | |
| 503 | 目前無法使用 cursor 分頁,因為伺服器無法為 cursor 簽章(沒有設定簽章用的密鑰)。offset 模式仍可使用,請通知維運人員。 | |
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 不存在或已進垃圾桶的資料表 ID,在所有範圍都回 CustomTable Not found,因為資料表權限檢查會先回應。位於路徑範圍之外、但呼叫者仍可存取的現行資料表,在聊天室路由回 CustomTable Not found,在部門路由回 Table not found in this department,在公司路由回 Table not found in this company。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records驗證欄位型別與必要值、建立資料列,並執行連結、規則、觸發器及核准流程。
新增單筆互動式資料時使用;大量寫入請改用批次新增端點。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records - 公司
/private/module/custom_tables/company/tables/{table_id}/records
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableInsertRequired |
| 部門 | 可使用 | CustomTableInsertRequired |
| 公司 | 可使用 | CustomTableInsertRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要新增資料列的自訂資料表 UUID。 |
include_incoming | query | boolean | 否 | 設為 true 時,在回應中一併嘗試加入新資料列的反向連結。 |
請求結構
CustomTableRecordCreate| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
created_by_ai | boolean | 否 | Whether this record was created by AI |
data | object | 是 | Record data matching the table schema. Keys: display names OR internal column IDs (both accepted on create). Example: { "col_abc123def456": "John Doe", // name column "col_def456ghi789": "john@example.com", // email column "col_ghi789jkl012": 75000, // salary column "col_jkl012mno345": "2023-01-15", // hire_date column "col_mno345pqr678": "2023-01-15 09:00", // start_time column "col_pqr678stu901": true // is_active column } Requirements: - Keys: display names or internal column IDs (both accepted; unknown keys dropped) - Do NOT include 'id' field - it will be auto-generated - Date fields: YYYY-MM-DD format (e.g., "2023-12-25") - Datetime fields: YYYY-MM-DD hh:mm format (e.g., "2023-12-25 14:30") - Required fields must have values (unless default values exist) - Data types must match column definitions - Link columns: value is a list of target-table record ids (cardinality "one" also accepts a single id). Link values are stored in the link table, never inside record data. - Rollup columns are computed on read and read-only — never include them. - user/social_client (principal) columns: write ONE raw principal id string (tenant-verified); reads render the enriched object documented on CustomTableRecordResponse. - principal columns: write ONE tagged cell user:<id> | smc:<id> | room:<id> (tenant + liveness verified); reads render {ref, kind, id, ...} — write back ref verbatim. |
回應結構
CustomTableRecordResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | boolean | 是 | Whether the current persisted record passes the caller's edit ACL (manager/all/own/filtered/none), shared by update, delete, restore, reorder and version restore. Included on private record responses. Independent of pending_approval. This is a current-row snapshot: updates still validate their new values, column permissions, rules and current authorization; true does not guarantee a write succeeds. |
created_at | string (date-time) | 是 | Record creation timestamp |
created_by | BaseUserInfo | null | 否 | Creator user info (null for external client records) |
created_by_ai | boolean | 是 | Whether the record was created by an AI agent |
created_by_client | SocialMediaClientBasicInfo | null | 否 | Creator social media client info (null for internal user records) |
data | object | 是 | Record data keyed by DISPLAY column names (reverse-mapped server-side; settings.column_mapping gives display->internal IDs) |
expanded | Record<string, ExpandedLinkColumn> | null | 否 | Only with expand_links=true: linked target records per link column, keyed by the link column's display name (same keying as `data`) |
id | string | 是 | Record UUID |
incoming_links | IncomingLinksResponse | null | 否 | Only with include_incoming=true on create/update: backlinks to this record (identical to `GET .../records/{id}/incoming-links`); absent otherwise |
is_deleted | boolean | 是 | Whether the record is soft-deleted |
pending_approval | PendingApprovalInfo | null | 否 | Approval-gate hold (spec §6): present iff a staged change currently holds this record — writes to it are blocked (409 approval_pending) until the review resolves; absent otherwise |
sort_order | integer | null | 否 | Manual sort order (null = ordered by created_at) |
table_id | string | 是 | Parent table UUID |
updated_at | string (date-time) | 是 | Last modification timestamp |
version | integer | 是 | Record version (incremented on every update/delete/revert) |
範例
建立一筆訂單資料列
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"include_incoming": "false"
},
"body": {
"data": {
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false
}
}{
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 1,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 409 | `date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。 | |
| 409 | 排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。 | |
| 409 | 這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。 | |
| 400 | 值沒通過欄位的 schema 檢查(例如缺少必要欄位):detail 是 Validation errors: 後接每個失敗欄位的訊息,以 "; " 相連。其他 400 帶有各自的訊息,沒有那個前綴:key 指到對你隱藏的欄位(Column '<key>' does not exist in table schema)、對 rollup、lookup 或 formula 欄位送非 null 值(例如 Rollup column '<col_key>' is computed and read-only)、格式錯誤的 link 或 attachment 值(例如 Link value must be a record id or a list of record ids,或 Link add/remove deltas are only supported on update)、沒通過歸屬檢查的 attachment 或 principal 值(例如 Unknown or inaccessible attachment ids: […]),或被寫入規則否決的資料列(Rule '<rule name or id>' violated: …)。唯一性規則衝突則是 409 unique 那一列。 | |
| 400 | 某個欄位帶有行內二進位內容。Base64 data URL 不論長度一律拒絕。純連續串都必須同時含大寫、小寫與數字:`base64-run` 至少 4 096 字元,且只有多行時才要求 MIME/PEM 折行形狀;`base64-magic` 至少 1 024 字元並帶已知檔案 magic 前綴,不要求該折行形狀。因此貼上的 id 清單、十六進位與一般文字不會誤判;另外兩個 reason 是 `data-url` 與 `data-url-embedded`。 | |
| 403 | CustomTableInsertRequired 閘道拒絕了此表的新增權限。 | |
| 403 | 這筆資料指向的 link target 存在、也通過你的 row ACL,只被你的 channel scope 擋住(在 REST 上是授予你所屬各聊天室之範圍的聯集)。detail 是物件:error 為 scp_out_of_scope、scp_scope_undeclared 或 scp_rule_dangling。這一類以前被併進下面那個統一的 404。 | |
| 403 | 你的編輯層級是 filtered,而這筆新資料列(含 link 值)會落在你可編輯的範圍之外。filtered 編輯者只能建立其 edit_filter 允許的資料列;沒有寫入任何東西。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 404 | link 值指向不存在、已被 soft delete,或被你的 row ACL 藏起來的目標。三種原因共用同一個訊息與狀態碼,刻意完全相同,讓寫入無法被拿來枚舉 id。 | |
| 409 | 資料表的 require_approval 規則把此次寫入暫存為 staged change,等待覆核核准;這道閘門沒有開關,只要資料表有 require_approval 規則就會生效。 | |
| 409 | 有 same_dimension_reference 規則涵蓋這張資料表:規則所屬資料表的每一筆 live 資料列,都必須恰好連到一個 live 的被引用列與一個 live 的維度列,而且被引用列必須帶有相同的維度。任何會讓某一列違反這條規則的寫入——包含刪除被引用列或維度列——都會被拒絕,且不會寫入任何東西。 | |
| 409 | unique 規則拒絕寫入,不產生效果。只有可讀取的衝突資料列,且鍵的 component 沒有任何一個對呼叫者隱藏時,才會帶 conflicting_record_id 與 key_tuple;不可讀取時省略兩者。自後端 5.21.0 起,衝突絕不會點出隱藏欄位:有 component 被隱藏時,columns 只列出讀得到的欄位,constraint 是規則不透明的 rule_<hex> id,並以 message "Unique constraint conflict" 標示被遮蔽的回應;沒有任何 component 可讀時,detail 只剩 {message}。可讀範例不是所有情況的固定 body。unique_tuple 維持不含值:以 unique_tuple_conflict(或罕見的 unique_tuple_hash_collision/unique_tuple_claim_unavailable)回 constraint 與 columns,遮蔽方式相同。 | |
| 409 | 兩筆寫入爭用同一批資料列,InnoDB 中止了這一筆。交易已回滾,不會留下任何部分寫入——直接重送請求即可。 | |
| 423 | 資料表正被結構遷移鎖定。 | |
| 429 | 此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。 | |
| 429 | 此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。 | |
| 413 | 序列化後的 `data` 超過 `CUSTOM_TABLE_MAX_RECORD_BYTES`——預設 1 MiB,可依部署覆寫。`limit` 與 `size` 都是儲存形式 JSON 的位元組數。 | |
| 422 | data 含有 key id。伺服器會產生紀錄 id 與 data.id 的值,所以建立的 body 不可以自己送;請求 schema 會在解析任何欄位之前拒絕它。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/lookup把同一資料表中的多個資料列 ID 解析成可顯示的資料,常用於呈現連結欄位標籤。
已取得一批連結目標 ID,且只需要查詢單一目標資料表時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/lookup - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/lookup - 公司
/private/module/custom_tables/company/tables/{table_id}/records/lookup
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要解析資料列的目標資料表 UUID。 |
請求結構
RecordLookupRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
ids | string[] | 是 | Record ids to resolve (deduped server-side, max 200 per call) |
回應結構
RecordLookupResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
records | ExpandedTargetRecord[] | 否 | 未指定 |
範例
解析兩個連結目標 ID
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"ids": [
"33333333-3333-4333-8333-333333333333",
"44444444-4444-4444-8444-444444444444"
]
}
}{
"records": [
{
"id": "33333333-3333-4333-8333-333333333333",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"readable": true
}
]
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 目標資料表不存在或不在路徑範圍內。不存在或已進垃圾桶的 ID,或任何聊天室路由的未命中,都是 CustomTable Not found;部門或公司路由上位於範圍之外的現行資料表則是 Table not found in this department/Table not found in this company。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/upsert先以一個欄位做等值比對:沒有符合資料就新增,恰好一筆則更新。
外部同步流程有穩定業務鍵值,希望用一次呼叫安全地新增或更新時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/upsert - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/upsert - 公司
/private/module/custom_tables/company/tables/{table_id}/records/upsert
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要執行 upsert 的自訂資料表 UUID。 |
請求結構
RecordUpsertRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
created_by_ai | boolean | 否 | Mark the record AI-authored (only used on insert) |
data | object | 是 | Record data (display-name OR internal keys; symmetric with create/update) |
match | RecordUpsertMatch | 是 | Equality predicate selecting the row to update |
回應結構
RecordUpsertResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
created | boolean | 是 | True if a new record was inserted; False if an existing one was updated |
matched_count | integer | 是 | How many readable rows matched the predicate (0 → inserted, 1 → updated) |
record | CustomTableRecordResponse | 是 | The inserted or updated record (same shape as create/update) |
範例
依品項名稱更新既有訂單
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"match": {
"column": "品項",
"value": "筆記本"
},
"data": {
"數量": 2
},
"created_by_ai": false
}
}{
"record": {
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 2,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
},
"created": false,
"matched_count": 1
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 409 | `date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。 | |
| 409 | 排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。 | |
| 409 | 這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。 | |
| 400 | 等值比對命中超過一筆資料,無法判定要更新哪一列。 | |
| 400 | 比對欄位不存在或對你隱藏。以系統欄位 id 比對時,比對的是真正的紀錄 id,所以 match.value 必須是非空的 id 字串;其他任何值都是 400 Upsert match on 'id' requires a non-empty record id string。 | |
| 400 | 比對欄位是 link、rollup、lookup 或 formula 欄位。 | |
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 403 | 恰好一筆符合而走更新分支,但呼叫者沒有此表的編輯權限。 | |
| 403 | 沒有符合資料而走新增分支,但呼叫者沒有此表的新增權限。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 409 | 所選的分支被 require_approval 規則暫存。 | |
| 409 | 另一筆待處理的暫存新增已持有這個比對值,或被比對到的資料列已被暫存持有。 | |
| 409 | 兩筆寫入爭用同一批資料列,InnoDB 中止了這一筆。交易已回滾,insert 與 update 兩條分支都沒有落地——直接重送請求即可,比對條件會重新評估。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/search以欄位條件、全文 q、儲存欄位條件或計算欄位條件搜尋,並回傳 ACL 範圍內的分頁結果,附 has_more;cursor 模式還會附 next_cursor。預設的 offset 模式也會回傳精確的 total。
需要多欄位條件、全文搜尋、cursor 或 offset 分頁、進階排序時使用。畫面要顯示符合幾筆時,請呼叫 records/count。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/search - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/search - 公司
/private/module/custom_tables/company/tables/{table_id}/records/search
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要搜尋的自訂資料表 UUID。 |
expand_links | query | boolean | 否 | 設為 true 時,內嵌有上限的連結目標預覽。 |
請求結構
CustomTableQueryRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
any_of | StoredFilterGroup[] | null | 否 | v1.2 §G19 — OR groups. Up to 10 groups; predicates AND-combine WITHIN a group and the groups OR-combine, and the whole any_of block AND-combines with `filters`/`stored_filters`/`computed_filters`/`q`. Each group needs >=1 predicate (an empty group / empty list is a 422, never match-all). Same predicate shape and column/op/type rules as `stored_filters`. Use it for OR-of-AND shapes, e.g. (status='open' AND owner=X) OR (status='review' AND owner=Y). |
computed_filters | FilterPredicate[] | null | 否 | v1.2: typed predicates on COMPUTED columns, pushed down as correlated SQL subqueries. AND-combined with `filters`. Each predicate: {"column": <rollup, cardinality-one lookup, or formula — display name or col_<hex>>, "op": eq|neq|gt|gte|lt|lte|in|is_null|is_not_null, "value": ...}. Max 3 terms, max 8 expanded subquery terms after formula inlining. SQL NULL semantics are normative: an empty/gated/dangling computed cell (NULL) never matches ANY op — `neq` included; only is_null matches it. Formula columns (Phase 2): numeric-output formulas take all ops; boolean-output (comparison) formulas take eq/neq (true/false) and is_null/is_not_null. Division compiles through NULLIF (div-by-zero = null). Accepted divergences: DECIMAL overflow in extreme value chains errors the request (the rendered cell shows null), and SQL DECIMAL vs app float arithmetic may disagree on boundary ==/!= of computed fractions. Sorting by computed columns (Phase 3) is supported via `sort_by` (see its description) — same >50k guard. Not supported (400): cardinality-"many" lookups (list cells), `contains` on computed columns, and any request when the table has more than 50,000 live records (the table-size guard — narrow with regular filters first). |
cursor | string | null | 否 | Opaque next_cursor from the preceding page. Supplying it selects cursor mode, with the same table, filters, q, view and sort. Mismatched/tampered cursors return 400 ct.invalid_cursor. Page size may change. |
editable | boolean | null | 否 | Filter within the caller's readable rows by current-row edit ACL: true = passing rows; false = non-passing rows (including creatorless rows under own permission); omitted/null = no filter. Manager/all: true keeps all readable rows, false returns none. Independent of approval holds. AND-combined with every other filter before total, sorting and pagination. Private queries only. |
filters | object | null | 否 | Filters to apply with intelligent matching based on column types. Keys are INTERNAL column IDs (col_<hex>), not display names. String/Text columns use case-insensitive partial matching (ILIKE): - "john" finds "John Doe", "johnson", etc. - "eng%" finds "Engineering", "Engine" (starts with) - "%@gmail" finds emails containing "@gmail" Other types use exact matching: - Numbers: 75000 matches exactly 75000 - Booleans: true matches exactly true - Dates: "2023-01-15" matches exactly that date Example: { "col_abc123def456": "john", // Partial text search (name column) "col_ghi789jkl012": 75000, // Exact number match (salary column) "col_pqr678stu901": true, // Exact boolean match "col_def456ghi789": "eng%" // Starts with "eng" } Link and rollup columns are NOT filterable or sortable in v1 (400 error). |
limit | integer | null | 否 | Maximum number of records to return |
offset | integer | null | 否 | Number of records to skip |
page_mode | "offset" | "cursor" | 否 | Use cursor for a count-free first page. Offset mode preserves the exact legacy total. Cursor mode ignores offset. Real system columns and stored numeric keys use keysets. Computed, collection and JSON text sorts (including string/date/select expressions, whose MySQL filesort prefix order cannot be reproduced by a full-value comparison) use a slower internal offset continuation. |
pending_approval | boolean | null | 否 | Filter by the record's existing approval-hold marker: true = pending_staged_change_id is non-NULL; false = NULL; omitted/null = no filter. Independent of editable and AND-combined with the other filters before total and pagination. Pending creates without a persisted record are not included. Private queries only. |
q | string | null | 否 | v1.2 REST query vocabulary (Fix B): global text search — a case-insensitive substring (ILIKE) OR-matched across every VISIBLE string/text column of the table (hidden/managers-only columns are excluded, `lookup: "exact"` columns too: `q` never searches them). AND-combined with `filters`, `stored_filters` and `computed_filters`. Empty/omitted = no search. |
sort | SortKey[] | null | 否 | v1.2 §G19 — multi-column sort: up to 3 {column, order} keys applied in order with a stable record-id tiebreaker appended last. Mutually exclusive with `sort_by`/`sort_order` (setting `sort` together with `sort_by` is a 422). Keys are stored scalar columns or id/created_at/updated_at/sort_order; computed / attachment / json / principal (user/social_client/principal) columns are rejected — computed columns stay single-sort via `sort_by`. |
sort_by | string | null | 否 | Column to sort by — INTERNAL column ID (col_<hex>) or id/created_at/updated_at. v1.2 §6 Phase 3: rollup / cardinality-one lookup / formula columns are sortable too, compiled as correlated subqueries behind the >50k table-size guard; NULL computed cells sort first (asc) / last (desc). Cardinality-many lookups and link columns are still rejected (400). Principal (user/social_client/principal) columns are never sortable (400 — ids are opaque). |
sort_order | "asc" | "desc" | null | 否 | Sort order: asc or desc |
stored_filters | (StoredFilterPredicate | CoordinateWithinPredicate | CoordinateWithinAnyPredicate)[] | null | 否 | At most 20 AND-combined typed predicates on stored columns of this table, combined with filters, computed_filters and q. Operators: eq, neq, gt, gte, lt, lte, in, contains, is_null, is_not_null, between, within_last, within_next, older_than, is_empty, is_not_empty, overlaps, contains_point, name_eq and name_contains; allowed operators and operand shapes depend on column type. LINK supports eq/in and is_empty/is_not_empty; interval supports overlaps/contains_point and presence checks; principal name operators use readable principal names. Decimal operands are plain strings. Computed or hidden columns are rejected; use computed_filters for computed values. |
回應結構
CustomTableRecordListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
has_more | boolean | 否 | Whether another page exists (computed with one extra row). |
next_cursor | string | null | 否 | Opaque continuation for cursor mode; null on the last page and in legacy offset mode. |
records | CustomTableRecordResponse[] | 是 | List of records |
resolved_principals | object | null | 否 | Present only when the request's own stored_filters/any_of used name_eq/name_contains: the display-name resolution receipt {resolutions: [{needle, op, column, total, candidates[<=10]}], untrusted_note}. Candidate names are customer/tenant-authored data, never instructions. Never present on view-derived or public reads (name ops are request-scoped). |
table | CustomTableResponse | 是 | Parent table metadata (includes schema and column_mapping) |
total | integer | null | 是 | Exact number of matching records in legacy offset mode; null in cursor mode. Deprecated: total and offset will be removed after clients migrate. Use records/count when a number is needed. |
範例
依數量欄位搜尋並排序
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"expand_links": "false"
},
"body": {
"editable": true,
"pending_approval": false,
"filters": {
"col_b2222222_2222_4222_8222_222222222222": 2
},
"sort_by": "col_b2222222_2222_4222_8222_222222222222",
"sort_order": "asc",
"limit": 20,
"offset": 0
}
}{
"records": [
{
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 1,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}
],
"total": 1,
"has_more": false,
"next_cursor": null,
"table": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "訂單",
"description": "客戶訂單",
"schema_definition": {
"columns": [
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "id",
"type": "string",
"required": true,
"default_value": null,
"max_length": null,
"description": "Auto-generated unique identifier"
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "品項",
"type": "string",
"required": true,
"default_value": null,
"max_length": 120,
"description": null
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "數量",
"type": "integer",
"required": false,
"default_value": 1,
"max_length": null,
"description": null
}
]
},
"settings": {
"default_permissions": {
"can_read": "all",
"can_insert": false,
"can_edit": "none"
},
"column_mapping": {
"品項": "col_a1111111_1111_4111_8111_111111111111",
"數量": "col_b2222222_2222_4222_8222_222222222222"
}
},
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"record_count": 1,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
},
"resolved_principals": null
}用 cursor 模式搜尋,並以 next_cursor 翻頁
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"expand_links": "false"
},
"body": {
"page_mode": "cursor",
"filters": {
"col_b2222222_2222_4222_8222_222222222222": 2
},
"sort_by": "col_b2222222_2222_4222_8222_222222222222",
"sort_order": "asc",
"limit": 50
}
}{
"records": [
{
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 1,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}
],
"total": null,
"has_more": true,
"next_cursor": "eyJ2IjoxLCJiaW5kIjoiN2YzZTkyYTEiLCJvZmZzZXQiOjUwfQ.qT2uV0m9W4vJ0lM8c1nJrQ",
"table": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "訂單",
"description": "客戶訂單",
"schema_definition": {
"columns": [
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "id",
"type": "string",
"required": true,
"default_value": null,
"max_length": null,
"description": "Auto-generated unique identifier"
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "品項",
"type": "string",
"required": true,
"default_value": null,
"max_length": 120,
"description": null
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "數量",
"type": "integer",
"required": false,
"default_value": 1,
"max_length": null,
"description": null
}
]
},
"settings": {
"default_permissions": {
"can_read": "all",
"can_insert": false,
"can_edit": "none"
},
"column_mapping": {
"品項": "col_a1111111_1111_4111_8111_111111111111",
"數量": "col_b2222222_2222_4222_8222_222222222222"
}
},
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"record_count": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
},
"resolved_principals": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 條件或排序欄位不存在或對你隱藏;兩者的 400 回應相同,detail 會附上 Valid columns(filter 與 sort 共用同一尾段格式,清單只列出你有權讀取的欄位,再加上 created_at、id 與 updated_at)。對你隱藏的 stored_filters、any_of 或 computed_filters 欄位,與不存在的欄位一樣回 Column '<id>' does not exist in table schema。舊版 filters 的值型別與欄位不符時同樣是 400,並一次回報所有出錯的 key:Filter validation errors: 後接每一則訊息,以 "; " 相連(例如 Filter validation errors: Filter value for column '<col_key>' must be an integer)。對 link、rollup、lookup、formula、attachment 或 interval 欄位使用 filters key 會被拒絕(interval 欄位請改用 stored_filters),attachment、json 或 principal 欄位無法排序,link、rollup、lookup 或 formula 欄位也不能作為 sort 的項目(請用 sort_by)。stored_filters、any_of、q、computed_filters 或計算欄 sort_by 中被查詢編譯器拒絕的項目是 400,detail 就是編譯器的訊息;sort_by 用 link 欄位會回 sorting on link column '<name>' is not supported (link cells are id lists, not scalar values)。在有 link、rollup、lookup 或 formula 欄位的表上,若該頁超過 100 筆紀錄,expand_links 同樣被拒絕(expand_links is only supported with limit <= 100; reduce limit or paginate)。 | |
| 400 | cursor 無效:內容被改過,或不屬於這次的查詢(資料表、條件、q、檢視、排序、欄位結構或呼叫者不同),也可能是發出這一頁之後你的權限變了。請不帶 cursor,從第一頁重新開始。超過 8,192 個字元的 cursor 不會走到這一步:請求驗證會先以 422 拒絕。 | |
| 400 | 這次讀取超過時間上限(預設 15 秒,limit_ms 會回報實際的設定值)。請縮小篩選範圍,或改用 cursor 分頁,不要翻很深的 offset;對符合筆數非常多的條件做 count,也可能碰到這個上限。 | |
| 503 | 目前無法使用 cursor 分頁,因為伺服器無法為 cursor 簽章(沒有設定簽章用的密鑰)。offset 模式仍可使用,請通知維運人員。 | |
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 不存在或已進垃圾桶的資料表 ID,在所有範圍都回 CustomTable Not found。位於路徑範圍之外的現行資料表,在聊天室路由回 CustomTable Not found,在部門路由回 Table not found(沒有範圍後綴),公司路由則回 Table not found in this company。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/count依呼叫者當下的租戶、列 ACL、欄 ACL 與 SCP 規則,回傳符合搜尋條件的資料列精確筆數。它接受和 records/search 相同的 body;分頁與排序欄位可以帶,但會被忽略。
只有畫面真的要顯示一個數字時才呼叫,例如徽章、總數、「1,280 筆中的 12 筆」。翻頁請用 cursor,不要每翻一頁就算一次筆數。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/count - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/count - 公司
/private/module/custom_tables/company/tables/{table_id}/records/count
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableCountReadRequired |
| 部門 | 可使用 | CustomTableCountReadRequired |
| 公司 | 可使用 | CustomTableCountReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要計算筆數的自訂資料表 UUID。 |
請求結構
CustomTableRecordCountRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
any_of | StoredFilterGroup[] | null | 否 | v1.2 §G19 — OR groups. Up to 10 groups; predicates AND-combine WITHIN a group and the groups OR-combine, and the whole any_of block AND-combines with `filters`/`stored_filters`/`computed_filters`/`q`. Each group needs >=1 predicate (an empty group / empty list is a 422, never match-all). Same predicate shape and column/op/type rules as `stored_filters`. Use it for OR-of-AND shapes, e.g. (status='open' AND owner=X) OR (status='review' AND owner=Y). |
computed_filters | FilterPredicate[] | null | 否 | v1.2: typed predicates on COMPUTED columns, pushed down as correlated SQL subqueries. AND-combined with `filters`. Each predicate: {"column": <rollup, cardinality-one lookup, or formula — display name or col_<hex>>, "op": eq|neq|gt|gte|lt|lte|in|is_null|is_not_null, "value": ...}. Max 3 terms, max 8 expanded subquery terms after formula inlining. SQL NULL semantics are normative: an empty/gated/dangling computed cell (NULL) never matches ANY op — `neq` included; only is_null matches it. Formula columns (Phase 2): numeric-output formulas take all ops; boolean-output (comparison) formulas take eq/neq (true/false) and is_null/is_not_null. Division compiles through NULLIF (div-by-zero = null). Accepted divergences: DECIMAL overflow in extreme value chains errors the request (the rendered cell shows null), and SQL DECIMAL vs app float arithmetic may disagree on boundary ==/!= of computed fractions. Sorting by computed columns (Phase 3) is supported via `sort_by` (see its description) — same >50k guard. Not supported (400): cardinality-"many" lookups (list cells), `contains` on computed columns, and any request when the table has more than 50,000 live records (the table-size guard — narrow with regular filters first). |
cursor | string | null | 否 | Opaque next_cursor from the preceding page. Supplying it selects cursor mode, with the same table, filters, q, view and sort. Mismatched/tampered cursors return 400 ct.invalid_cursor. Page size may change. |
editable | boolean | null | 否 | Filter within the caller's readable rows by current-row edit ACL: true = passing rows; false = non-passing rows (including creatorless rows under own permission); omitted/null = no filter. Manager/all: true keeps all readable rows, false returns none. Independent of approval holds. AND-combined with every other filter before total, sorting and pagination. Private queries only. |
filters | object | null | 否 | Filters to apply with intelligent matching based on column types. Keys are INTERNAL column IDs (col_<hex>), not display names. String/Text columns use case-insensitive partial matching (ILIKE): - "john" finds "John Doe", "johnson", etc. - "eng%" finds "Engineering", "Engine" (starts with) - "%@gmail" finds emails containing "@gmail" Other types use exact matching: - Numbers: 75000 matches exactly 75000 - Booleans: true matches exactly true - Dates: "2023-01-15" matches exactly that date Example: { "col_abc123def456": "john", // Partial text search (name column) "col_ghi789jkl012": 75000, // Exact number match (salary column) "col_pqr678stu901": true, // Exact boolean match "col_def456ghi789": "eng%" // Starts with "eng" } Link and rollup columns are NOT filterable or sortable in v1 (400 error). |
limit | integer | null | 否 | Maximum number of records to return |
offset | integer | null | 否 | Number of records to skip |
page_mode | "offset" | "cursor" | 否 | Use cursor for a count-free first page. Offset mode preserves the exact legacy total. Cursor mode ignores offset. Real system columns and stored numeric keys use keysets. Computed, collection and JSON text sorts (including string/date/select expressions, whose MySQL filesort prefix order cannot be reproduced by a full-value comparison) use a slower internal offset continuation. |
pending_approval | boolean | null | 否 | Filter by the record's existing approval-hold marker: true = pending_staged_change_id is non-NULL; false = NULL; omitted/null = no filter. Independent of editable and AND-combined with the other filters before total and pagination. Pending creates without a persisted record are not included. Private queries only. |
q | string | null | 否 | v1.2 REST query vocabulary (Fix B): global text search — a case-insensitive substring (ILIKE) OR-matched across every VISIBLE string/text column of the table (hidden/managers-only columns are excluded, `lookup: "exact"` columns too: `q` never searches them). AND-combined with `filters`, `stored_filters` and `computed_filters`. Empty/omitted = no search. |
sort | SortKey[] | null | 否 | v1.2 §G19 — multi-column sort: up to 3 {column, order} keys applied in order with a stable record-id tiebreaker appended last. Mutually exclusive with `sort_by`/`sort_order` (setting `sort` together with `sort_by` is a 422). Keys are stored scalar columns or id/created_at/updated_at/sort_order; computed / attachment / json / principal (user/social_client/principal) columns are rejected — computed columns stay single-sort via `sort_by`. |
sort_by | string | null | 否 | Column to sort by — INTERNAL column ID (col_<hex>) or id/created_at/updated_at. v1.2 §6 Phase 3: rollup / cardinality-one lookup / formula columns are sortable too, compiled as correlated subqueries behind the >50k table-size guard; NULL computed cells sort first (asc) / last (desc). Cardinality-many lookups and link columns are still rejected (400). Principal (user/social_client/principal) columns are never sortable (400 — ids are opaque). |
sort_order | "asc" | "desc" | null | 否 | Sort order: asc or desc |
stored_filters | (StoredFilterPredicate | CoordinateWithinPredicate | CoordinateWithinAnyPredicate)[] | null | 否 | v1.2 REST query vocabulary (Fix A/C): typed predicates on STORED (non-computed) columns and LINK columns of THIS table. AND-combined with `filters`, `computed_filters` and `q`; `filters` (exact/ILIKE dict) stays unchanged. Each predicate: {"column": <INTERNAL col_<hex> key>, "op": eq|neq|gt|gte|lt| lte|in|contains|is_null|is_not_null|between, "value": ...}. Stored scalar columns compile to the correct typed JSON clause (a numeric/date op is NEVER silently degraded to exact match). `between` takes a [low, high] pair (gte AND lte). LINK columns accept only in/eq and compile to an indexed EXISTS over the link table (records linked to the given target ids). rollup/lookup/formula columns are rejected (400) — use `computed_filters`. Hidden (managers-only) columns are rejected as unknown columns, with one exception on `records/search`: a column whose column ACL says `lookup: "exact"` can be looked up by ONE top-level `{"column": <its key>, "op": "eq", "value": <its full value>}` entry (no other op, not inside `any_of`, not referenced anywhere else in the request); the matching records are returned with that column. Rate limited per user and table (429 `ct.lookup_rate_limited`). |
回應結構
CustomTableRecordCountResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
count | integer | 是 | Exact matching record count after current tenant, row ACL, column ACL and SCP rules. |
範例
計算數量為 2 的資料列有幾筆
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"filters": {
"col_b2222222_2222_4222_8222_222222222222": 2
}
}
}{
"count": 12
}計算呼叫者讀得到的全部資料列有幾筆
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {}
}{
"count": 1280
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 篩選欄位不存在或對你隱藏,或篩選值的型別不對:拒絕方式和 records/search 相同(例如 Filter validation errors: Filter value for column '<col_key>' must be an integer)。 | |
| 400 | 這次讀取超過時間上限(預設 15 秒,limit_ms 會回報實際的設定值)。請縮小篩選範圍,或改用 cursor 分頁,不要翻很深的 offset;對符合筆數非常多的條件做 count,也可能碰到這個上限。 | |
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 不存在或已進垃圾桶的資料表 ID,在所有範圍都回 CustomTable Not found。位於路徑範圍之外的現行資料表,在聊天室路由回 CustomTable Not found,在部門路由回 Table not found(沒有範圍後綴),公司路由則回 Table not found in this company。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/export以和搜尋端點相同的可見性與篩選流程,串流產生 CSV 或 XLSX 檔案。
使用者要下載目前篩選結果,而不是在 JSON 中逐頁取回資料時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/export - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/export - 公司
/private/module/custom_tables/company/tables/{table_id}/records/export
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要匯出的自訂資料表 UUID。 |
請求結構
CustomTableExportPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
any_of | StoredFilterGroup[] | null | 否 | OR groups (v1.2 §G19), same as the search endpoint's `any_of`. An empty list is a 422 (omit / null = no constraint). |
computed_filters | FilterPredicate[] | null | 否 | Typed predicates on computed columns, same as the search endpoint's `computed_filters`. |
filters | object | null | 否 | Exact/ILIKE filters keyed by INTERNAL column ID (col_<hex>), same semantics as the search endpoint's `filters`. |
format | "csv" | "xlsx" | 否 | Output file format. `csv` is always available. `xlsx` requires the optional `openpyxl` dependency — if it is not installed the request returns 400 (never a silent CSV). |
q | string | null | 否 | Global case-insensitive substring search across visible string/text columns, same as the search endpoint's `q`. |
sort | SortKey[] | null | 否 | Multi-column sort (v1.2 §G19), same as the search endpoint's `sort`; mutually exclusive with sort_by. |
sort_by | string | null | 否 | Column to sort by — INTERNAL column ID (col_<hex>) or id/created_at/updated_at, same as the search endpoint. |
sort_order | string | null | 否 | Sort order: asc or desc |
stored_filters | StoredFilterPredicate[] | null | 否 | Typed predicates on stored/link columns (INTERNAL keys), same as the search endpoint's `stored_filters`. |
範例
匯出篩選後的 CSV
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"format": "csv",
"filters": {
"col_b2222222_2222_4222_8222_222222222222": 2
},
"sort_by": "col_b2222222_2222_4222_8222_222222222222",
"sort_order": "asc"
}
}{
"headers": {
"content-type": "text/csv; charset=utf-8",
"content-disposition": "attachment; filename=\"export.csv\""
},
"note": "Streaming file body omitted."
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 篩選結果超過 100000 筆匯出上限,或其他匯出條件不合法。每個舊版 filters 的值,都會在讀取任何資料列之前先對照它的欄位檢查型別,所有不符合的項目會列在同一個 detail 中:Filter validation errors: <message>; <message>,例如 Filter value for column '<key>' must be an integer。不存在或對你隱藏的 filters key 或排序欄位,會回 Filter column '<id>'(或 Sort column '<id>')does not exist in table schema. Valid columns: […],清單只列出你有權讀取的欄位;對你隱藏的 stored_filters、any_of 或 computed_filters 欄位則回 Column '<id>' does not exist in table schema。 | |
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 不存在或已進垃圾桶的資料表 ID,或任何聊天室路由的未命中,都是 CustomTable Not found;部門或公司路由上位於範圍之外的現行資料表則是 Table not found in this department/Table not found in this company。 | |
| 413 | 產生的檔案會超過位元組上限(預設 512 MiB;部署端可以調整,但不會超過 1 GiB)。Body 是具型別項目的陣列:{"detail":[{"code":"ct.export_too_large","detail":"Export exceeds its byte limit; narrow your filter and retry."}]}。不會串流任何內容;請縮小篩選或拆分匯出。 | |
| 422 | 產生時間超過時間預算(預設 60 秒;部署端可以調整,但不會超過 90 秒):{"detail":[{"code":"ct.export_time_budget_exceeded","detail":"Export exceeded its server render time budget; narrow your filter and retry."}]}。這個 422 與請求驗證錯誤共用外層,後者的項目帶 type、loc 與 msg,因此請依 code 分支。不會串流任何內容。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/aggregate在伺服器端依欄位分組,計算 count、count_distinct、sum、avg、min 或 max。
製作摘要卡、群組統計或圖表,不需要下載每筆明細時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/aggregate - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/aggregate - 公司
/private/module/custom_tables/company/tables/{table_id}/records/aggregate
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要彙總的自訂資料表 UUID。 |
請求結構
AggregateRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
filters | FilterPredicate[] | null | 否 | Pre-aggregation row filters — v1.1 {column, op, value} predicates (same typing rules as rollup filters; max 10, AND-combined). Stored scalar columns only. |
group_by | string[] | 否 | 0–2 stored scalar grouping columns: string/text/integer/float/decimal/boolean/date/datetime/select/user/social_client/principal, or a cardinality-one link grouped by target record id (unlinked rows group under null). Empty means one overall row. |
metrics | AggregateMetric[] | 是 | 1-5 aggregations computed per group |
回應結構
AggregateResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
groups | AggregateGroup[] | 是 | 未指定 |
table_id | string | 是 | 未指定 |
total_groups | integer | 是 | Number of groups returned (capped at 1000 — narrower grouping required beyond that) |
範例
計算資料表總筆數
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"group_by": [],
"metrics": [
{
"column": "*",
"agg": "count"
}
]
}
}{
"groups": [
{
"key": {},
"metrics": {
"count::*": 2
}
}
],
"table_id": "22222222-2222-4222-8222-222222222222",
"total_groups": 1
}計算 decimal 欄位的平均值(此例的資料表有 decimal 欄位「單價」)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"group_by": [],
"metrics": [
{
"column": "單價",
"agg": "avg",
"result_scale": 2,
"rounding_mode": "half_even"
}
]
}
}{
"groups": [
{
"key": {},
"metrics": {
"avg::單價": "1290.50"
}
}
],
"table_id": "22222222-2222-4222-8222-222222222222",
"total_groups": 1
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 分組欄位、指標欄位、運算或 filters predicate 不適用於目前結構(指標錯誤使用小寫 metric column … not found)。detail 是驗證器的訊息;有問題的 filters predicate 例如會寫 filter column '<name>' not found in '<table>',後面接一段列出該表可用欄位的提示。 | |
| 400 | 缺少/不相容/無效單位以 detail.code 拒絕,不換算。 | |
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 422 | aggregate 只接受 group_by、metrics、filters——送 stored_filters、any_of、q(或任何打錯的 key)都是 422。forbid 收斂之前這會回 200、多餘的 key 被無聲丟掉,等於整個 aggregate 在沒有過濾的情況下執行。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/order整批更新指定資料列的 sort_order,供拖曳排序等介面保存順序。
使用者完成手動排序,且只需保存位置有變動的資料列時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/order - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/order - 公司
/private/module/custom_tables/company/tables/{table_id}/records/order
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableEditRequired |
| 部門 | 可使用 | CustomTableEditRequired |
| 公司 | 可使用 | CustomTableEditRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要重新排序的自訂資料表 UUID。 |
請求結構
ReorderRecordsRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
records | RecordOrderUpdate[] | 是 | List of record_id/sort_order pairs to update |
回應結構
ReorderRecordsResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
updated_count | integer | 是 | Number of records whose sort_order was updated |
範例
交換兩筆資料列的排序位置
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"records": [
{
"record_id": "33333333-3333-4333-8333-333333333333",
"sort_order": 0
},
{
"record_id": "44444444-4444-4444-8444-444444444444",
"sort_order": 1
}
]
}
}{
"updated_count": 2,
"message": "Records reordered successfully"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 400 | 請求中有重複的資料列 ID 或 sort_order(Duplicate record IDs in reorder request、Duplicate sort_order values in reorder request);編輯層級為 own 或 filtered 時,重複的資料列 ID 會更早以 404 被攔下。 | |
| 400 | can_edit=all 時,缺少的 ID 會以 400 Records not found in table: <ids> 從 CRUD 重排路徑回傳。 | |
| 400 | 要求的某個 sort_order 已經被 reorder 集合以外的資料列佔著,包含已被 soft delete 但仍佔著槽位的資料列。衝突的槽位以遞增順序列出。dead-held 這一類以前會通過 pre-check、然後死在 flush,回 409 "Duplicate value '<table_id>-<slot>' violates unique constraint 'uq_table_sort_order'."——那是資料庫形狀的答案,既沒指出該改哪個槽位,也沒給出一個可用的空槽位。 | |
| 403 | CustomTableEditRequired 閘門拒絕了對這張資料表的編輯權限。 | |
| 403 | 你的編輯層級是 own,而清單中有資料列是別人建立的。 | |
| 404 | 編輯層級為 own 或 filtered 時,不存在、已軟刪除、屬於其他資料表或在請求中重複出現的 ID,會在 CRUD 路徑執行前以 404 One or more records not found. 回傳。不是你建立的資料列(own)是 403。filtered 時,被你的讀取政策或這張資料表的 channel scope 隱藏的資料列是 404 Record not found,你能讀取但超出編輯範圍的資料列是 403 This record is outside your editable scope. | |
| 409 | 兩筆寫入爭用同一批資料列,InnoDB 中止了這一筆。交易已回滾,原本的排序維持不變——直接重送請求即可。 | |
| 423 | 資料表目前正被結構遷移鎖定。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/{record_id}依 ID 回傳一筆可讀資料列,並將內部欄位鍵轉回顯示名稱。
顯示資料列詳細內容,或在編輯前取得最新版本時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/{record_id} - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/{record_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/records/{record_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 資料列所屬的自訂資料表 UUID。 |
record_id | path | uuid | 是 | 要取得的資料列 UUID。 |
expand_links | query | boolean | 否 | 設為 true 時,內嵌連結目標預覽。 |
回應結構
CustomTableRecordResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | boolean | 是 | Whether the current persisted record passes the caller's edit ACL (manager/all/own/filtered/none), shared by update, delete, restore, reorder and version restore. Included on private record responses. Independent of pending_approval. This is a current-row snapshot: updates still validate their new values, column permissions, rules and current authorization; true does not guarantee a write succeeds. |
created_at | string (date-time) | 是 | Record creation timestamp |
created_by | BaseUserInfo | null | 否 | Creator user info (null for external client records) |
created_by_ai | boolean | 是 | Whether the record was created by an AI agent |
created_by_client | SocialMediaClientBasicInfo | null | 否 | Creator social media client info (null for internal user records) |
data | object | 是 | Record data keyed by DISPLAY column names (reverse-mapped server-side; settings.column_mapping gives display->internal IDs) |
expanded | Record<string, ExpandedLinkColumn> | null | 否 | Only with expand_links=true: linked target records per link column, keyed by the link column's display name (same keying as `data`) |
id | string | 是 | Record UUID |
incoming_links | IncomingLinksResponse | null | 否 | Only with include_incoming=true on create/update: backlinks to this record (identical to `GET .../records/{id}/incoming-links`); absent otherwise |
is_deleted | boolean | 是 | Whether the record is soft-deleted |
pending_approval | PendingApprovalInfo | null | 否 | Approval-gate hold (spec §6): present iff a staged change currently holds this record — writes to it are blocked (409 approval_pending) until the review resolves; absent otherwise |
sort_order | integer | null | 否 | Manual sort order (null = ordered by created_at) |
table_id | string | 是 | Parent table UUID |
updated_at | string (date-time) | 是 | Last modification timestamp |
version | integer | 是 | Record version (incremented on every update/delete/revert) |
範例
取得一筆訂單
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"record_id": "33333333-3333-4333-8333-333333333333"
},
"query": {
"expand_links": "false"
}
}{
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 1,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 404 | 你的讀取政策把這筆資料列隱藏了:own 或 filtered 讀取層級,或這張資料表的 channel scope。 | |
| 404 | 列不存在或已軟刪除;隱藏列也回相同 Record not found。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/{record_id}局部更新一筆資料列、重新驗證欄位與連結,並建立新的版本歷程。
已知資料列 ID,且要修改少數欄位時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/{record_id} - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/{record_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/records/{record_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableEditRequired |
| 部門 | 可使用 | CustomTableEditRequired |
| 公司 | 可使用 | CustomTableEditRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 資料列所屬的自訂資料表 UUID。 |
record_id | path | uuid | 是 | 要更新的資料列 UUID。 |
include_incoming | query | boolean | 否 | 設為 true 時,在回應中一併嘗試加入更新後的反向連結。 |
請求結構
CustomTableRecordUpdate| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
expected_version | integer | null | 否 | Optimistic-lock guard for lost-update detection. When set, the update fails with HTTP 409 unless it matches the record's current version. Omit for last-writer-wins (unchanged behavior). |
回應結構
CustomTableRecordResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | boolean | 是 | Whether the current persisted record passes the caller's edit ACL (manager/all/own/filtered/none), shared by update, delete, restore, reorder and version restore. Included on private record responses. Independent of pending_approval. This is a current-row snapshot: updates still validate their new values, column permissions, rules and current authorization; true does not guarantee a write succeeds. |
created_at | string (date-time) | 是 | Record creation timestamp |
created_by | BaseUserInfo | null | 否 | Creator user info (null for external client records) |
created_by_ai | boolean | 是 | Whether the record was created by an AI agent |
created_by_client | SocialMediaClientBasicInfo | null | 否 | Creator social media client info (null for internal user records) |
data | object | 是 | Record data keyed by DISPLAY column names (reverse-mapped server-side; settings.column_mapping gives display->internal IDs) |
expanded | Record<string, ExpandedLinkColumn> | null | 否 | Only with expand_links=true: linked target records per link column, keyed by the link column's display name (same keying as `data`) |
id | string | 是 | Record UUID |
incoming_links | IncomingLinksResponse | null | 否 | Only with include_incoming=true on create/update: backlinks to this record (identical to `GET .../records/{id}/incoming-links`); absent otherwise |
is_deleted | boolean | 是 | Whether the record is soft-deleted |
pending_approval | PendingApprovalInfo | null | 否 | Approval-gate hold (spec §6): present iff a staged change currently holds this record — writes to it are blocked (409 approval_pending) until the review resolves; absent otherwise |
sort_order | integer | null | 否 | Manual sort order (null = ordered by created_at) |
table_id | string | 是 | Parent table UUID |
updated_at | string (date-time) | 是 | Last modification timestamp |
version | integer | 是 | Record version (incremented on every update/delete/revert) |
範例
更新訂單數量
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"record_id": "33333333-3333-4333-8333-333333333333"
},
"query": {
"include_incoming": "false"
},
"body": {
"col_b2222222_2222_4222_8222_222222222222": 3
}
}{
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 3
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 2,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 409 | `date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。 | |
| 409 | 排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。 | |
| 409 | 這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。 | |
| 400 | 鍵指向資料表中不存在的欄位(顯示名稱或 col_<hex>)或對你隱藏的欄位(Column '<key>' does not exist in table schema)、值不符合欄位型別或格式(Validation error for column '<name>': …)、送出了 id 欄位(Cannot modify the 'id' field)、對 rollup、lookup 或 formula 欄位送非 null 值(例如 Rollup column '<col_key>' is computed and read-only)、link 或 attachment 值格式錯誤、沒通過歸屬檢查的 attachment 或 principal 值(例如 Unknown or inaccessible attachment ids: […]),或寫入規則否決了這次變更(Rule '<rule name or id>' violated: …);唯一性與 transition 規則屬於 409 那幾列。不存在、已刪除或被隱藏的 link 目標屬於 404 那一列;只被 scope 隱藏的目標屬於 403 那一列。 | |
| 400 | 與 create 同一套守門,但只檢查有變動的欄位:頂層值與既有值相同就略過,變動欄位內部與既有值相同的葉字串也放行。新出現的二進位葉節點仍然會被拒絕。 | |
| 403 | CustomTableEditRequired 閘門拒絕了對這張資料表的編輯權限。 | |
| 403 | 你的編輯層級為 own,資料列由他人建立且可讀取。資料列不可讀或不存在時,改回 404 Record not found。 | |
| 403 | 這筆資料列可讀,但超出你的 filtered 編輯範圍,或你的變更會讓它離開該範圍。 | |
| 403 | 這次更新寫入的 link 值指向的目標存在、也通過你的 row ACL,只被你的 channel scope 擋住(在 REST 上是授予你所屬各聊天室之範圍的聯集)。detail 是物件:error 為 scp_out_of_scope、scp_scope_undeclared 或 scp_rule_dangling。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 404 | 資料列不存在、已軟刪除或不在呼叫者的可讀範圍內。一般資料列寫入統一回 {detail: "Record not found"};own 編輯者只有在可讀取他人建立的資料列時,才會收到 403。 | |
| 404 | link 值指向不存在、已被 soft delete,或被你的 row ACL 藏起來的目標——三種原因共用同一個訊息與狀態碼。 | |
| 409 | 資料表的 require_approval 規則把此次寫入暫存為 staged change,等待覆核核准;這道閘門沒有開關,只要資料表有 require_approval 規則就會生效。 | |
| 409 | expected_version 已與儲存的版本不符;沒有寫入任何東西。請重新讀取,再決定是否重試。 | |
| 409 | 已有暫存變更持有這筆資料列;在覆核處理完之前,寫入會被擋下。 | |
| 409 | 有 same_dimension_reference 規則涵蓋這張資料表:規則所屬資料表的每一筆 live 資料列,都必須恰好連到一個 live 的被引用列與一個 live 的維度列,而且被引用列必須帶有相同的維度。任何會讓某一列違反這條規則的寫入——包含刪除被引用列或維度列——都會被拒絕,且不會寫入任何東西。 | |
| 409 | unique 規則拒絕寫入,不產生效果。只有可讀取的衝突資料列,且鍵的 component 沒有任何一個對呼叫者隱藏時,才會帶 conflicting_record_id 與 key_tuple;不可讀取時省略兩者。自後端 5.21.0 起,衝突絕不會點出隱藏欄位:有 component 被隱藏時,columns 只列出讀得到的欄位,constraint 是規則不透明的 rule_<hex> id,並以 message "Unique constraint conflict" 標示被遮蔽的回應;沒有任何 component 可讀時,detail 只剩 {message}。可讀範例不是所有情況的固定 body。unique_tuple 維持不含值:以 unique_tuple_conflict(或罕見的 unique_tuple_hash_collision/unique_tuple_claim_unavailable)回 constraint 與 columns,遮蔽方式相同。 | |
| 409 | transition 規則不允許該欄位從已存的值變成新值,所以沒有寫入任何東西。detail 是純字串:Rule '<rule name or id>' violated: transition of '<column>' from <old> to <new> is not allowed。 | |
| 409 | 兩筆寫入爭用同一批資料列,InnoDB 中止了這一筆。交易已回滾,該筆記錄維持原狀——直接重送請求即可。 | |
| 423 | 資料表正被結構遷移鎖定。 | |
| 429 | 此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。 | |
| 413 | 合併後的資料列同時「超過上限」且「比目前更大」。`previous_size` 是目前的大小;維持原大小或變小的編輯永遠允許。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/{record_id}軟刪除一筆資料列,並回報刪除當下有多少連結指向它。
需要可從垃圾桶復原地移除資料列時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/{record_id} - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/{record_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/records/{record_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableEditRequired |
| 部門 | 可使用 | CustomTableEditRequired |
| 公司 | 可使用 | CustomTableEditRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 資料列所屬的自訂資料表 UUID。 |
record_id | path | uuid | 是 | 要軟刪除的資料列 UUID。 |
回應結構
RecordDeleteResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
incoming_links_removed | integer | 否 | Count of link rows that referenced the deleted record at delete time |
message | string | 否 | Status message |
範例
刪除一筆測試訂單
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"record_id": "33333333-3333-4333-8333-333333333333"
}
}{
"message": "Record deleted successfully",
"incoming_links_removed": 0
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 409 | 排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。 | |
| 409 | 這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。 | |
| 403 | CustomTableEditRequired 閘門拒絕了對這張資料表的編輯權限。 | |
| 403 | 你的編輯層級為 own,資料列由他人建立且可讀取。資料列不可讀或不存在時,改回 404 Record not found。 | |
| 403 | 這筆資料列可讀,但超出你的 filtered 編輯範圍。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 404 | 資料列不存在或已軟刪除,或在 filtered 編輯層級下被你的讀取政策或這張資料表的 channel scope 隱藏。detail 是 Record not found;當你的編輯層級是 own 或 filtered 且 ID 不存在時則是 CustomTableRecord Not found。你能讀取但不能編輯的資料列是 403,不是 404。 | |
| 409 | 資料表的 require_approval 規則把此次刪除暫存為 staged change,等待覆核核准;這道閘門沒有開關,只要資料表有 require_approval 規則就會生效。 | |
| 409 | 已有暫存變更持有這筆資料列;在覆核處理完之前,寫入會被擋下。 | |
| 409 | 有 same_dimension_reference 規則涵蓋這張資料表:規則所屬資料表的每一筆 live 資料列,都必須恰好連到一個 live 的被引用列與一個 live 的維度列,而且被引用列必須帶有相同的維度。任何會讓某一列違反這條規則的寫入——包含刪除被引用列或維度列——都會被拒絕,且不會寫入任何東西。 | |
| 409 | 兩筆寫入爭用同一批資料列,InnoDB 中止了這一筆。交易已回滾,該筆記錄維持原狀——直接重送請求即可。 | |
| 423 | 資料表正被結構遷移鎖定。 | |
| 429 | 此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/{record_id}/hydrated一次回傳資料列、反向連結與資料表結構,供詳細頁直接呈現。
資料列詳細頁同時需要欄位定義與「被哪些資料連結」面板時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/{record_id}/hydrated - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/{record_id}/hydrated - 公司
/private/module/custom_tables/company/tables/{table_id}/records/{record_id}/hydrated
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 資料列所屬的自訂資料表 UUID。 |
record_id | path | uuid | 是 | 要完整載入的資料列 UUID。 |
expand_links | query | boolean | 否 | 設為 true 時,在 record 內嵌連結目標預覽。 |
limit_per_table | query | int 1..50 | 否 | 每個反向連結群組最多內嵌幾筆來源資料,可填 1 到 50,預設為 20。 |
回應結構
HydratedRecordResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
incoming_links | IncomingLinksResponse | 是 | Backlinks (identical to `GET .../records/{id}/incoming-links`) |
record | CustomTableRecordResponse | 是 | The record (identical to `GET .../records/{id}`) |
table | CustomTableResponse | 是 | Table metadata for rendering the record's fields |
範例
載入訂單詳細頁所需資料
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"record_id": "33333333-3333-4333-8333-333333333333"
},
"query": {
"expand_links": "false",
"limit_per_table": "20"
}
}{
"record": {
"id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"created_by_ai": false,
"created_by": {
"id": "99999999-9999-4999-8999-999999999999",
"created_at": "2026-01-01 08:00:00",
"expired_at": "2027-01-01 08:00:00",
"photo_url": "https://example.com/avatar.png",
"enabled": true,
"is_verified": true,
"username": "api-user",
"email": "api-user@example.com",
"nickname": "API User",
"department_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"department_name": "營運部"
},
"created_by_client": null,
"is_deleted": false,
"sort_order": 1,
"version": 1,
"expanded": null,
"incoming_links": null,
"can_edit": true,
"pending_approval": null,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
},
"incoming_links": {
"record_id": "33333333-3333-4333-8333-333333333333",
"groups": [
{
"source_table_id": "55555555-5555-4555-8555-555555555555",
"source_table_name": "出貨",
"link_field": "訂單",
"cardinality": "one",
"records": [
{
"id": "66666666-6666-4666-8666-666666666666",
"data": {
"id": "88888888-8888-4888-8888-888888888888",
"編號": "S-001"
},
"readable": true
}
],
"total": 1
}
]
},
"table": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "訂單",
"description": "客戶訂單",
"schema_definition": {
"columns": [
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "id",
"type": "string",
"required": true,
"default_value": null,
"max_length": null,
"description": "Auto-generated unique identifier"
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "品項",
"type": "string",
"required": true,
"default_value": null,
"max_length": 120,
"description": null
},
{
"target_table_id": null,
"cardinality": null,
"link_field": null,
"aggregation": null,
"target_column": null,
"direction": null,
"source_table_id": null,
"match": null,
"filter": null,
"expression": null,
"pick": null,
"fallback": null,
"options": null,
"max_count": null,
"max_file_bytes": null,
"allowed_mime_types": null,
"restricted": null,
"name": "數量",
"type": "integer",
"required": false,
"default_value": 1,
"max_length": null,
"description": null
}
]
},
"settings": {
"default_permissions": {
"can_read": "all",
"can_insert": false,
"can_edit": "none"
},
"column_mapping": {
"品項": "col_a1111111_1111_4111_8111_111111111111",
"數量": "col_b2222222_2222_4222_8222_222222222222"
}
},
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"department_id": null,
"company_id": null,
"record_count": 2,
"created_at": "2026-07-19T02:00:00",
"updated_at": "2026-07-19T02:05:00"
}
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 404 | 你的讀取政策把這筆資料列隱藏了:own 或 filtered 讀取層級,或這張資料表的 channel scope。 | |
| 404 | 來源列不存在或已軟刪除;隱藏來源列也回相同 Record not found。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/{record_id}/incoming-links依來源資料表與連結欄位分組,列出可讀且正指向目標的資料列,包含同公司內由較窄 scope 建立的有效 link。
建立「被哪些資料引用」面板,或在刪除前盤點相依關係時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/{record_id}/incoming-links - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/{record_id}/incoming-links - 公司
/private/module/custom_tables/company/tables/{table_id}/records/{record_id}/incoming-links
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 目標資料列所屬的自訂資料表 UUID。 |
record_id | path | uuid | 是 | 要查詢反向連結的目標資料列 UUID。 |
limit_per_table | query | int 1..50 | 否 | 每個來源資料表與欄位群組最多內嵌幾筆,可填 1 到 50,預設為 20。 |
回應結構
IncomingLinksResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
groups | IncomingLinkGroup[] | 否 | 未指定 |
record_id | string | 是 | 未指定 |
範例
列出指向一筆訂單的資料
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"record_id": "33333333-3333-4333-8333-333333333333"
},
"query": {
"limit_per_table": "20"
}
}{
"record_id": "33333333-3333-4333-8333-333333333333",
"groups": [
{
"source_table_id": "55555555-5555-4555-8555-555555555555",
"source_table_name": "出貨",
"link_field": "訂單",
"cardinality": "one",
"records": [
{
"id": "66666666-6666-4666-8666-666666666666",
"data": {
"id": "88888888-8888-4888-8888-888888888888",
"編號": "S-001"
},
"readable": true
}
],
"total": 1
}
]
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 讀取門檻拒絕了你:你的有效讀取層級為 none 時回 Read access denied for this table.。資料表門檻會先回應:聊天室資料表而你不在該聊天室時回 Not a member of this chatroom.,除非有個人或部門授權開放它,或你管理聊天室所屬部門或公司;部門資料表而你不在該部門時回 Insufficient permissions for department-scoped table.,除非你是公司管理者,或有授權、moderator 列或全公司受眾開放它。另一家公司的資料表不會走到這些 403:自後端 5.21.0 起,它與 id 不存在一樣回 404 CustomTable Not found。 | |
| 404 | 資料表 ID 不存在或已進垃圾桶(回 CustomTable Not found;資料表所屬聊天室已刪除時則回 Chatroom Not found),或現行資料表不在路徑範圍內:聊天室範圍回 CustomTable Not found,部門範圍回 Table not found in this department,公司範圍回 Table not found in this company。 | |
| 404 | 你的讀取政策把這筆資料列隱藏了:own 或 filtered 讀取層級,或這張資料表的 channel scope。 | |
| 404 | 來源列不存在或已軟刪除;隱藏來源列也回相同 Record not found。 | |
/private/module/custom_tables/{scope}/links/resolve在一次呼叫中,把多張目標資料表的 ID 清單解析成可顯示資料。
一個畫面同時呈現多種連結欄位,想避免逐表發出 lookup 請求時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/links/resolve - 部門
/private/module/custom_tables/department/{department_id}/links/resolve - 公司
/private/module/custom_tables/company/links/resolve
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | ChatRoomJoinedRequired |
| 部門 | 可使用 | DepartmentTenantRequired |
| 公司 | 可使用 | get_current_user |
請求結構
LinkLabelResolveRequest| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
targets | LinkResolveTarget[] | 是 | Per-target-table id lists (max 50 tables per call) |
回應結構
LinkLabelResolveResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
results | LinkResolveResult[] | 否 | 未指定 |
範例
解析一張目標資料表的連結 ID
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"body": {
"targets": [
{
"table_id": "22222222-2222-4222-8222-222222222222",
"ids": [
"33333333-3333-4333-8333-333333333333",
"44444444-4444-4444-8444-444444444444"
]
}
]
}
}{
"results": [
{
"table_id": "22222222-2222-4222-8222-222222222222",
"found": true,
"readable": true,
"records": [
{
"id": "33333333-3333-4333-8333-333333333333",
"data": {
"id": "77777777-7777-4777-8777-777777777777",
"品項": "筆記本",
"數量": 2
},
"readable": true
}
]
}
]
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 422 | targets 超過 50 組,或單組 ids 超過 200 個。 | |
單筆讀取的不存在與隱藏列均回 404 Record not found;trash-restore/bulk 的 not-found 措辭仍不同。另見型別化查詢值 與歷史讀取。