Skip to Content
API 參考批次操作

批次操作

同步執行原子混合寫入、派送允許部分成功的大量工作,或先暫存部門/公司資料集,再一次原子發布。下方卡片包含五個 publish-session 操作;完整生命週期與重試協定見原子資料集發布指南。

卡片也列出暫存世代:僅追加、不可見的多表輸入與一次同步啟用,和取代式 publish session、部分成功 worker 不同。

POST
路由範本/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_idpathuuid是要執行混合批次的自訂資料表 UUID。

請求結構

BulkRecordPayload
欄位型別必填說明
actionsBulkRecordAction[]是List of insert/update/delete actions (max 100 per request)

回應結構

BulkRecordResponse
欄位型別必填說明
deletedinteger是Number of records deleted
insertedinteger是Number of records inserted
messagestring否未指定
updatedinteger是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"
      }
    ]
  }
}
回應200
{
  "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"
      }
    ]
  }
}
回應200
{
  "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。讀取不受影響。
{
  "detail": {
    "error": "governed_context_required",
    "message": "This table accepts record writes only through Commands (write_policy: commands_only). Run a Command that is allowed to write it instead.",
    "table_id": "22222222-2222-4222-8222-222222222222",
    "operation": "bulk"
  }
}
409`date_interval_exclusion` 規則拒絕此次寫入:`date_interval_exclusion_invalid_period`(建立時省略 end key、結束日為空字串或不晚於起始日,或 scope link 沒有恰好一個目標)、`date_interval_exclusion_overlap`(另一筆 live 資料列涵蓋了這段期間的一部分),或 `date_interval_exclusion_retry`(寫入等待期間資料表的規則被改變;沒有寫入任何東西,請重送)。body 是固定訊息,絕不會指出資料列、日期或 scope 值。
{
  "detail": {
    "error": "date_interval_exclusion_overlap",
    "message": "Date period overlaps another period in this partition."
  }
}
409排除送審者(`exclude_requester`)之後,某個必要的審批 gate 沒有任何可核准者。送出會在任何 review process 出現之前就被拒絕;`gate_order` 指出該 gate。對於不管理每一張被引用資料表的 `authority: definition` command 呼叫者,只會回傳安全的 key,所以沒有 `gate_order`。
{
  "detail": {
    "error": "no_eligible_approver",
    "message": "Excluding the requester leaves a required review gate with no eligible approver",
    "gate_order": 1
  }
}
409這筆寫入需要排除送審者,但呼叫的主體沒有已驗證的人類身分(例如 service account),無法歸屬。不會暫存任何東西。
{
  "detail": {
    "error": "requester_unattributable",
    "message": "This review requires the requester to be excluded, but the submitting principal has no verified human identity"
  }
}
400某個 action 結構不合法(例如 Action N: 'data' is required for insert);索引從 0 起算。
{
  "detail": "Action 1: 'data' is required for insert"
}
400某個 action 寫入的 link 值指向不存在、已被 soft delete,或被你的 row ACL 藏起來的目標。這條路由把底層的 ValueError 映成 400,所以統一的參照失敗在這裡是帶字串 detail 的 400——不是單筆通道回的 404。三種原因的訊息刻意完全相同:分開它們就等於讓寫入變成 existence oracle。
{
  "detail": "linked record not found"
}
400任何一個 action 的資料列帶有行內二進位,整個請求就失敗——這條 lane 是單一交易。`column` 會指出是哪個欄位。
{
  "detail": {
    "error": "inline_binary_not_allowed",
    "column": "附件備份",
    "reason": "base64-run",
    "message": "Cell '附件備份' holds inline binary/base64 content (base64-run); upload the file as an attachment (attachment column or the blob API) and store its reference instead"
  }
}
400Predicate rule 否決了某個 action(「Rule '<name>' violated: <reason>」)。批次是原子的,所以沒有套用任何變更。
{
  "detail": "Rule 'require-quantity' violated: '數量' is required"
}
403路由 dependency 拒絕:呼叫者對此表沒有編輯權。即使批次只有 insert 也需要這項權利。
{
  "detail": "Edit access not granted for this table."
}
403批次含 insert action,但呼叫者沒有此表的新增權限。
{
  "detail": "You do not have insert permission on this table."
}
403own 編輯者指定了他人建立的資料列。REST 較早執行的 own/filtered pre-pass 會先檢查所有目標,仍回這個帶資料列 ID 的 403;與一般單筆寫入不同,此 pre-pass 不會依可讀範圍遮蔽。
{
  "detail": "You can only edit/delete records you created (record '33333333-3333-4333-8333-333333333333')."
}
403受 filter 限制的編輯者指定了可讀但不在其 edit filter 內的資料列,或某個 action 會讓資料列移出 filter。批次執行前檢查變更前的資料,執行中檢查變更後的資料。
{
  "detail": "This record is outside your editable scope."
}
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 可能是字串也可能是物件。
{
  "detail": {
    "error": "scp_out_of_scope",
    "message": "link target(s) mv-2: the link target exists but is outside your channel scope",
    "table_id": "22222222-2222-4222-8222-222222222222"
  }
}
404表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this chatroom"
}
404REST 較早執行的 own/filtered pre-pass 對不存在的目標,仍回此處帶資料列 ID 的字串。之後的 mutation 檢查使用 {detail: "Record not found"}。不要假設所有 bulk 拒絕都使用一般資料列的 body。
{
  "detail": "Record '33333333-3333-4333-8333-333333333333' not found"
}
409資料表的 require_approval 規則把整批寫入暫存等待覆核(BulkApprovalConflict409Response)。
{
  "detail": {
    "error": "approval_required",
    "process_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
    "rule_id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
    "rule_label": "require_approval",
    "staged_change_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd"
  }
}
409在有 require_approval 的表格上,某個目標資料列已被另一個待審的 staged change 占住。沒有 stage 任何東西;請等該變更被核准或拒絕。
{
  "detail": {
    "error": "approval_pending",
    "process_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
    "staged_change_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd"
  }
}
409unique 規則拒絕 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。
{
  "detail": {
    "constraint": "unique-item",
    "columns": [
      "品項"
    ],
    "conflicting_record_id": "44444444-4444-4444-8444-444444444444",
    "key_tuple": [
      "鉛筆"
    ]
  }
}
409某個 update action 帶了 expected_version,而資料列當下的 version 不同。比對是在 refresh 過的資料列上、於 SELECT … FOR UPDATE 之下進行。這批是 atomic,所以什麼都沒套用——衝突之前的 action 沒有,之後的也沒有。action_index 是你送出的 actions 陣列中以 0 起算的位置。
{
  "detail": {
    "error": "version_conflict",
    "message": "Action 1: record was modified concurrently (current version 5, expected 4); the atomic batch was rolled back — re-read and retry.",
    "action_index": 1,
    "record_id": "33333333-3333-4333-8333-333333333333",
    "current_version": 5,
    "expected_version": 4
  }
}
409同一道 guard、不同的答案:當 require_approval 規則命中這一批時,expected_version 會帶進 staging 並在 staging lock 下複驗,而 atomic 呼叫端拿到的是這個、不是 version_conflict——沒有 action_index、沒有版本數字,只有被動過的 record ids。沒有 stage、也沒有執行。這條路由上不要只認 version_conflict。
{
  "detail": {
    "error": "record_changed_during_staging",
    "message": "a target record changed while the batch was being staged; nothing was staged or executed — retry",
    "record_ids": [
      "33333333-3333-4333-8333-333333333333"
    ]
  }
}
409有序批次的 action 在先前暫時 action 之後才進入核准保留。所有暫時效果回滾;拆開批次後重試,不保留部分執行。
{
  "detail": {
    "error": "approval_routing_changed",
    "message": "An ordered action enters an approval hold; split the batch and retry."
  }
}
422expected_version 被送在 insert 或 delete action 上。它只在 update action 有效——insert 沒有前一個版本,delete 也沒有資料需要保護。在任何 action 執行之前就被 model 擋下。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "actions",
        0
      ],
      "msg": "Value error, expected_version is only valid on update actions — an insert has no prior version and delete carries no data to guard",
      "input": {
        "action": "insert",
        "data": {
          "品項": "鉛筆"
        },
        "expected_version": 4
      }
    }
  ]
}
422某個 action 的 data 帶了 id。資料列 id 由伺服器產生;update 或 delete 的目標請放在 record_id。
{
  "detail": [
    {
      "type": "value_error",
      "loc": [
        "body",
        "actions",
        0,
        "data"
      ],
      "msg": "Value error, The 'id' field cannot be provided in data — it is auto-generated",
      "input": {
        "id": "33333333-3333-4333-8333-333333333333",
        "品項": "鉛筆"
      }
    }
  ]
}
423資料表正被結構遷移鎖定。
{
  "detail": "Table is currently locked for operation: add_column"
}
429此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。
{
  "detail": {
    "error": "staged_cap_exceeded",
    "scope": "actor",
    "limit": 500
  }
}
429此表或此操作者的待覆核暫存變更已達上限(staged_cap_exceeded)。
{
  "detail": {
    "error": "staged_cap_exceeded",
    "scope": "table",
    "limit": 50
  }
}
413任何一個 action 儲存後會超過上限,整個請求就失敗。更新是以合併後的資料列量測,且只拒絕變大。
{
  "detail": {
    "error": "record_too_large",
    "message": "Record data is 1310720 bytes; the limit is 1048576 bytes",
    "limit": 1048576,
    "size": 1310720
  }
}
POST
路由範本/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_idpathuuid是要批次新增資料列的自訂資料表 UUID。
workerqueryenum:legacy|batch|sqs|pubsub否背景執行後端;可選 legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(未設定或無效時為 legacy)。

