批次操作
同步執行原子混合寫入、派送允許部分成功的大量工作,或先暫存部門/公司資料集,再一次原子發布。下方卡片包含五個 publish-session 操作;完整生命週期與重試協定見原子資料集發布指南。
卡片也列出暫存世代:僅追加、不可見的多表輸入與一次同步啟用,和取代式 publish session、部分成功 worker 不同。
/private/module/custom_tables/{scope}/tables/{table_id}/records/bulk在一個交易中混合新增、更新與刪除資料列,並回傳各類操作筆數。
最多 100 個相依動作必須全成或全敗時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/bulk - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/bulk - 公司
/private/module/custom_tables/company/tables/{table_id}/records/bulk
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableEditRequired |
| 部門 | 可使用 | CustomTableEditRequired |
| 公司 | 可使用 | CustomTableEditRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要執行混合批次的自訂資料表 UUID。 |
請求結構
BulkRecordPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
actions | BulkRecordAction[] | 是 | List of insert/update/delete actions (max 100 per request) |
回應結構
BulkRecordResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
deleted | integer | 是 | Number of records deleted |
inserted | integer | 是 | Number of records inserted |
message | string | 否 | 未指定 |
updated | integer | 是 | Number of records updated |
範例
原子地新增、更新與刪除各一筆
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"actions": [
{
"action": "insert",
"data": {
"品項": "鉛筆",
"數量": 5
}
},
{
"action": "update",
"record_id": "33333333-3333-4333-8333-333333333333",
"data": {
"col_b2222222_2222_4222_8222_222222222222": 3
}
},
{
"action": "delete",
"record_id": "44444444-4444-4444-8444-444444444444"
}
]
}
}{
"inserted": 1,
"updated": 1,
"deleted": 1,
"message": "Bulk record operations completed successfully"
}用 expected_version 保護單一 action(樂觀鎖)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"actions": [
{
"action": "update",
"record_id": "33333333-3333-4333-8333-333333333333",
"data": {
"col_b2222222_2222_4222_8222_222222222222": 3
},
"expected_version": 4
},
{
"action": "delete",
"record_id": "44444444-4444-4444-8444-444444444444"
}
]
}
}{
"inserted": 0,
"updated": 1,
"deleted": 1,
"message": "Bulk record operations completed 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。讀取不受影響。 | |
| 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 | 某個 action 結構不合法(例如 Action N: 'data' is required for insert);索引從 0 起算。 | |
| 400 | 某個 action 寫入的 link 值指向不存在、已被 soft delete,或被你的 row ACL 藏起來的目標。這條路由把底層的 ValueError 映成 400,所以統一的參照失敗在這裡是帶字串 detail 的 400——不是單筆通道回的 404。三種原因的訊息刻意完全相同:分開它們就等於讓寫入變成 existence oracle。 | |
| 400 | 任何一個 action 的資料列帶有行內二進位,整個請求就失敗——這條 lane 是單一交易。`column` 會指出是哪個欄位。 | |
| 400 | Predicate rule 否決了某個 action(「Rule '<name>' violated: <reason>」)。批次是原子的,所以沒有套用任何變更。 | |
| 403 | 路由 dependency 拒絕:呼叫者對此表沒有編輯權。即使批次只有 insert 也需要這項權利。 | |
| 403 | 批次含 insert action,但呼叫者沒有此表的新增權限。 | |
| 403 | own 編輯者指定了他人建立的資料列。REST 較早執行的 own/filtered pre-pass 會先檢查所有目標,仍回這個帶資料列 ID 的 403;與一般單筆寫入不同,此 pre-pass 不會依可讀範圍遮蔽。 | |
| 403 | 受 filter 限制的編輯者指定了可讀但不在其 edit filter 內的資料列,或某個 action 會讓資料列移出 filter。批次執行前檢查變更前的資料,執行中檢查變更後的資料。 | |
| 403 | 某個 action 指向的 link target 存在、也通過你的 row ACL,只被 acting room 的 channel scope 擋住。這裡的 detail 是**物件**不是字串:error 為 scp_out_of_scope、scp_scope_undeclared(你的房間在目標資料表上沒有宣告 channel scope)或 scp_rule_dangling(該表上的 channel rule 已無法解析)。請以 detail.error 分支、不要以狀態碼分支,並預期這條路由的 detail 可能是字串也可能是物件。 | |
| 404 | 表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。 | |
| 404 | REST 較早執行的 own/filtered pre-pass 對不存在的目標,仍回此處帶資料列 ID 的字串。之後的 mutation 檢查使用 {detail: "Record not found"}。不要假設所有 bulk 拒絕都使用一般資料列的 body。 | |
| 409 | 資料表的 require_approval 規則把整批寫入暫存等待覆核(BulkApprovalConflict409Response)。 | |
| 409 | 在有 require_approval 的表格上,某個目標資料列已被另一個待審的 staged change 占住。沒有 stage 任何東西;請等該變更被核准或拒絕。 | |
| 409 | unique 規則拒絕 action,整個有序批次回滾。範例的 conflicting_record_id 與 key_tuple 只在衝突資料列可讀、且鍵的 component 沒有任何一個對呼叫者隱藏時出現;有 component 被隱藏時會回被遮蔽的 body(columns 只列出讀得到的欄位、constraint 為規則的 rule_<hex> id、message 為 "Unique constraint conflict")。unique_tuple 改回不含值的 {error: "unique_tuple_conflict", constraint, columns},遮蔽方式相同;transition 規則仍可能回字串 detail。 | |
| 409 | 某個 update action 帶了 expected_version,而資料列當下的 version 不同。比對是在 refresh 過的資料列上、於 SELECT … FOR UPDATE 之下進行。這批是 atomic,所以什麼都沒套用——衝突之前的 action 沒有,之後的也沒有。action_index 是你送出的 actions 陣列中以 0 起算的位置。 | |
| 409 | 同一道 guard、不同的答案:當 require_approval 規則命中這一批時,expected_version 會帶進 staging 並在 staging lock 下複驗,而 atomic 呼叫端拿到的是這個、不是 version_conflict——沒有 action_index、沒有版本數字,只有被動過的 record ids。沒有 stage、也沒有執行。這條路由上不要只認 version_conflict。 | |
| 409 | 有序批次的 action 在先前暫時 action 之後才進入核准保留。所有暫時效果回滾;拆開批次後重試,不保留部分執行。 | |
| 422 | expected_version 被送在 insert 或 delete action 上。它只在 update action 有效——insert 沒有前一個版本,delete 也沒有資料需要保護。在任何 action 執行之前就被 model 擋下。 | |
| 422 | 某個 action 的 data 帶了 id。資料列 id 由伺服器產生;update 或 delete 的目標請放在 record_id。 | |
| 423 | 資料表正被結構遷移鎖定。 | |
| 429 | 此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。 | |
| 429 | 此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。 | |
| 413 | 任何一個 action 儲存後會超過上限,整個請求就失敗。更新是以合併後的資料列量測,且只拒絕變大。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/bulk-insert派送最多 50,000 筆資料列的背景新增工作,立即回傳追蹤 ticket。
資料量超過同步混合批次上限,且可接受背景逐筆結果時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/bulk-insert - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/bulk-insert - 公司
/private/module/custom_tables/company/tables/{table_id}/records/bulk-insert
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableInsertRequired |
| 部門 | 可使用 | CustomTableInsertRequired |
| 公司 | 可使用 | CustomTableInsertRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要批次新增資料列的自訂資料表 UUID。 |
worker | query | enum:legacy|batch|sqs|pubsub | 否 | 背景執行後端;可選 legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(未設定或無效時為 legacy)。 |
請求結構
BulkInsertPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
created_by_ai | boolean | 否 | Whether records are created by AI |
idempotency_key | string | null | 否 | Optional replay key, at most 128 characters, scoped to table, insert operation and acting actor; an empty string disables deduplication. During the 24-hour Redis replay window, the same payload returns the original ticket. If a held batch outlives that marker or Redis loses it, the database backstop can return a new ticket with the same staged_change_id/process_id, without a second staged change. Overlong persistence keys use an operation-scoped digest. A duplicate arriving before dispatch is bound may return retryable 409 with Retry-After: 1. |
match_column | string | null | 否 | Upsert mode: a column display name or internal col_<hex> id. When set, each row UPDATES the existing (non-deleted) record in this table whose match_column value equals the row's value, and inserts only when no match is found. When null (default) every row is inserted. |
records | object[] | 是 | List of record data objects keyed by column IDs (max 50000 per request) |
回應結構
BulkTaskResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
replayed | boolean | null | 否 | True when this response is an idempotent replay: the idempotency_key matched a ticket-bound in-flight/completed submission for the same (table, operation-kind, actor), so ticket_id is the ORIGINAL ticket and NO new batch was dispatched. Null/false on a fresh submission that queued a new batch. |
ticket_id | string | 是 | Ticket ID |
範例
派送兩筆資料列的背景新增工作
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"worker": "legacy"
},
"body": {
"records": [
{
"品項": "原子筆",
"數量": 10
},
{
"品項": "橡皮擦",
"數量": 4
}
],
"created_by_ai": false,
"idempotency_key": "insert-demo-001"
}
}{
"ticket_id": "ct-bulk-77777777-7777-4777-8777-777777777777",
"message": "Bulk insert task submitted",
"replayed": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 403 | 呼叫者沒有此資料表的新增權限。 | |
| 404 | 表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。 | |
| 422 | Body 不符合模型:未知 key(例如 camelCase 拼錯)、清單為空或超過 50,000 筆、idempotency_key 超過 128 字元,或(bulk-insert)match_column 超過 255 字元。沒有提交任何工作。 | |
| 423 | 派送當下資料表正被結構遷移鎖定。 | |
| 409 | 先前一個帶相同 idempotency_key 的請求仍在派送中,且在約兩秒的短暫輪詢內沒有綁定到 ticket。回應附 Retry-After: 1;請用同一個 key 重試。 | |
| 409 | 滾動部署期間,舊版伺服器保留了這個 idempotency_key。請在 replay 期間過後重試,或改用新 key 提交。 | |
| 500 | 提交給 batch、sqs 或 pubsub worker 時拋出錯誤(「Failed to submit task to AWS Batch: …」或「Failed to submit task to SQS-ECS: …」)。Ticket 會寫成 failed,idempotency_key 也會繼續綁定它,所以要重新執行必須換新 key。 | |
| 503 | 冪等狀態儲存在已看到或已嘗試保留之後失敗。回應附 Retry-After: 1;請用同一個 key 重試。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/bulk-update派送最多 50,000 筆局部更新,立即回傳追蹤 ticket。
大量既有資料列可獨立更新,且允許部分資料失敗時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/bulk-update - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/bulk-update - 公司
/private/module/custom_tables/company/tables/{table_id}/records/bulk-update
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableEditRequired |
| 部門 | 可使用 | CustomTableEditRequired |
| 公司 | 可使用 | CustomTableEditRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要批次更新資料列的自訂資料表 UUID。 |
worker | query | enum:legacy|batch|sqs|pubsub | 否 | 背景執行後端;可選 legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(未設定或無效時為 legacy)。 |
請求結構
BulkUpdatePayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
idempotency_key | string | null | 否 | Replay protection scoped to (table, operation-kind=update, acting actor): re-dispatching the same bulk-update with the same idempotency_key inside the 24h ticket window returns the ORIGINAL ticket_id instead of applying the batch a second time — makes a timed-out retry safe. A different operation (insert/delete), a different actor, or a different table reusing this key is an INDEPENDENT batch and never replays this ticket. When the batch is HELD by an approval rule, a resubmit with the same key returns the SAME held ticket (staged change + process) instead of colliding with its own pending rows. Empty string is treated as omitted; values up to 128 characters preserve the established wire contract and use an operation-scoped digest when the persisted composite would be long. A duplicate that arrives before dispatch is bound short-polls for the original ticket; if it remains pending, the endpoint returns retryable 409 with Retry-After: 1. |
updates | BulkUpdateItem[] | 是 | List of record updates (record_id + partial data) (max 50000 per request) |
回應結構
BulkTaskResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
replayed | boolean | null | 否 | True when this response is an idempotent replay: the idempotency_key matched a ticket-bound in-flight/completed submission for the same (table, operation-kind, actor), so ticket_id is the ORIGINAL ticket and NO new batch was dispatched. Null/false on a fresh submission that queued a new batch. |
ticket_id | string | 是 | Ticket ID |
範例
派送一筆資料列的背景更新工作
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"worker": "legacy"
},
"body": {
"updates": [
{
"record_id": "33333333-3333-4333-8333-333333333333",
"data": {
"col_b2222222_2222_4222_8222_222222222222": 8
}
}
],
"idempotency_key": "update-demo-001"
}
}{
"ticket_id": "ct-bulk-77777777-7777-4777-8777-777777777777",
"message": "Bulk update task submitted",
"replayed": null
}用 expected_version 保護更新(樂觀鎖)
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"worker": "legacy"
},
"body": {
"updates": [
{
"record_id": "33333333-3333-4333-8333-333333333333",
"data": {
"col_b2222222_2222_4222_8222_222222222222": 8
},
"expected_version": 4
}
],
"idempotency_key": "update-demo-002"
}
}{
"ticket_id": "ct-bulk-77777777-7777-4777-8777-777777777777",
"message": "Bulk update task submitted",
"replayed": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 403 | 呼叫者沒有此資料表的編輯權限。 | |
| 404 | 表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。 | |
| 422 | payload 帶了 model 沒宣告的 key——包含把真 key 打錯字。整批都沒有送出。forbid 收斂之前這會回 200、未知 key 被無聲丟掉;如果打錯的是 expected_version,你以為有的保護其實不存在。 | |
| 423 | 派送當下資料表正被結構遷移鎖定。 | |
| 409 | 先前一個帶相同 idempotency_key 的請求仍在派送中,且在約兩秒的短暫輪詢內沒有綁定到 ticket。回應附 Retry-After: 1;請用同一個 key 重試。 | |
| 409 | 滾動部署期間,舊版伺服器保留了這個 idempotency_key。請在 replay 期間過後重試,或改用新 key 提交。 | |
| 500 | 提交給 batch、sqs 或 pubsub worker 時拋出錯誤(「Failed to submit task to AWS Batch: …」或「Failed to submit task to SQS-ECS: …」)。Ticket 會寫成 failed,idempotency_key 也會繼續綁定它,所以要重新執行必須換新 key。 | |
| 503 | 冪等狀態儲存在已看到或已嘗試保留之後失敗。回應附 Retry-After: 1;請用同一個 key 重試。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/records/bulk-delete派送最多 50,000 個資料列 ID 的背景軟刪除工作,立即回傳追蹤 ticket。
需要大量移入資料列垃圾桶,且可接受逐筆失敗時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/records/bulk-delete - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/records/bulk-delete - 公司
/private/module/custom_tables/company/tables/{table_id}/records/bulk-delete
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableEditRequired |
| 部門 | 可使用 | CustomTableEditRequired |
| 公司 | 可使用 | CustomTableEditRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要批次刪除資料列的自訂資料表 UUID。 |
worker | query | enum:legacy|batch|sqs|pubsub | 否 | 背景執行後端;可選 legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(未設定或無效時為 legacy)。 |
請求結構
BulkDeletePayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
idempotency_key | string | null | 否 | Replay protection scoped to (table, operation-kind=delete, acting actor): re-dispatching the same bulk-delete with the same idempotency_key inside the 24h ticket window returns the ORIGINAL ticket_id instead of re-running the batch — makes a timed-out retry safe. A different operation (insert/update), a different actor, or a different table reusing this key is an INDEPENDENT batch and never replays this ticket. When the batch is HELD by an approval rule, a resubmit with the same key returns the SAME held ticket (staged change + process) instead of colliding with its own pending rows. Empty string is treated as omitted; values up to 128 characters preserve the established wire contract and use an operation-scoped digest when the persisted composite would be long. A duplicate that arrives before dispatch is bound short-polls for the original ticket; if it remains pending, the endpoint returns retryable 409 with Retry-After: 1. |
record_ids | string[] | 是 | List of record IDs to soft-delete (max 50000 per request) |
回應結構
BulkTaskResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
replayed | boolean | null | 否 | True when this response is an idempotent replay: the idempotency_key matched a ticket-bound in-flight/completed submission for the same (table, operation-kind, actor), so ticket_id is the ORIGINAL ticket and NO new batch was dispatched. Null/false on a fresh submission that queued a new batch. |
ticket_id | string | 是 | Ticket ID |
範例
派送一筆資料列的背景刪除工作
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"worker": "legacy"
},
"body": {
"record_ids": [
"44444444-4444-4444-8444-444444444444"
],
"idempotency_key": "delete-demo-001"
}
}{
"ticket_id": "ct-bulk-77777777-7777-4777-8777-777777777777",
"message": "Bulk delete task submitted",
"replayed": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 `write_policy` 是 `commands_only`:任何不在 Command 之內的主體所做的資料列寫入,都會在任何東西被暫存、排入佇列或 commit 之前,以 typed 的 `governed_context_required` 被拒絕(不會產生版本、history、receipt、token 或 Review process)。`operation` 指出是哪種寫入,若該通道有名稱則以 `surface` 指出;請改用被允許寫入該資料表的 Command。讀取不受影響。 | |
| 403 | 呼叫者沒有此資料表的編輯權限。 | |
| 404 | 表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。 | |
| 422 | Body 不符合模型:未知 key(例如 camelCase 拼錯)、清單為空或超過 50,000 筆、idempotency_key 超過 128 字元,或(bulk-insert)match_column 超過 255 字元。沒有提交任何工作。 | |
| 423 | 派送當下資料表正被結構遷移鎖定。 | |
| 409 | 先前一個帶相同 idempotency_key 的請求仍在派送中,且在約兩秒的短暫輪詢內沒有綁定到 ticket。回應附 Retry-After: 1;請用同一個 key 重試。 | |
| 409 | 滾動部署期間,舊版伺服器保留了這個 idempotency_key。請在 replay 期間過後重試,或改用新 key 提交。 | |
| 500 | 提交給 batch、sqs 或 pubsub worker 時拋出錯誤(「Failed to submit task to AWS Batch: …」或「Failed to submit task to SQS-ECS: …」)。Ticket 會寫成 failed,idempotency_key 也會繼續綁定它,所以要重新執行必須換新 key。 | |
| 503 | 冪等狀態儲存在已看到或已嘗試保留之後失敗。回應附 Retry-After: 1;請用同一個 key 重試。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions為部門或公司資料表開啟由 moderator 擁有的 staging session,供後續原子性發布資料集。
當資料集必須先分批驗證,再一次對讀者可見時使用;也可選擇取代 ACL 限制後的既有 live rows。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/publish-sessions - 公司
/private/module/custom_tables/company/tables/{table_id}/publish-sessions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要發布資料的部門或公司資料表 UUID。 |
請求結構
PublishSessionCreatePayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
idempotency_key | string | null | 否 | Create replay protection scoped to (table, acting user): re-creating with the same key while the original session has not expired returns the SAME session (replayed=true) instead of opening a second one. An empty string is treated as omitted. |
replace_filter | StoredFilterPredicate[] | null | 否 | Which LIVE rows the commit replaces: every non-deleted record matching ALL predicates (AND) is soft-deleted in the same transaction that inserts the staged rows — additionally floored by the acting user's row ACL and SCP scope at commit time. Omitted/empty = append-only publish (no deletes). Validated (compiled) at create time; a predicate on an unknown/computed column is a 400. Chunk staging checks each row against the LIVE rows, not against the replace outcome: a row that collides with a row this replace would delete (the same unique value, or an overlapping date_interval_exclusion period in the same partition) is refused at staging with 400 `invalid_rows`. Replace such a row onto a free value/interval, or change the live row first. |
回應結構
PublishSessionCreateResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
expires_at | string (date-time) | 是 | Hard TTL (create + 24h): after this, chunks and commit are rejected and the session expires |
replayed | boolean | 否 | True when idempotency_key matched an existing non-expired session — no new session was opened |
session_id | string | 是 | Publish session UUID — path id for chunks/commit/abort/status |
範例
開啟 append-only publish session
{
"pathParams": {
"department_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"idempotency_key": "catalog-publish-20260820"
}
}{
"session_id": "33333333-3333-4333-8333-333333333333",
"expires_at": "2026-08-21T00:00:00Z",
"replayed": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 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 | replace_filter 無效、引用未知或 computed 欄位,或無法編譯。 | |
| 404 | 這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。 | |
| 409 | 資料表帶有 require_approval;publish session 無法進入該 review hold。 | |
| 409 | 同一資料表的唯一 active slot 已被另一個 open 或 committing session 占用。 | |
| 409 | 資料表帶有 channel rule,但這條 moderator REST lane 沒有 acting-room carrier,因此會在開啟 session 前直接拒絕。 | |
| 422 | Body 含有 idempotency_key 與 replace_filter 以外的 key、idempotency_key 超過 128 字元,或 replace_filter 超過 20 個 predicate。 | |
| 423 | Schema 或 maintenance operation 正持有 table lock。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions/{session_id}/chunks驗證一個有編號的 chunk,並在 open publish session 內原子性取代該 chunk slot。
在 commit 完整資料集前,用來一次上傳 1 到 5,000 筆 create-shape rows。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/publish-sessions/{session_id}/chunks - 公司
/private/module/custom_tables/company/tables/{table_id}/publish-sessions/{session_id}/chunks
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | Publish session 所屬資料表 UUID。 |
session_id | path | uuid | 是 | 開啟 publish session 時回傳的 UUID。 |
請求結構
PublishChunkPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
records | object[] | 是 | Record data objects (create-path shape: display names OR internal col_<hex> keys; link/attachment cells accept the single-record create shapes). Whole session is capped at 5000 staged rows (v1). |
seq | integer | 是 | Chunk slot (0..63). Resubmitting the same seq atomically REPLACES that chunk's staged rows (retry-safe). |
回應結構
PublishChunkResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
accepted | integer | 是 | Rows staged from this chunk (== len(records)) |
staged_total | integer | 是 | Total rows currently staged across all chunks of the session |
範例
暫存前兩筆資料
{
"pathParams": {
"department_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"session_id": "33333333-3333-4333-8333-333333333333"
},
"body": {
"seq": 0,
"records": [
{
"Order number": "A-1001",
"Status": "open"
},
{
"Order number": "A-1002",
"Status": "open"
}
]
}
}{
"accepted": 2,
"staged_total": 2
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 至少一筆 row 未通過 schema 或 advisory-rule 驗證;整個 chunk 會被拒絕並回傳逐列錯誤。 | |
| 403 | 呼叫者雖是 table moderator,但不是開啟此 session 的 moderator。 | |
| 409 | Session 已到期或不再是 open。 | |
| 404 | 這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。 | |
| 404 | 這張表格沒有此 id 的 publish session。 | |
| 409 | Session 已超過 24 小時期限。這次請求會當場讓它過期、清掉已 staged 的資料列、釋放表格的發布 slot,並回 session_expired;請開啟新的 session。 | |
| 413 | 取代這個 chunk 後,session 會超過 5,000 筆 staged rows。 | |
| 422 | seq 不在 0–63 範圍、records 為空或超過 5,000 筆,或 body 含有 seq 與 records 以外的 key。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions/{session_id}/commit派送非同步 atomic flip,在同一 transaction 內 soft-delete 已授權的 replace set,並插入所有 staged rows。
所有 chunk 都已接受,而且 session status 的 staged_count 符合預期後使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/publish-sessions/{session_id}/commit - 公司
/private/module/custom_tables/company/tables/{table_id}/publish-sessions/{session_id}/commit
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | Publish session 所屬資料表 UUID。 |
session_id | path | uuid | 是 | Open publish session UUID。 |
worker | query | enum:legacy|batch|sqs|pubsub | 否 | 背景 worker lane:legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(該變數未設定或無效時為 legacy)。 |
回應結構
PublishCommitResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
replayed | boolean | 否 | True when the session was already committing/committed — the existing ticket is returned, nothing was dispatched again |
ticket_id | string | 是 | Commit worker ticket for progress tracking via GET /task/{ticket_id} |
範例
派送原子性 publish commit
{
"pathParams": {
"department_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"session_id": "33333333-3333-4333-8333-333333333333"
},
"query": {
"worker": "legacy"
}
}{
"ticket_id": "ct-publish-44444444-4444-4444-8444-444444444444",
"replayed": false
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | Session 尚未暫存任何 row。 | |
| 403 | 呼叫者雖是 moderator,但不是此 session 的建立者。 | |
| 409 | Session 已到期、已 abort,或因其他原因不是 open。 | |
| 404 | 這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。 | |
| 404 | 這張表格沒有此 id 的 publish session。 | |
| 409 | Session 已超過 24 小時期限。這次請求會當場讓它過期、清掉已 staged 的資料列、釋放表格的發布 slot,並回 session_expired;請開啟新的 session。 | |
| 423 | Schema 或 maintenance operation 正持有 table lock;session 仍保持 open。 | |
| 500 | 把 commit 提交給 batch、sqs 或 pubsub worker 時拋出錯誤。detail 為「Failed to submit task to AWS Batch: …」或「Failed to submit task to SQS-ECS: …」,ticket 會寫成 failed,error_message 為「Batch submission failed: …」或「SQS submission failed: …」。Session 會停在 committing;見派送失敗的說明。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions/{session_id}/abort刪除所有 staged rows,並釋放資料表的 active publish slot。
Open session 不再需要 commit、應直接捨棄時使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/publish-sessions/{session_id}/abort - 公司
/private/module/custom_tables/company/tables/{table_id}/publish-sessions/{session_id}/abort
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | Publish session 所屬資料表 UUID。 |
session_id | path | uuid | 是 | Open publish session UUID。 |
回應結構
PublishSessionStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
committed_at | string (date-time) | null | 否 | When the commit transaction landed (status == committed) |
created_at | string (date-time) | 是 | When the session was opened |
error | object | null | 否 | Structured failure detail of the last commit attempt (error_code, message, seq/row_index attribution when a staged row caused it); the session is back to `open` and retryable when set |
expires_at | string (date-time) | 是 | Hard TTL (create + 24h) |
replace_filter | StoredFilterPredicate[] | null | 否 | The replace filter captured at create; null = append-only publish |
session_id | string | 是 | Publish session UUID |
staged_count | integer | 是 | Rows currently staged (0 after commit/abort — staged rows are dropped) |
status | "open" | "committing" | "committed" | "aborted" | "expired" | 是 | open | committing | committed | aborted | expired |
table_id | string | 是 | Parent table UUID |
ticket_id | string | null | 否 | Latest commit worker ticket (null before the first commit) |
範例
中止 open session
{
"pathParams": {
"department_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"session_id": "33333333-3333-4333-8333-333333333333"
}
}{
"session_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"status": "aborted",
"staged_count": 0,
"replace_filter": null,
"error": null,
"created_at": "2026-08-20T00:00:00Z",
"expires_at": "2026-08-21T00:00:00Z",
"committed_at": null,
"ticket_id": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 呼叫者雖是 moderator,但不是此 session 的建立者。 | |
| 409 | 只有 open session 能 abort;已 committing 的 session 無法由這裡取消。 | |
| 404 | 這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。 | |
| 404 | 這張表格沒有此 id 的 publish session。 | |
| 409 | Session 已超過 24 小時期限。這次請求會當場讓它過期、清掉已 staged 的資料列、釋放表格的發布 slot,並回 session_expired;請開啟新的 session。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions/{session_id}回傳 staged count、生命週期狀態、ticket、時間戳、replace filter 與最近一次結構化 commit error,但不揭露 staged row 內容。
用來監控 publish,或說明為何另一位 moderator 正占用資料表的 active slot。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/publish-sessions/{session_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/publish-sessions/{session_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | Publish session 所屬資料表 UUID。 |
session_id | path | uuid | 是 | 要檢查的 publish session UUID。 |
回應結構
PublishSessionStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
committed_at | string (date-time) | null | 否 | When the commit transaction landed (status == committed) |
created_at | string (date-time) | 是 | When the session was opened |
error | object | null | 否 | Structured failure detail of the last commit attempt (error_code, message, seq/row_index attribution when a staged row caused it); the session is back to `open` and retryable when set |
expires_at | string (date-time) | 是 | Hard TTL (create + 24h) |
replace_filter | StoredFilterPredicate[] | null | 否 | The replace filter captured at create; null = append-only publish |
session_id | string | 是 | Publish session UUID |
staged_count | integer | 是 | Rows currently staged (0 after commit/abort — staged rows are dropped) |
status | "open" | "committing" | "committed" | "aborted" | "expired" | 是 | open | committing | committed | aborted | expired |
table_id | string | 是 | Parent table UUID |
ticket_id | string | null | 否 | Latest commit worker ticket (null before the first commit) |
範例
檢查 open publish session
{
"pathParams": {
"department_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"session_id": "33333333-3333-4333-8333-333333333333"
}
}{
"session_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"status": "open",
"staged_count": 2,
"replace_filter": null,
"error": null,
"created_at": "2026-08-20T00:00:00Z",
"expires_at": "2026-08-21T00:00:00Z",
"committed_at": null,
"ticket_id": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。 | |
| 404 | Session 不存在於此資料表,或屬於另一張資料表。 | |
/private/module/custom_tables/{scopeName}/{scope_id}/generations暫存前綁定預期筆數、原始輸入 digest 與選填精確總額。
透過單一啟用柵欄,向多張表新增資料。
- 部門
/private/module/custom_tables/department/{scope_id}/generations - 公司
/private/module/custom_tables/company/{scope_id}/generations
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | GenerationCreateAccess (moderator on every manifest table) |
| 公司 | 可使用 | GenerationCreateAccess (moderator on every manifest table) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
scope_id | path | uuid | 是 | 所屬公司/部門 UUID;company 路徑也包含此段。 |
請求結構
GenerationCreatePayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
idempotency_key | string | 是 | 未指定 |
tables | GenerationTableManifest[] | 是 | 未指定 |
ttl_seconds | integer | 否 | 未指定 |
回應結構
GenerationStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
activation_receipt | GenerationActivationReceipt | null | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
error | CtProblem | null | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
generation_id | string (uuid) | 是 | 未指定 |
manifest_digest | string | 是 | 未指定 |
staged_count | integer | 是 | 未指定 |
status | "open" | "active" | "aborted" | "expired" | "failed" | 是 | 未指定 |
範例
第一張表宣告一筆,第二張表宣告零筆
{
"pathParams": {
"scope_id": "11111111-1111-4111-8111-111111111111",
"generation_id": "44444444-4444-4444-8444-444444444444",
"table_id": "22222222-2222-4222-8222-222222222222",
"seq": "0"
},
"body": {
"idempotency_key": "load-20261003",
"ttl_seconds": 3600,
"tables": [
{
"table_id": "22222222-2222-4222-8222-222222222222",
"row_count": 1,
"digest": "c52d860ff12fb18e7f45918ee3c024e97dcd9f9b6ffed1a260d5178c0b27d858"
},
{
"table_id": "33333333-3333-4333-8333-333333333333",
"row_count": 0,
"digest": "4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945"
}
]
}
}{
"generation_id": "44444444-4444-4444-8444-444444444444",
"status": "open",
"manifest_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"staged_count": 0,
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T01:00:00Z",
"activation_receipt": null,
"error": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 409 | 相同 create key、不同輸入:此 key 的建議為 do_not_retry。 | |
/private/module/custom_tables/{scopeName}/{scope_id}/generations/{generation_id}取得具權威性的狀態,不重複寫入有效資料。
結果不明時讀回,包含 ct.operation_outcome_unknown。
- 部門
/private/module/custom_tables/department/{scope_id}/generations/{generation_id} - 公司
/private/module/custom_tables/company/{scope_id}/generations/{generation_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | GenerationAccess(mutate=false) |
| 公司 | 可使用 | GenerationAccess(mutate=false) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
scope_id | path | uuid | 是 | 所屬公司/部門 UUID;company 路徑也包含此段。 |
generation_id | path | uuid | 是 | 伺服器產生的 generation UUID;handle 不授予權限。 |
回應結構
GenerationStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
activation_receipt | GenerationActivationReceipt | null | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
error | CtProblem | null | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
generation_id | string (uuid) | 是 | 未指定 |
manifest_digest | string | 是 | 未指定 |
staged_count | integer | 是 | 未指定 |
status | "open" | "active" | "aborted" | "expired" | "failed" | 是 | 未指定 |
範例
讀取持久化的 active 收據
{
"pathParams": {
"scope_id": "11111111-1111-4111-8111-111111111111",
"generation_id": "44444444-4444-4444-8444-444444444444",
"table_id": "22222222-2222-4222-8222-222222222222",
"seq": "0"
}
}{
"generation_id": "44444444-4444-4444-8444-444444444444",
"status": "active",
"manifest_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"staged_count": 0,
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T01:00:00Z",
"activation_receipt": {
"receipt_id": "66666666-6666-4666-8666-666666666666",
"generation_id": "44444444-4444-4444-8444-444444444444",
"manifest_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"activated_at": "2026-10-03T00:01:00Z",
"total_records": 1,
"record_ids_digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"tables": [
{
"table_id": "22222222-2222-4222-8222-222222222222",
"row_count": 1
},
{
"table_id": "33333333-3333-4333-8333-333333333333",
"row_count": 0
}
]
},
"error": null
}/private/module/custom_tables/{scopeName}/{scope_id}/generations/{generation_id}/tables/{table_id}/chunks/{seq}儲存私有輸入與分配的 ID,不公開為有效資料列。
以可重試 chunk 填滿宣告的 manifest。
- 部門
/private/module/custom_tables/department/{scope_id}/generations/{generation_id}/tables/{table_id}/chunks/{seq} - 公司
/private/module/custom_tables/company/{scope_id}/generations/{generation_id}/tables/{table_id}/chunks/{seq}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | GenerationAccess(mutate=true) |
| 公司 | 可使用 | GenerationAccess(mutate=true) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
scope_id | path | uuid | 是 | 所屬公司/部門 UUID;company 路徑也包含此段。 |
generation_id | path | uuid | 是 | 伺服器產生的 generation UUID;handle 不授予權限。 |
table_id | path | uuid | 是 | Manifest 已宣告且屬於選定範圍的資料表 UUID。 |
seq | path | int:0..10000 | 是 | Chunk 序號 0–10000;依序號,再依列索引組合。 |
請求結構
GenerationChunkPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
records | Record<string, JsonValue>[] | 是 | 未指定 |
回應結構
GenerationChunkResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
digest | string | 是 | 未指定 |
generation_id | string (uuid) | 是 | 未指定 |
record_ids | string (uuid)[] | 是 | 未指定 |
seq | integer | 是 | 未指定 |
staged_count | integer | 是 | 未指定 |
table_id | string (uuid) | 是 | 未指定 |
範例
在序號 0 暫存 [{"Code":"A"}]
{
"pathParams": {
"scope_id": "11111111-1111-4111-8111-111111111111",
"generation_id": "44444444-4444-4444-8444-444444444444",
"table_id": "22222222-2222-4222-8222-222222222222",
"seq": "0"
},
"body": {
"records": [
{
"Code": "A"
}
]
}
}{
"generation_id": "44444444-4444-4444-8444-444444444444",
"table_id": "22222222-2222-4222-8222-222222222222",
"seq": 0,
"staged_count": 1,
"record_ids": [
"55555555-5555-4555-8555-555555555555"
],
"digest": "c52d860ff12fb18e7f45918ee3c024e97dcd9f9b6ffed1a260d5178c0b27d858"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 409 | 相同資料表/序號、不同輸入:此序號的建議為 do_not_retry。 | |
/private/module/custom_tables/{scopeName}/{scope_id}/generations/{generation_id}/activate檢查 manifest、目前定義、原生規則與最終彙總,再一起發布所有資料列與收據。
全部 chunk 已暫存,完整原始陣列 digest 也吻合。
- 部門
/private/module/custom_tables/department/{scope_id}/generations/{generation_id}/activate - 公司
/private/module/custom_tables/company/{scope_id}/generations/{generation_id}/activate
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | GenerationAccess(mutate=true) |
| 公司 | 可使用 | GenerationAccess(mutate=true) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
scope_id | path | uuid | 是 | 所屬公司/部門 UUID;company 路徑也包含此段。 |
generation_id | path | uuid | 是 | 伺服器產生的 generation UUID;handle 不授予權限。 |
請求結構
GenerationActivatePayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
manifest_digest | string | 是 | 未指定 |
回應結構
GenerationStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
activation_receipt | GenerationActivationReceipt | null | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
error | CtProblem | null | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
generation_id | string (uuid) | 是 | 未指定 |
manifest_digest | string | 是 | 未指定 |
staged_count | integer | 是 | 未指定 |
status | "open" | "active" | "aborted" | "expired" | "failed" | 是 | 未指定 |
範例
使用伺服器回傳的 manifest commitment
{
"pathParams": {
"scope_id": "11111111-1111-4111-8111-111111111111",
"generation_id": "44444444-4444-4444-8444-444444444444",
"table_id": "22222222-2222-4222-8222-222222222222",
"seq": "0"
},
"body": {
"manifest_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}{
"generation_id": "44444444-4444-4444-8444-444444444444",
"status": "active",
"manifest_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"staged_count": 0,
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T01:00:00Z",
"activation_receipt": {
"receipt_id": "66666666-6666-4666-8666-666666666666",
"generation_id": "44444444-4444-4444-8444-444444444444",
"manifest_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"activated_at": "2026-10-03T00:01:00Z",
"total_records": 1,
"record_ids_digest": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"tables": [
{
"table_id": "22222222-2222-4222-8222-222222222222",
"row_count": 1
},
{
"table_id": "33333333-3333-4333-8333-333333333333",
"row_count": 0
}
]
},
"error": null
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 409 | Manifest 未完整;保持 open,不產生有效資料變更。 | |
/private/module/custom_tables/{scopeName}/{scope_id}/generations/{generation_id}/abort關閉 open generation,只丟棄私有 payload。
啟用前取消;不能撤銷 active 資料列。
- 部門
/private/module/custom_tables/department/{scope_id}/generations/{generation_id}/abort - 公司
/private/module/custom_tables/company/{scope_id}/generations/{generation_id}/abort
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | GenerationAccess(mutate=true) |
| 公司 | 可使用 | GenerationAccess(mutate=true) |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
scope_id | path | uuid | 是 | 所屬公司/部門 UUID;company 路徑也包含此段。 |
generation_id | path | uuid | 是 | 伺服器產生的 generation UUID;handle 不授予權限。 |
回應結構
GenerationStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
activation_receipt | GenerationActivationReceipt | null | 是 | 未指定 |
created_at | string (date-time) | 是 | 未指定 |
error | CtProblem | null | 是 | 未指定 |
expires_at | string (date-time) | 是 | 未指定 |
generation_id | string (uuid) | 是 | 未指定 |
manifest_digest | string | 是 | 未指定 |
staged_count | integer | 是 | 未指定 |
status | "open" | "active" | "aborted" | "expired" | "failed" | 是 | 未指定 |
範例
關閉為 aborted,不產生有效資料變更
{
"pathParams": {
"scope_id": "11111111-1111-4111-8111-111111111111",
"generation_id": "44444444-4444-4444-8444-444444444444",
"table_id": "22222222-2222-4222-8222-222222222222",
"seq": "0"
}
}{
"generation_id": "44444444-4444-4444-8444-444444444444",
"status": "aborted",
"manifest_digest": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"staged_count": 0,
"created_at": "2026-10-03T00:00:00Z",
"expires_at": "2026-10-03T01:00:00Z",
"activation_receipt": null,
"error": null
}