權限與存取控制
解析實際存取權、管理使用者與用戶端授權,並掌握各範圍實際提供的權限端點。
Scope matrix:departmentPermissions 只適用 chatroom table;departmentTableGrants 適用 department 與 company table;clientPermissions 與 clientAccess 適用三種 scope;chatroomPermissions 仍是 department-table-to-room 的獨立介面。路徑名稱相似,不代表可以把某個 family 擴張到其他 scope。
/private/module/custom_tables/{scope}/tables/{table_id}/permissions分別回傳資料表的隱含管理者,以及逐使用者設定的明確授權。
適合用來製作存取管理畫面,或稽核哪些人可以操作這張資料表。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permissions - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/permissions - 公司
/private/module/custom_tables/company/tables/{table_id}/permissions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableGrantRequired |
| 部門 | 可使用 | CustomTableGrantRequired |
| 公司 | 可使用 | CustomTableGrantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要列出使用者權限的資料表 UUID。 |
回應結構
TablePermissionsResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
explicit_grants | ExplicitGrantEntry[] | 是 | Users with explicit per-user permission grants |
implicit_managers | ImplicitManagerEntry[] | 是 | Users with implicit full access (creators and moderators) |
範例
列出管理者與明確授權
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
}
}{
"implicit_managers": [
{
"id": "55555555-5555-4555-8555-555555555555",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.manager",
"email": "manager@example.invalid",
"nickname": "林主管",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部",
"role": "creator"
}
],
"explicit_grants": [
{
"id": "44444444-4444-4444-8444-444444444444",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.member",
"email": "member@example.invalid",
"nickname": "陳小安",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部",
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"granted_by": {
"id": "55555555-5555-4555-8555-555555555555",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.manager",
"email": "manager@example.invalid",
"nickname": "林主管",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部"
},
"granted_at": "2026-07-19T02:05:00"
}
]
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 呼叫者或許可使用資料表,但無權檢視或管理授權。 | |
| 404 | 資料表不存在,或不屬於指定範圍。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/permissions/me解析呼叫者對單一資料表最終擁有的讀取、新增、編輯與管理權限。
適合依實際權限啟用介面操作,不必從角色自行猜測。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permissions/me - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/permissions/me - 公司
/private/module/custom_tables/company/tables/{table_id}/permissions/me
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | ChatRoomJoinedRequired |
| 部門 | 可使用 | CustomTableAccessRequired |
| 公司 | 可使用 | CustomTableAccessRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要解析呼叫者權限的資料表 UUID。 |
回應結構
MyPermissionsResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 是 | Effective edit/delete access: "none" | "own" | "all" | "filtered" (v1.2 §11) |
can_insert | boolean | 是 | Whether the user can insert records |
can_read | "none" | "own" | "all" | "filtered" | 是 | Effective read access: "none" | "own" | "all" | "filtered" (v1.2 §11) |
is_manager | boolean | 是 | Whether the user is a table manager (implicit full access) |
範例
解析目前使用者的資料表權限
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
}
}{
"can_read": "all",
"can_insert": true,
"can_edit": "all",
"is_manager": true
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 資料表不存在、不屬於此範圍,或已被存取檢查隱藏。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/permissions建立或取代某位使用者的明確讀取、新增、編輯、篩選與可見欄位設定。
某位使用者需要不同於預設值或共用範圍授權時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permissions - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/permissions - 公司
/private/module/custom_tables/company/tables/{table_id}/permissions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableGrantRequired |
| 部門 | 可使用 | CustomTableGrantRequired |
| 公司 | 可使用 | CustomTableGrantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要加入明確授權的資料表 UUID。 |
請求結構
GrantPermissionPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit/delete access: "none" | "own" | "all" | "filtered" (v1.2 §11) |
can_insert | boolean | 否 | Whether the user can insert records |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read access: "none" | "own" | "all" | "filtered" (v1.2 §11) |
edit_filter | object | null | 否 | v1.2 §11 row policy — required when can_edit="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — an and/or/not tree of {"column": col_<hex>, "op": ..., "value": ...} predicates; required when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
user_id | string | 是 | Target user UUID |
visible_columns | string[] | null | 否 | Column allowlist: INTERNAL column keys (col_<hex>) visible under this grant; all other columns are hidden (read strip + write reject + formula/export/view/history scoping). null/omitted = all columns (v1.2 column_acl still applies on top). Empty list rejected (422); unknown keys rejected (422). |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
授予完整讀取與僅可編輯自己資料的權限
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"user_id": "44444444-4444-4444-8444-444444444444",
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"visible_columns": [
"col_88888888_8888_4888_8888_888888888888"
]
}
}{
"message": "Permissions updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 目標使用者已是隱含管理者,不能再建立重複的明確授權。 | |
| 409 | 兩筆寫入爭用同一批資料列,InnoDB 中止了這一筆。交易已回滾,授權內容維持不變——直接重送請求即可。 | |
| 422 | 權限值、篩選條件或可見欄位不符合這張資料表的規格。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/permissions/bulk在單一交易中授予或撤銷多筆明確使用者權限。
適合同步成員資料,或製作多人權限編輯器。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permissions/bulk - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/permissions/bulk - 公司
/private/module/custom_tables/company/tables/{table_id}/permissions/bulk
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableGrantRequired |
| 部門 | 可使用 | CustomTableGrantRequired |
| 公司 | 可使用 | CustomTableGrantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要批次變更明確授權的資料表 UUID。 |
請求結構
BulkPermissionPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
actions | BulkPermissionAction[] | 是 | List of grant/revoke actions (max 100 per request) |
回應結構
BulkPermissionResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
granted | integer | 是 | Number of users granted/updated permissions |
message | string | 否 | 未指定 |
revoked | integer | 是 | Number of users whose permissions were revoked |
範例
以批次操作授予一位使用者權限
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"actions": [
{
"action": "grant",
"user_id": "44444444-4444-4444-8444-444444444444",
"can_read": "own",
"can_insert": true,
"can_edit": "own"
}
]
}
}{
"message": "Bulk permissions updated successfully",
"granted": 1,
"revoked": 0
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 一位以上的目標使用者不存在,或操作與資料表管理者規則衝突。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/permissions/{user_id}刪除使用者的明確 grant,使後續有效權限回到管理者、部門、聊天室或資料表預設值,而不是新增一筆 deny。
使用者不應再受到個別權限覆寫時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permissions/{user_id} - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/permissions/{user_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/permissions/{user_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableGrantRequired |
| 部門 | 可使用 | CustomTableGrantRequired |
| 公司 | 可使用 | CustomTableGrantRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有這筆明確授權的資料表 UUID。 |
user_id | path | uuid | 是 | 要移除明確授權的使用者 UUID。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
移除一筆明確使用者授權
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"user_id": "44444444-4444-4444-8444-444444444444"
}
}{
"message": "Permissions revoked successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 此使用者在這張資料表沒有明確權限列。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/default-permissions取代當管理者或明確授權都未命中時所使用的後備資料列權限。
適合設定其他未單獨配置的使用者或用戶端的基礎體驗。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/default-permissions - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/default-permissions - 公司
/private/module/custom_tables/company/tables/{table_id}/default-permissions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要更新後備權限的資料表 UUID。 |
請求結構
DefaultPermissionsPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
audience | "scope" | "company" | 否 | Who these defaults (and the system fallback) apply to. "scope" (default) = the table's own scope members, current behavior. "company" — DEPARTMENT-scoped tables only (422 elsewhere) — shares the table to ALL departments: every same-company user resolves these defaults and passes the membership door. Per-department grant rows still override per department (narrower or wider), and per-user grants override those. PATCH replaces the whole default_permissions object, so omitting audience on a later call reverts the share to "scope". |
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit/delete access level: "none" | "own" | "all" | "filtered" (v1.2 §11, requires edit_filter) |
can_insert | boolean | 否 | Whether users can insert new records |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read access level: "none" | "own" (only own records) | "all" | "filtered" (v1.2 §11, requires read_filter) |
edit_filter | object | null | 否 | v1.2 §11 row policy — REQUIRED when can_edit="filtered". Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — an and/or/not tree of {"column": col_<hex>, "op": ..., "value": ...} predicates; REQUIRED when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
開放讀取,但預設不允許寫入
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"can_read": "all",
"can_insert": false,
"can_edit": "none"
}
}{
"message": "Default permissions updated successfully"
}把部門表分享給公司裡的每一個部門
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"can_read": "all",
"can_insert": false,
"can_edit": "none",
"audience": "company"
}
}{
"message": "Default permissions updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 資料表不存在,或不屬於指定範圍。 | |
| 409 | settings 合併在伺服器自身處理之後仍輸掉資料表列鎖。沒有任何內容被合併,儲存的預設權限維持原狀——重送 PATCH;拿到 200 才代表合併真的成立。 | |
| 422 | 受篩選的權限層級缺少必要的篩選條件,或其他權限值不合法。 | |
| 422 | 在 chatroom 或 company scope 的表上送了 `audience: "company"`。detail 就是這一整句。不在 `{"scope","company"}` 內的值根本到不了這道閘——`audience` 是 `Literal`,Pydantic 會先以一般的 422 驗證 body 擋下。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/column-acl取代疊加在資料列權限之上的欄位讀取限制。
適合在不拆分資料表的情況下,對非管理者隱藏敏感欄位。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/column-acl - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/column-acl - 公司
/private/module/custom_tables/company/tables/{table_id}/column-acl
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要取代欄位 ACL 的資料表 UUID。 |
請求結構
ColumnAclPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
column_acl | Record<string, Record<string, string>> | 否 | {"col_<hex>": {"read": "all"|"managers"}} — hide a column from non-managers. Internal keys (renames do not break it). Empty = clear. |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
將單一欄位限制為僅管理者可讀
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"column_acl": {
"col_88888888_8888_4888_8888_888888888888": {
"read": "managers"
}
}
}
}{
"message": "Column ACL updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 409 | settings 合併在伺服器自身處理之後仍輸掉資料表列鎖。儲存的欄位 ACL 維持原狀——你送出的收緊「沒有」生效;重送 PATCH 直到拿到 200。 | |
| 422 | ACL 引用了不存在的欄位內部 ID,或規則無效。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions列出附加在聊天室資料表上的部門共用授權。
適合稽核哪些部門可繼承聊天室資料表的存取權。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/department-permissions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要列出部門授權的聊天室資料表 UUID。 |
回應結構
DepartmentPermissionListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
departments | DepartmentPermissionResponse[] | 是 | 未指定 |
範例
列出獲授權使用聊天室資料表的部門
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
}
}{
"departments": [
{
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部",
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"granted_by": {
"id": "55555555-5555-4555-8555-555555555555",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.manager",
"email": "manager@example.invalid",
"nickname": "林主管",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部"
},
"granted_at": "2026-07-19T02:05:00"
}
]
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 聊天室資料表不存在,或不屬於指定聊天室。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions/{department_id}在聊天室資料表建立或取代一筆部門共用權限。
同一部門的成員需要對聊天室資料表共用基礎權限時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/department-permissions/{department_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要加入授權的聊天室資料表 UUID。 |
department_id | path | uuid | 是 | 要取得存取權的目標部門 UUID,與聊天室範圍 ID 不同。 |
請求結構
DepartmentPermissionPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit access: "none" | "own" | "all" | "filtered" |
can_insert | boolean | 否 | Insert permission |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read access: "none" | "own" | "all" | "filtered" |
edit_filter | object | null | 否 | v1.2 §11 row policy — required when can_edit="filtered". Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — required when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
visible_columns | string[] | null | 否 | Column allowlist: INTERNAL column keys (col_<hex>) visible under this grant; all other columns are hidden (read strip + write reject + formula/export/view/history scoping). null/omitted = all columns (v1.2 column_acl still applies on top). Empty list rejected (422); unknown keys rejected (422). |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
授予部門使用聊天室資料表的權限
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"department_id": "33333333-3333-4333-8333-333333333333"
},
"body": {
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"visible_columns": [
"col_88888888_8888_4888_8888_888888888888"
]
}
}{
"message": "Department permissions updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 目標部門或聊天室資料表不存在於呼叫者的公司。 | |
| 422 | 權限、篩選條件或可見欄位無效。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions/{department_id}取代聊天室資料表上既有的部門共用權限。
部門授權已存在,且要以 PATCH 明確調整時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/department-permissions/{department_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有該授權的聊天室資料表 UUID。 |
department_id | path | uuid | 是 | 要調整授權的目標部門 UUID。 |
請求結構
DepartmentPermissionPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit access: "none" | "own" | "all" | "filtered" |
can_insert | boolean | 否 | Insert permission |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read access: "none" | "own" | "all" | "filtered" |
edit_filter | object | null | 否 | v1.2 §11 row policy — required when can_edit="filtered". Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — required when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
visible_columns | string[] | null | 否 | Column allowlist: INTERNAL column keys (col_<hex>) visible under this grant; all other columns are hidden (read strip + write reject + formula/export/view/history scoping). null/omitted = all columns (v1.2 column_acl still applies on top). Empty list rejected (422); unknown keys rejected (422). |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
降低現有部門授權
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"department_id": "33333333-3333-4333-8333-333333333333"
},
"body": {
"can_read": "own",
"can_insert": false,
"can_edit": "none"
}
}{
"message": "Department permissions updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 部門權限列不存在。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions/{department_id}刪除聊天室資料表上的部門明確 grant;這只移除該權限覆寫,不會刪除部門、資料表或其中資料。
部門不應再受到這筆直接共用授權時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/department-permissions/{department_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有部門授權的聊天室資料表 UUID。 |
department_id | path | uuid | 是 | 要移除授權的目標部門 UUID。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
移除部門授權
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"department_id": "33333333-3333-4333-8333-333333333333"
}
}{
"message": "Department permissions revoked successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 部門權限列不存在。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions回傳 department/company scope 表上所有以「部門」為 principal 的授權列,附部門名稱、授權者與時間;沒有授權時 `departments` 為空陣列。
在放寬或撤銷前,用它稽核哪些部門取得了這張部門或公司資料表。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/department-permissions - 公司
/private/module/custom_tables/company/tables/{table_id}/department-permissions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 路由所指定、仍存活的 department/company scope 資料表 UUID。 |
回應結構
DepartmentPermissionListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
departments | DepartmentPermissionResponse[] | 是 | 未指定 |
範例
列出部門或公司資料表已授權的部門
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222"
}
}{
"departments": [
{
"department_id": "99999999-9999-4999-8999-999999999999",
"department_name": "客服部",
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"granted_by": {
"id": "55555555-5555-4555-8555-555555555555",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.manager",
"email": "manager@example.invalid",
"nickname": "林主管",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部"
},
"granted_at": "2026-07-19T02:05:00"
}
]
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 該表的 effective company 與呼叫者不同。租戶錨點跑在 explicit-moderator 捷徑之前,因此一筆過期的跨公司 moderator 列在這裡不再能授權任何事。 | |
| 404 | 由 moderator dependency 拋出:整個系統裡根本沒有這個 table id。它可能先於 route-specific scope resolver 觸發,因此字串與路由自己的 `Table not found` 不同。 | |
| 404 | 這個 id 在指定的部門或公司 scope 解析不到 live table;其他 scope 的表與已在垃圾桶的表回應相同。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions/{target_department_id}在 department/company scope table 上為一個部門 upsert 完整 grant row,等同 IaC `grant` 行搭配 `principal.type: "department"`。
把部門或公司資料表分享給同公司的指定部門時使用;若所有部門都應繼承,應明確設定 company-audience default,而不是逐部門建立 row。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/department-permissions/{target_department_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/department-permissions/{target_department_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有此授權的 department/company scope table UUID。 |
target_department_id | path | uuid | 是 | 接受授權的同公司部門;在 department route 上,它與 owning scope 的 `department_id` 不同。 |
請求結構
DepartmentPermissionPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit access: "none" | "own" | "all" | "filtered" |
can_insert | boolean | 否 | Insert permission |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read access: "none" | "own" | "all" | "filtered" |
edit_filter | object | null | 否 | v1.2 §11 row policy — required when can_edit="filtered". Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — required when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
visible_columns | string[] | null | 否 | Column allowlist: INTERNAL column keys (col_<hex>) visible under this grant; all other columns are hidden (read strip + write reject + formula/export/view/history scoping). null/omitted = all columns (v1.2 column_acl still applies on top). Empty list rejected (422); unknown keys rejected (422). |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
把部門或公司資料表分享給一個部門,並限制 row 與 column
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"target_department_id": "99999999-9999-4999-8999-999999999999"
},
"body": {
"can_read": "filtered",
"read_filter": {
"and": [
{
"column": "col_88888888_8888_4888_8888_888888888888",
"op": "eq",
"value": "已出貨"
}
]
},
"can_insert": false,
"can_edit": "none",
"visible_columns": [
"col_88888888_8888_4888_8888_888888888888"
]
}
}{
"message": "Department permissions updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 這個 id 在指定的部門或公司 scope 解析不到 live table。 | |
| 404 | 目標部門不存在於呼叫者的公司;POST 與 PATCH 都會執行這項 company-anchored 驗證。 | |
| 422 | `visible_columns` 中有不屬於這張表的欄位。請送 internal `col_<hex>` key;空陣列同樣會被拒絕,`null`/省略代表所有欄位。 | |
| 422 | Row policy 文法:level 是 `filtered` 卻沒給 filter,或在其他 level 上夾帶 filter。與所有 grant 介面同一套規則。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions/{target_department_id}取代既有的 department 授權列。Payload 是同一個 `DepartmentPermissionPayload`、語意與 POST 同樣是取代,唯一差別是該列必須已經存在。
當「授權列不存在」應該明確報錯、而不是被建立出來時使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/department-permissions/{target_department_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/department-permissions/{target_department_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有此授權的 department/company scope table UUID。 |
target_department_id | path | uuid | 是 | 要被取代授權列的部門。 |
請求結構
DepartmentPermissionPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit access: "none" | "own" | "all" | "filtered" |
can_insert | boolean | 否 | Insert permission |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read access: "none" | "own" | "all" | "filtered" |
edit_filter | object | null | 否 | v1.2 §11 row policy — required when can_edit="filtered". Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — required when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
visible_columns | string[] | null | 否 | Column allowlist: INTERNAL column keys (col_<hex>) visible under this grant; all other columns are hidden (read strip + write reject + formula/export/view/history scoping). null/omitted = all columns (v1.2 column_acl still applies on top). Empty list rejected (422); unknown keys rejected (422). |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
把既有的 department 授權收窄成只看自己的列
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"target_department_id": "99999999-9999-4999-8999-999999999999"
},
"body": {
"can_read": "own",
"can_insert": false,
"can_edit": "none"
}
}{
"message": "Department permissions updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 這個 id 在指定的部門或公司 scope 解析不到 live table。 | |
| 404 | 這張表上沒有該部門的授權列。PATCH 永遠不會建立新列——請改用 POST。 | |
| 422 | Row policy 文法錯誤或未知的 `visible_columns` key,驗證方式與 POST 完全相同。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/department-permissions/{target_department_id}從 department/company scope table 刪除一筆明確的 department grant row。
當另一個部門不該再透過這一列繼承存取權時使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/department-permissions/{target_department_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/department-permissions/{target_department_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有此授權的 department/company scope table UUID。 |
target_department_id | path | uuid | 是 | 要移除授權列的部門。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
移除部門或公司資料表上的一筆 department grant
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"target_department_id": "99999999-9999-4999-8999-999999999999"
}
}{
"message": "Department permissions revoked successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 這個 id 在指定的部門或公司 scope 解析不到 live table。 | |
| 404 | 這張表上沒有該部門的授權列;對同一列 DELETE 第二次就會拿到這個。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/moderators回傳部門或公司資料表明確指定的管理者。
適合顯示或稽核誰可以管理共用範圍資料表。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/moderators - 公司
/private/module/custom_tables/company/tables/{table_id}/moderators
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableAccessRequired |
| 公司 | 可使用 | CustomTableAccessRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 部門或公司資料表 UUID。 |
回應結構
List[ModeratorResponse]OpenAPI 快照中沒有此模型的頂層元件欄位。
範例
列出部門資料表的管理者
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222"
}
}[
{
"id": "55555555-5555-4555-8555-555555555555",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.manager",
"email": "manager@example.invalid",
"nickname": "林主管",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部"
}
]錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 資料表不存在,或不屬於指定部門或公司。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/moderators/{user_id}將同公司的某位使用者提升為部門或公司資料表的明確管理者。
要將共用範圍資料表的結構、規則與權限管理委派給他人時使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/moderators/{user_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/moderators/{user_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 部門或公司資料表 UUID。 |
user_id | path | uuid | 是 | 要設為管理者的使用者 UUID。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
新增部門資料表管理者
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"user_id": "44444444-4444-4444-8444-444444444444"
}
}{
"message": "Moderator added successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 這位使用者已是管理者。 | |
| 404 | 該表的 effective company 內沒有這個 id 的使用者。現在所有 scope 都是同一句訊息;department scope 以前會回 `User not found or not in this department`,並拒絕來自其他部門的同公司使用者。 | |
| 404 | 這張表不在被指定的 scope 內(company 介面上是 `Table not found in this company`)。與目標使用者那個 404 是不同的一種。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/moderators/{user_id}從部門或公司資料表刪除一筆明確管理者指派。
不再需要某位使用者代理管理資料表時使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/moderators/{user_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/moderators/{user_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 部門或公司資料表 UUID。 |
user_id | path | uuid | 是 | 要移除的管理者 UUID。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
移除部門資料表管理者
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"user_id": "44444444-4444-4444-8444-444444444444"
}
}{
"message": "Moderator removed successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 這位使用者不是此資料表的明確管理者。 | |
| 404 | 這張表不在被指定的 scope 內(company 介面上是 `Table not found in this company`)。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/chatroom-permissions列出可存取某張部門資料表的聊天室與受眾。
適合在變更或撤銷存取權前,稽核部門資料表已分享至哪些地方。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/chatroom-permissions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要列出聊天室授權的部門資料表 UUID。 |
回應結構
ChatroomGrantListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
grants | ChatroomGrantResponse[] | 是 | Grant rows |
範例
列出共用某張部門資料表的聊天室
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222"
}
}{
"grants": [
{
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"chatroom_name": "訂單協作室",
"audience": "internal",
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"read_filter": null,
"edit_filter": null,
"visible_columns": [
"col_88888888_8888_4888_8888_888888888888"
],
"scope_values": null,
"granted_by": {
"id": "55555555-5555-4555-8555-555555555555",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.manager",
"email": "manager@example.invalid",
"nickname": "林主管",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部"
},
"granted_at": "2026-07-19T02:05:00"
}
]
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 部門資料表不存在,或不屬於指定部門。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/chatroom-permissions/{chatroom_id}在部門資料表建立或取代一筆按受眾區分的聊天室授權。
要把部門資料分享給聊天室的內部成員、外部用戶端,或兩種受眾時使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/chatroom-permissions/{chatroom_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要分享的部門資料表 UUID。 |
chatroom_id | path | uuid | 是 | 要取得存取權的目標聊天室 UUID,與部門範圍 ID 不同。 |
請求結構
ChatroomGrantPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
audience | "internal" | "external" | 是 | Which audience this grant covers: 'internal' = the chatroom's member users; 'external' = its social media clients. |
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit level. 'filtered' requires edit_filter. |
can_insert | boolean | 否 | May the audience insert records. |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read level. 'filtered' requires read_filter. |
edit_filter | object | null | 否 | Row policy limiting editable rows when can_edit='filtered'. Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | Row policy limiting readable rows when can_read='filtered'. A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
scope_values | string[] | null | 否 | SCP v1.3 channel scope: the record ids of the linked table this chatroom may read and write through, for the link column named by the table's `channel` rule (e.g. this room's warehouses). Only meaningful on a channel-governed table. Every id must be a LIVE record of the linked table (422 otherwise). null/omitted = UNDECLARED, which DENIES every row on a governed table — 'no narrowing' is expressed by not authoring the channel rule, never by an empty or absent scope. Empty list rejected (422). |
visible_columns | string[] | null | 否 | Column allowlist: INTERNAL column keys (col_<hex>) visible under this grant; all other columns are hidden (read strip + write reject + formula/export/view/history scoping). null/omitted = all columns (v1.2 column_acl still applies on top). Empty list rejected (422); unknown keys rejected (422). |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
將部門資料表分享給聊天室內部成員
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"body": {
"audience": "internal",
"can_read": "all",
"can_insert": true,
"can_edit": "own",
"visible_columns": [
"col_88888888_8888_4888_8888_888888888888"
]
}
}{
"message": "Chatroom grant updated successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 目標聊天室不存在,或不屬於呼叫者的公司。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/chatroom-permissions/{chatroom_id}從部門資料表刪除一筆按受眾區分的聊天室授權。
聊天室或某種受眾不應再繼承部門資料表存取權時使用。
- 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/chatroom-permissions/{chatroom_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 未提供 | 不適用 |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有授權的部門資料表 UUID。 |
chatroom_id | path | uuid | 是 | 要移除授權的目標聊天室 UUID。 |
audience | query | enum:internal|external|both | 否 | 要移除的受眾資料列;省略時預設為 both。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
移除內部受眾的聊天室授權
{
"pathParams": {
"department_id": "33333333-3333-4333-8333-333333333333",
"table_id": "22222222-2222-4222-8222-222222222222",
"chatroom_id": "11111111-1111-4111-8111-111111111111"
},
"query": {
"audience": "internal"
}
}{
"message": "Chatroom grant revoked"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 該資料表、聊天室與受眾組合沒有授權。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/client-permissions回傳聊天室、部門或公司資料表上,外部或 agent client 的明確 grants。
用來稽核 direct client-specific override,並與 passphrase、room-audience、table-default access 分開檢視。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/client-permissions - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/client-permissions - 公司
/private/module/custom_tables/company/tables/{table_id}/client-permissions
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 聊天室、部門或公司資料表 UUID。 |
回應結構
ClientPermissionListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
clients | ClientPermissionResponse[] | 是 | 未指定 |
範例
列出明確用戶端授權
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
}
}{
"clients": [
{
"social_media_client_id": "66666666-6666-4666-8666-666666666666",
"client": {
"id": "66666666-6666-4666-8666-666666666666",
"nickname": "範例客戶",
"platform": "agent",
"external_id": "catalog-external-user",
"external_channel_id": "11111111-1111-4111-8111-111111111111",
"external_profile": {
"nickname": "範例客戶"
}
},
"can_read": "own",
"can_insert": true,
"can_edit": "own",
"granted_by": {
"id": "55555555-5555-4555-8555-555555555555",
"created_at": "2026-07-19T02:00:00",
"expired_at": "2027-07-19T02:00:00",
"photo_url": null,
"enabled": true,
"is_verified": true,
"username": "catalog.manager",
"email": "manager@example.invalid",
"nickname": "林主管",
"department_id": "33333333-3333-4333-8333-333333333333",
"department_name": "營運部"
},
"granted_at": "2026-07-19T02:05:00",
"granted_via": "manual"
}
]
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 資料表不存在,或不屬於指定範圍。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/client-permissions/{client_id}建立或取代一個 same-company external client 對聊天室、部門或公司資料表的明確權限。
找到需要直接覆寫的正確 social_media_client_id 後使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/client-permissions/{client_id} - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/client-permissions/{client_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/client-permissions/{client_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要加入用戶端授權的資料表 UUID。 |
client_id | path | uuid | 是 | 要取得權限的社群媒體或 agent 用戶端 UUID。 |
請求結構
ClientPermissionPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit level. 'filtered' requires edit_filter. |
can_insert | boolean | 否 | May the client insert records. |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read level. 'filtered' requires read_filter. |
edit_filter | object | null | 否 | v1.2 §11 row policy — required when can_edit="filtered". Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — required when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
visible_columns | string[] | null | 否 | Column allowlist: INTERNAL column keys (col_<hex>) visible under this grant; all other columns are hidden (read strip + write reject + formula/export/view/history scoping). null/omitted = all columns (v1.2 column_acl still applies on top). Empty list rejected (422); unknown keys rejected (422). |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
授予 agent 存取自己資料列的權限
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"client_id": "66666666-6666-4666-8666-666666666666"
},
"body": {
"can_read": "own",
"can_insert": true,
"can_edit": "own",
"visible_columns": [
"col_88888888_8888_4888_8888_888888888888"
]
}
}{
"message": "Client permissions granted"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 指定範圍內看不到該用戶端或資料表。 | |
| 422 | 權限、篩選條件或可見欄位無效。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/client-permissions/{client_id}刪除某個 client 的明確 row/column grant,使有效權限改由任何適用的 passphrase、room-audience 或 table-default policy 重新解析。
用戶端不再需要單獨覆寫時使用;撤銷後應重新檢查 fallback,不要假設有效權限必然是 none。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/client-permissions/{client_id} - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/client-permissions/{client_id} - 公司
/private/module/custom_tables/company/tables/{table_id}/client-permissions/{client_id}
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 擁有這筆用戶端授權的資料表 UUID。 |
client_id | path | uuid | 是 | 要移除明確授權的用戶端 UUID。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
移除明確用戶端授權
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"client_id": "66666666-6666-4666-8666-666666666666"
}
}{
"message": "Client permissions revoked successfully"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 這張資料表不存在該用戶端的明確權限列。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/permission-requests列出用戶端提交的待審或已審存取申請——對象可以是聊天室資料表,或分享進該聊天室的部門資料表。
適合製作用戶端存取申請的管理者審核清單。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permission-requests
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要列出申請的資料表 UUID——聊天室資料表,或分享進此聊天室的部門資料表。 |
status | query | enum:pending|approved|rejected | 否 | 選填的審核狀態:pending、approved 或 rejected。 |
回應結構
ClientPermissionRequestListResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
requests | ClientPermissionRequestResponse[] | 是 | 未指定 |
範例
列出待審用戶端申請
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"status": "pending"
}
}{
"requests": [
{
"id": "77777777-7777-4777-8777-777777777777",
"social_media_client_id": "66666666-6666-4666-8666-666666666666",
"client": {
"id": "66666666-6666-4666-8666-666666666666",
"nickname": "範例客戶",
"platform": "agent",
"external_id": "catalog-external-user",
"external_channel_id": "11111111-1111-4111-8111-111111111111",
"external_profile": {
"nickname": "範例客戶"
}
},
"custom_table_id": "22222222-2222-4222-8222-222222222222",
"table_name": "訂單",
"status": "pending",
"requested_permissions": "insert,read_all",
"requested_at": "2026-07-19T02:00:00",
"reviewed_by": null,
"reviewed_at": null,
"review_note": null
}
]
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 資料表不存在,或無法從指定聊天室看到。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/permission-requests/{request_id}/approve將待審用戶端申請標記為已核准,並寫入選定的明確用戶端授權。
管理者審閱待審申請並確定最終權限後使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permission-requests/{request_id}/approve
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 申請指向的資料表 UUID(聊天室資料表,或分享進此聊天室的部門資料表)。 |
request_id | path | uuid | 是 | 待審權限申請 UUID。 |
請求結構
ApprovePermissionRequestPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
can_edit | "none" | "own" | "all" | "filtered" | 否 | Edit access: "none" | "own" | "all" | "filtered" (v1.2 §11) |
can_insert | boolean | 否 | Insert permission |
can_read | "none" | "own" | "all" | "filtered" | 否 | Read access: "none" | "own" | "all" | "filtered" (v1.2 §11) |
edit_filter | object | null | 否 | v1.2 §11 row policy — required when can_edit="filtered". Token grammar is identical to read_filter: A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
read_filter | object | null | 否 | v1.2 §11 row policy — required when can_read="filtered". A row policy is a boolean tree over scalar predicates: a node is {"and": [node, ...]} (every child holds), {"or": [node, ...]} (at least one holds), {"not": node} (strict complement) or a predicate {"column": "col_<hex>", "op": "eq|neq|gt|gte|lt|lte|in|contains|is_null|is_not_null", "value": ...}. Groups are non-empty; a node carries exactly its kind's keys; the root may be any node (the legacy {"and": [preds]} is one such tree). Caps: depth <= 5 (a bare predicate is depth 1), <= 24 predicates, <= 100 entries per "in" list (caps bind AUTHORED policies; the server may compose wider resolved trees). Predicates reference internal column keys of stored scalar columns only. An empty/unset cell fails every value op and matches only is_null, so {"not": {col eq X}} INCLUDES blank cells while {col neq X} excludes them. Unresolvable tokens and malformed nodes always NARROW the row set (never-match under "and"/"not", dropped under "or"), never widen it. A node may also be a link_target leaf {"link": "col_<hex> of a LINK column of this table", "quantifier": "any"|"all", "target": node, "require_present": bool} — it follows the link ONE hop and evaluates "target" (any row-policy node over the LINKED table's stored scalar columns; no link node inside it: 1-hop) against the linked rows: "any" = at least one linked row matches, "all" = every linked row matches (vacuously true when none — require_present MUST be stated on "all" and must be absent on "any"). Only LIVE (not trashed) linked rows count and the linked table's SCP channel floor applies; the linked table's own row/column ACL does NOT. Tokens inside "target" resolve against the LINKED column's type ("$me" on its user column = rows assigned to me). Caps: <= 6 link leaves; a target costs one depth level and its predicates count toward the 24. A {"link", "op", "value"} membership leaf is NOT accepted — write link_target with a target {"column": "id", "op": "in", "value": [...]}. Example: {"or": [{"column": "col_a1", "op": "eq", "value": "TW"}, {"not": {"column": "col_b2", "op": "eq", "value": "$me"}}]}. Predicate values may use per-request tokens (stored RAW, substituted at permission-resolution time): "$me" = the acting principal's own id (user id; client id for social clients; the linked user's id for agent-platform clients) — ops eq/neq or as an element of an "in" list, on user/social_client/principal/string/text columns; on a principal column "$me" is a SET — the caller's own user:<id> plus room:<id> for every live chatroom they belong to (REST) or the acting chatroom only inside an agent / command session; social clients match smc:<id> only; a public-read token never matches — compiled as an "in" over tagged cells ("neq" excludes blank cells); "$me.department" = the acting user's department id — string/text columns; on a principal column it is a SET: user:<id> for every live user of that department plus room:<id> for every live chatroom of that department (the acting chatroom only inside an agent / command session, and only if it belongs to the department) — matches NO rows for clients; "$today"/"$today+Nd"/"$today-Nd" (N 1..730, Asia/Taipei day boundary) — date columns with eq/neq/gt/gte/lt/lte, datetime columns with gt/gte/lt/lte only (an eq day is unsatisfiable — author gte "$today" AND lt "$today+1d"), never inside "in" lists; "$now" = the current Asia/Taipei instant at minute precision — datetime columns with gt/gte/lt/lte only (never eq/neq, never a date column — use "$today" there — never inside an "in" list). Any other "$"-prefixed value is rejected (reserved). Grant endpoints echo the RAW token, never a resolved literal. Unlike "own" (fixed to the record creator), "$me" may target any legal column — e.g. an assigned-owner column. A token whose principal kind never matches the column (a user-audience "$me" on a social_client column) is legal but matches nothing. |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
核准讀取與新增權限
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"request_id": "77777777-7777-4777-8777-777777777777"
},
"body": {
"can_read": "all",
"can_insert": true,
"can_edit": "none"
}
}{
"message": "Permission request approved"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 申請不存在、屬於其他資料表,或已不是待審狀態。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/permission-requests/{request_id}/reject將待審用戶端申請標記為已駁回;選填的審核說明會保存下來,並隨駁回通知送達申請人。
適用於不應轉成明確 grant 的 pending 申請;review_note 會保存並隨駁回通知送達申請人,申請終結為 rejected。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/permission-requests/{request_id}/reject
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 未提供 | 不適用 |
| 公司 | 未提供 | 不適用 |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 申請指向的資料表 UUID(聊天室資料表,或分享進此聊天室的部門資料表)。 |
request_id | path | uuid | 是 | 待審權限申請 UUID。 |
請求結構
RejectPermissionRequestPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
review_note | string | null | 否 | Optional reason for rejection |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
附上審核說明駁回申請
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222",
"request_id": "77777777-7777-4777-8777-777777777777"
},
"body": {
"review_note": "請先補充使用情境"
}
}{
"message": "Permission request rejected"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 申請不存在、屬於其他資料表,或已不是待審狀態。 | |
/private/module/custom_tables/{scope}/tables/{table_id}/client-access在三種 table scope 上啟用、輪替或停用 passphrase,並取代驗證成功後授予的權限。
外部用戶端需透過密語解鎖受控資料表存取,而不是逐一手動授權時使用。
- 聊天室
/private/module/custom_tables/chatroom/{chatroom_id}/tables/{table_id}/client-access - 部門
/private/module/custom_tables/department/{department_id}/tables/{table_id}/client-access - 公司
/private/module/custom_tables/company/tables/{table_id}/client-access
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableModeratorRequired |
| 部門 | 可使用 | CustomTableModeratorRequired |
| 公司 | 可使用 | CustomTableModeratorRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 要變更 client-access 設定的聊天室、部門或公司資料表 UUID。 |
請求結構
ClientAccessConfigPayload| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
passphrase | string | null | 否 | Raw passphrase (will be bcrypt-hashed before storage). Required when enabling. |
passphrase_enabled | boolean | 是 | Whether passphrase access is enabled |
passphrase_permissions | DefaultPermissionsPayload | null | 否 | Permissions granted when a client uses the passphrase. Defaults to read-only if omitted. Row-policy token values ($me/$today/$now…) are legal here: the RAW token is copied onto each joining client's grant and resolves per request thereafter. |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
啟用密語並授予自有資料列權限
{
"pathParams": {
"chatroom_id": "11111111-1111-4111-8111-111111111111",
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"passphrase_enabled": true,
"passphrase": "catalog-demo-passphrase",
"passphrase_permissions": {
"can_read": "own",
"can_insert": true,
"can_edit": "own"
}
}
}{
"message": "Client access configuration updated"
}錯誤
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 404 | 資料表不存在,或不屬於指定的聊天室、部門或公司 scope。 | |
| 409 | settings 合併在伺服器自身處理之後仍輸掉資料表列鎖。通行碼設定維持原狀——重送 PATCH;拿到 200 才代表合併真的成立。 | |
| 422 | 密語設定或權限組合不合法。 | |