請求結構

BulkInsertPayload
欄位型別必填說明
created_by_aiboolean否Whether records are created by AI
idempotency_keystring | 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_columnstring | 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.
recordsobject[]是List of record data objects keyed by column IDs (max 50000 per request)

回應結構

BulkTaskResponse
欄位型別必填說明
messagestring否未指定
replayedboolean | 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_idstring是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"
  }
}
回應200
{
  "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。讀取不受影響。
{
  "detail": {
    "error": "governed_context_required",
    "message": "This table accepts record writes only through Commands (write_policy: commands_only). Run a Command that is allowed to write it instead.",
    "table_id": "22222222-2222-4222-8222-222222222222",
    "operation": "create",
    "surface": "bulk_insert"
  }
}
403呼叫者沒有此資料表的新增權限。
{
  "detail": "Insert access not granted for this table."
}
404表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this chatroom"
}
422Body 不符合模型:未知 key(例如 camelCase 拼錯)、清單為空或超過 50,000 筆、idempotency_key 超過 128 字元,或(bulk-insert)match_column 超過 255 字元。沒有提交任何工作。
{
  "detail": [
    {
      "type": "extra_forbidden",
      "loc": [
        "body",
        "matchColumn"
      ],
      "msg": "Extra inputs are not permitted",
      "input": "品項"
    }
  ]
}
423派送當下資料表正被結構遷移鎖定。
{
  "detail": "Table is currently locked for operation: add_column"
}
409先前一個帶相同 idempotency_key 的請求仍在派送中,且在約兩秒的短暫輪詢內沒有綁定到 ticket。回應附 Retry-After: 1;請用同一個 key 重試。
{
  "detail": "A bulk insert with this idempotency_key is already in progress"
}
409滾動部署期間,舊版伺服器保留了這個 idempotency_key。請在 replay 期間過後重試,或改用新 key 提交。
{
  "detail": "A bulk insert with this idempotency_key was submitted during a deployment transition; retry after the replay window"
}
500提交給 batch、sqs 或 pubsub worker 時拋出錯誤(「Failed to submit task to AWS Batch: …」或「Failed to submit task to SQS-ECS: …」)。Ticket 會寫成 failed,idempotency_key 也會繼續綁定它,所以要重新執行必須換新 key。
{
  "detail": "Failed to submit task to AWS Batch: <error>"
}
503冪等狀態儲存在已看到或已嘗試保留之後失敗。回應附 Retry-After: 1;請用同一個 key 重試。
{
  "detail": "Bulk insert idempotency state is temporarily unavailable; retry with the same key"
}
POST
路由範本/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_idpathuuid是要批次更新資料列的自訂資料表 UUID。
workerqueryenum:legacy|batch|sqs|pubsub否背景執行後端;可選 legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(未設定或無效時為 legacy)。

請求結構

BulkUpdatePayload
欄位型別必填說明
idempotency_keystring | 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.
updatesBulkUpdateItem[]是List of record updates (record_id + partial data) (max 50000 per request)

回應結構

BulkTaskResponse
欄位型別必填說明
messagestring否未指定
replayedboolean | 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_idstring是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"
  }
}
回應200
{
  "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"
  }
}
回應200
{
  "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。讀取不受影響。
{
  "detail": {
    "error": "governed_context_required",
    "message": "This table accepts record writes only through Commands (write_policy: commands_only). Run a Command that is allowed to write it instead.",
    "table_id": "22222222-2222-4222-8222-222222222222",
    "operation": "update",
    "surface": "bulk_update"
  }
}
403呼叫者沒有此資料表的編輯權限。
{
  "detail": "Edit access not granted for this table."
}
404表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this chatroom"
}
422payload 帶了 model 沒宣告的 key——包含把真 key 打錯字。整批都沒有送出。forbid 收斂之前這會回 200、未知 key 被無聲丟掉;如果打錯的是 expected_version,你以為有的保護其實不存在。
{
  "detail": [
    {
      "type": "extra_forbidden",
      "loc": [
        "body",
        "updates",
        0,
        "expectedversion"
      ],
      "msg": "Extra inputs are not permitted",
      "input": 4
    }
  ]
}
423派送當下資料表正被結構遷移鎖定。
{
  "detail": "Table is currently locked for operation: add_column"
}
409先前一個帶相同 idempotency_key 的請求仍在派送中,且在約兩秒的短暫輪詢內沒有綁定到 ticket。回應附 Retry-After: 1;請用同一個 key 重試。
{
  "detail": "A bulk update with this idempotency_key is already in progress"
}
409滾動部署期間,舊版伺服器保留了這個 idempotency_key。請在 replay 期間過後重試,或改用新 key 提交。
{
  "detail": "A bulk update with this idempotency_key was submitted during a deployment transition; retry after the replay window"
}
500提交給 batch、sqs 或 pubsub worker 時拋出錯誤(「Failed to submit task to AWS Batch: …」或「Failed to submit task to SQS-ECS: …」)。Ticket 會寫成 failed,idempotency_key 也會繼續綁定它,所以要重新執行必須換新 key。
{
  "detail": "Failed to submit task to AWS Batch: <error>"
}
503冪等狀態儲存在已看到或已嘗試保留之後失敗。回應附 Retry-After: 1;請用同一個 key 重試。
{
  "detail": "Bulk update idempotency state is temporarily unavailable; retry with the same key"
}
POST
路由範本/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_idpathuuid是要批次刪除資料列的自訂資料表 UUID。
workerqueryenum:legacy|batch|sqs|pubsub否背景執行後端;可選 legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(未設定或無效時為 legacy)。

請求結構

BulkDeletePayload
欄位型別必填說明
idempotency_keystring | 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_idsstring[]是List of record IDs to soft-delete (max 50000 per request)

回應結構

BulkTaskResponse
欄位型別必填說明
messagestring否未指定
replayedboolean | 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_idstring是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"
  }
}
回應200
{
  "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。讀取不受影響。
{
  "detail": {
    "error": "governed_context_required",
    "message": "This table accepts record writes only through Commands (write_policy: commands_only). Run a Command that is allowed to write it instead.",
    "table_id": "22222222-2222-4222-8222-222222222222",
    "operation": "delete",
    "surface": "bulk_delete"
  }
}
403呼叫者沒有此資料表的編輯權限。
{
  "detail": "Edit access not granted for this table."
}
404表格不在路由的 scope 內。detail 會指出 scope:「Table not found in this chatroom」、「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this chatroom"
}
422Body 不符合模型:未知 key(例如 camelCase 拼錯)、清單為空或超過 50,000 筆、idempotency_key 超過 128 字元,或(bulk-insert)match_column 超過 255 字元。沒有提交任何工作。
{
  "detail": [
    {
      "type": "extra_forbidden",
      "loc": [
        "body",
        "ids"
      ],
      "msg": "Extra inputs are not permitted",
      "input": [
        "44444444-4444-4444-8444-444444444444"
      ]
    }
  ]
}
423派送當下資料表正被結構遷移鎖定。
{
  "detail": "Table is currently locked for operation: add_column"
}
409先前一個帶相同 idempotency_key 的請求仍在派送中,且在約兩秒的短暫輪詢內沒有綁定到 ticket。回應附 Retry-After: 1;請用同一個 key 重試。
{
  "detail": "A bulk delete with this idempotency_key is already in progress"
}
409滾動部署期間,舊版伺服器保留了這個 idempotency_key。請在 replay 期間過後重試,或改用新 key 提交。
{
  "detail": "A bulk delete with this idempotency_key was submitted during a deployment transition; retry after the replay window"
}
500提交給 batch、sqs 或 pubsub worker 時拋出錯誤(「Failed to submit task to AWS Batch: …」或「Failed to submit task to SQS-ECS: …」)。Ticket 會寫成 failed,idempotency_key 也會繼續綁定它,所以要重新執行必須換新 key。
{
  "detail": "Failed to submit task to AWS Batch: <error>"
}
503冪等狀態儲存在已看到或已嘗試保留之後失敗。回應附 Retry-After: 1;請用同一個 key 重試。
{
  "detail": "Bulk delete idempotency state is temporarily unavailable; retry with the same key"
}
POST
路由範本/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions

開啟 publish session

用途

為部門或公司資料表開啟由 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_idpathuuid是要發布資料的部門或公司資料表 UUID。

請求結構

PublishSessionCreatePayload
欄位型別必填說明
idempotency_keystring | 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_filterStoredFilterPredicate[] | 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_atstring (date-time)是Hard TTL (create + 24h): after this, chunks and commit are rejected and the session expires
replayedboolean否True when idempotency_key matched an existing non-expired session — no new session was opened
session_idstring是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"
  }
}
回應201
{
  "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。讀取不受影響。
{
  "detail": {
    "error": "governed_context_required",
    "message": "This table accepts record writes only through Commands (write_policy: commands_only). Run a Command that is allowed to write it instead.",
    "table_id": "22222222-2222-4222-8222-222222222222",
    "operation": "publish",
    "surface": "publish_session"
  }
}
400replace_filter 無效、引用未知或 computed 欄位,或無法編譯。
{
  "detail": "Invalid replace_filter: column 'unknown' does not exist"
}
404這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this department"
}
409資料表帶有 require_approval;publish session 無法進入該 review hold。
{
  "detail": {
    "error": "approval_conflict",
    "message": "Table carries a require_approval rule; publish sessions cannot run on gated tables (the atomic flip cannot be held for review)"
  }
}
409同一資料表的唯一 active slot 已被另一個 open 或 committing session 占用。
{
  "detail": {
    "error": "session_conflict",
    "message": "Another publish session is already open/committing on this table",
    "session_id": "33333333-3333-4333-8333-333333333333"
  }
}
409資料表帶有 channel rule,但這條 moderator REST lane 沒有 acting-room carrier,因此會在開啟 session 前直接拒絕。
{
  "detail": {
    "error": "channel_rule_conflict",
    "message": "Table carries a channel rule; publish sessions cannot run on channel-governed tables (the REST lane carries no acting room, so the commit worker's channel floor would deny every row)"
  }
}
422Body 含有 idempotency_key 與 replace_filter 以外的 key、idempotency_key 超過 128 字元,或 replace_filter 超過 20 個 predicate。
{
  "detail": [
    {
      "type": "extra_forbidden",
      "loc": [
        "body",
        "replace"
      ],
      "msg": "Extra inputs are not permitted",
      "input": true
    }
  ]
}
423Schema 或 maintenance operation 正持有 table lock。
{
  "detail": "Table is currently locked for operation: add_column"
}
POST
路由範本/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions/{session_id}/chunks

暫存 publish chunk

用途

驗證一個有編號的 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_idpathuuid是Publish session 所屬資料表 UUID。
session_idpathuuid是開啟 publish session 時回傳的 UUID。

請求結構

PublishChunkPayload
欄位型別必填說明
recordsobject[]是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).
seqinteger是Chunk slot (0..63). Resubmitting the same seq atomically REPLACES that chunk's staged rows (retry-safe).

回應結構

PublishChunkResponse
欄位型別必填說明
acceptedinteger是Rows staged from this chunk (== len(records))
staged_totalinteger是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"
      }
    ]
  }
}
回應200
{
  "accepted": 2,
  "staged_total": 2
}

錯誤

驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。

狀態發生條件錯誤本文
400至少一筆 row 未通過 schema 或 advisory-rule 驗證;整個 chunk 會被拒絕並回傳逐列錯誤。
{
  "detail": {
    "error": "invalid_rows",
    "message": "Chunk rejected: one or more rows failed validation (fix and resubmit the SAME seq)",
    "seq": 0,
    "rows": [
      {
        "row_index": 1,
        "errors": [
          "Column 'Status' has an invalid value"
        ]
      }
    ],
    "error_count": 1
  }
}
403呼叫者雖是 table moderator,但不是開啟此 session 的 moderator。
{
  "detail": {
    "error": "not_session_owner",
    "message": "Only the moderator who opened this publish session can stage chunks, commit, or abort it"
  }
}
409Session 已到期或不再是 open。
{
  "detail": {
    "error": "session_not_open",
    "message": "Publish session is 'committing'; chunks are accepted only while open",
    "status": "committing"
  }
}
404這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this department"
}
404這張表格沒有此 id 的 publish session。
{
  "detail": "Publish session not found on this table"
}
409Session 已超過 24 小時期限。這次請求會當場讓它過期、清掉已 staged 的資料列、釋放表格的發布 slot,並回 session_expired;請開啟新的 session。
{
  "detail": {
    "error": "session_expired",
    "message": "Publish session expired; open a new session",
    "session_id": "33333333-3333-4333-8333-333333333333"
  }
}
413取代這個 chunk 後,session 會超過 5,000 筆 staged rows。
{
  "detail": {
    "error": "session_row_cap_exceeded",
    "message": "Session cap is 5000 staged rows (5000 already staged in other chunks)",
    "limit": 5000
  }
}
422seq 不在 0–63 範圍、records 為空或超過 5,000 筆,或 body 含有 seq 與 records 以外的 key。
{
  "detail": [
    {
      "type": "less_than_equal",
      "loc": [
        "body",
        "seq"
      ],
      "msg": "Input should be less than or equal to 63",
      "input": 64,
      "ctx": {
        "le": 63
      }
    }
  ]
}
POST
路由範本/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions/{session_id}/commit

提交 publish session

用途

派送非同步 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_idpathuuid是Publish session 所屬資料表 UUID。
session_idpathuuid是Open publish session UUID。
workerqueryenum:legacy|batch|sqs|pubsub否背景 worker lane:legacy、batch、sqs 或 pubsub。省略時使用部署的 DEFAULT_BACKGROUND_WORKER_TYPE(該變數未設定或無效時為 legacy)。

回應結構

PublishCommitResponse
欄位型別必填說明
replayedboolean否True when the session was already committing/committed — the existing ticket is returned, nothing was dispatched again
ticket_idstring是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"
  }
}
回應200
{
  "ticket_id": "ct-publish-44444444-4444-4444-8444-444444444444",
  "replayed": false
}

錯誤

驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。

狀態發生條件錯誤本文
400Session 尚未暫存任何 row。
{
  "detail": {
    "error": "empty_session",
    "message": "Nothing staged; submit at least one chunk before committing"
  }
}
403呼叫者雖是 moderator,但不是此 session 的建立者。
{
  "detail": {
    "error": "not_session_owner",
    "message": "Only the moderator who opened this publish session can stage chunks, commit, or abort it"
  }
}
409Session 已到期、已 abort,或因其他原因不是 open。
{
  "detail": {
    "error": "session_not_open",
    "message": "Publish session is 'aborted' and cannot be committed",
    "status": "aborted"
  }
}
404這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this department"
}
404這張表格沒有此 id 的 publish session。
{
  "detail": "Publish session not found on this table"
}
409Session 已超過 24 小時期限。這次請求會當場讓它過期、清掉已 staged 的資料列、釋放表格的發布 slot,並回 session_expired;請開啟新的 session。
{
  "detail": {
    "error": "session_expired",
    "message": "Publish session expired; open a new session",
    "session_id": "33333333-3333-4333-8333-333333333333"
  }
}
423Schema 或 maintenance operation 正持有 table lock;session 仍保持 open。
{
  "detail": "Table is currently locked for operation: add_column"
}
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;見派送失敗的說明。
{
  "detail": "Failed to submit task to SQS-ECS: <error>"
}
POST
路由範本/private/module/custom_tables/{scope}/tables/{table_id}/publish-sessions/{session_id}/abort

中止 publish session

用途

刪除所有 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_idpathuuid是Publish session 所屬資料表 UUID。
session_idpathuuid是Open publish session UUID。

回應結構

PublishSessionStatusResponse
欄位型別必填說明
committed_atstring (date-time) | null否When the commit transaction landed (status == committed)
created_atstring (date-time)是When the session was opened
errorobject | 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_atstring (date-time)是Hard TTL (create + 24h)
replace_filterStoredFilterPredicate[] | null否The replace filter captured at create; null = append-only publish
session_idstring是Publish session UUID
staged_countinteger是Rows currently staged (0 after commit/abort — staged rows are dropped)
status"open" | "committing" | "committed" | "aborted" | "expired"是open | committing | committed | aborted | expired
table_idstring是Parent table UUID
ticket_idstring | 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"
  }
}
回應200
{
  "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 的建立者。
{
  "detail": {
    "error": "not_session_owner",
    "message": "Only the moderator who opened this publish session can stage chunks, commit, or abort it"
  }
}
409只有 open session 能 abort;已 committing 的 session 無法由這裡取消。
{
  "detail": {
    "error": "session_not_open",
    "message": "Publish session is 'committing'; only an open session can be aborted",
    "status": "committing"
  }
}
404這張表格不屬於此部門或公司。detail 會指出 scope:「Table not found in this department」或「Table not found in this company」。
{
  "detail": "Table not found in this department"
}
404這張表格沒有此 id 的 publish session。
{
  "detail": "Publish session not found on this table"
}
409Session 已超過 24 小時期限。這次請求會當場讓它過期、清掉已 staged 的資料列、釋放表格的發布 slot,並回 session_expired;請開啟新的 session。
{
  "detail": {
    "error": "session_expired",
    "message": "Publish session expired; open a new session",
    "session_id": "33333333-3333-4333-8333-333333333333"
  }
}
GET
路由範本/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_idpathuuid是Publish session 所屬資料表 UUID。
session_idpathuuid是要檢查的 publish session UUID。

回應結構

PublishSessionStatusResponse
欄位型別必填說明
committed_atstring (date-time) | null否When the commit transaction landed (status == committed)
created_atstring (date-time)是When the session was opened
errorobject | 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_atstring (date-time)是Hard TTL (create + 24h)
replace_filterStoredFilterPredicate[] | null否The replace filter captured at create; null = append-only publish
session_idstring是Publish session UUID
staged_countinteger是Rows currently staged (0 after commit/abort — staged rows are dropped)
status"open" | "committing" | "committed" | "aborted" | "expired"是open | committing | committed | aborted | expired
table_idstring是Parent table UUID
ticket_idstring | 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"
  }
}
回應200
{
  "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」。
{
  "detail": "Table not found in this department"
}
404Session 不存在於此資料表,或屬於另一張資料表。
{
  "detail": "Publish session not found on this table"
}
POST
路由範本/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_idpathuuid是所屬公司/部門 UUID;company 路徑也包含此段。

請求結構

GenerationCreatePayload
欄位型別必填說明
idempotency_keystring是未指定
tablesGenerationTableManifest[]是未指定
ttl_secondsinteger否未指定

回應結構

GenerationStatusResponse
欄位型別必填說明
activation_receiptGenerationActivationReceipt | null是未指定
created_atstring (date-time)是未指定
errorCtProblem | null是未指定
expires_atstring (date-time)是未指定
generation_idstring (uuid)是未指定
manifest_digeststring是未指定
staged_countinteger是未指定
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"
      }
    ]
  }
}
回應201
{
  "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。
{
  "detail": [
    {
      "type": "ct.request_key_reuse",
      "loc": [
        "generation"
      ],
      "msg": "Generation request key has different input",
      "retry_advice": "do_not_retry",
      "operation_id": null
    }
  ]
}
GET
路由範本/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_idpathuuid是所屬公司/部門 UUID;company 路徑也包含此段。
generation_idpathuuid是伺服器產生的 generation UUID;handle 不授予權限。

回應結構

GenerationStatusResponse
欄位型別必填說明
activation_receiptGenerationActivationReceipt | null是未指定
created_atstring (date-time)是未指定
errorCtProblem | null是未指定
expires_atstring (date-time)是未指定
generation_idstring (uuid)是未指定
manifest_digeststring是未指定
staged_countinteger是未指定
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"
  }
}
回應200
{
  "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
}
PUT
路由範本/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_idpathuuid是所屬公司/部門 UUID;company 路徑也包含此段。
generation_idpathuuid是伺服器產生的 generation UUID;handle 不授予權限。
table_idpathuuid是Manifest 已宣告且屬於選定範圍的資料表 UUID。
seqpathint:0..10000是Chunk 序號 0–10000;依序號,再依列索引組合。

請求結構

GenerationChunkPayload
欄位型別必填說明
recordsRecord<string, JsonValue>[]是未指定

回應結構

GenerationChunkResponse
欄位型別必填說明
digeststring是未指定
generation_idstring (uuid)是未指定
record_idsstring (uuid)[]是未指定
seqinteger是未指定
staged_countinteger是未指定
table_idstring (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"
      }
    ]
  }
}
回應200
{
  "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。
{
  "detail": [
    {
      "type": "ct.request_key_reuse",
      "loc": [
        "generation"
      ],
      "msg": "Chunk sequence has different input",
      "retry_advice": "do_not_retry",
      "operation_id": null
    }
  ]
}
POST
路由範本/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_idpathuuid是所屬公司/部門 UUID;company 路徑也包含此段。
generation_idpathuuid是伺服器產生的 generation UUID;handle 不授予權限。

請求結構

GenerationActivatePayload
欄位型別必填說明
manifest_digeststring是未指定

回應結構

GenerationStatusResponse
欄位型別必填說明
activation_receiptGenerationActivationReceipt | null是未指定
created_atstring (date-time)是未指定
errorCtProblem | null是未指定
expires_atstring (date-time)是未指定
generation_idstring (uuid)是未指定
manifest_digeststring是未指定
staged_countinteger是未指定
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"
  }
}
回應200
{
  "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 佔位符。

狀態發生條件錯誤本文
409Manifest 未完整;保持 open,不產生有效資料變更。
{
  "detail": [
    {
      "type": "ct.activation_blocked",
      "loc": [
        "generation"
      ],
      "msg": "Generation manifest is incomplete",
      "retry_advice": "fix_input",
      "operation_id": "44444444-4444-4444-8444-444444444444"
    }
  ]
}
POST
路由範本/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_idpathuuid是所屬公司/部門 UUID;company 路徑也包含此段。
generation_idpathuuid是伺服器產生的 generation UUID;handle 不授予權限。

回應結構

GenerationStatusResponse
欄位型別必填說明
activation_receiptGenerationActivationReceipt | null是未指定
created_atstring (date-time)是未指定
errorCtProblem | null是未指定
expires_atstring (date-time)是未指定
generation_idstring (uuid)是未指定
manifest_digeststring是未指定
staged_countinteger是未指定
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"
  }
}
回應200
{
  "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
}
Last updated on