组织目录同步

服务端 API 更新于 2026-09-21 阅读 10

阅读指引:本篇是《开放平台服务端 API 指南》的第 10 部分。接口基础地址、通用约定与各接口请求限制,请先阅读《服务端 API 概览》。

13. 组织目录读取与可靠同步

组织接口使用 App access token,需要 contact:organization:read,并始终受发布范围约束。组织变更事件是尽量推送;应用必须使用同步状态接口弥补事件丢失,不能每次调用都直接做全量同步。

正在查找组织变化推送?事件类型、请求结构、签名、幂等和失败补偿流程见 第 10.8 节:组织目录变更事件。Webhook 只负责加速发现变化,sync-state 才是判断是否需要同步的可靠依据。

13.1 接口与通用约定

Method 路径 用途
GET /openapi/v1/organization/sync-state 比较上次同步水位,判断是否需要同步
POST /openapi/v1/organization/sync-complete 确认同一快照已经完整读取
GET /openapi/v1/organization/departments 一次读取范围内全量组织,或指定节点自身及其完整子树
GET /openapi/v1/organization/department-detail 读取指定部门详情
GET /openapi/v1/organization/department-users 分页读取指定部门直接任职用户
GET /openapi/v1/organization/user-detail 读取发布范围内用户及其可见任职
  • 所有 ID 在 JSON 中使用十进制字符串;不要转换成 JavaScript Number。
  • 四个目录读取接口拒绝未知或重复 query 参数;同步状态和完成接口也只应提交本文规定的参数。
  • 成功响应结构为 code/message/data/request_id;排障时记录 request_id
  • 全量读取期间,四个目录读取接口应带当前 X-Matrix-Directory-Sync-Tokensync-complete 则在 JSON body 提交同一个 Token。
  • 组织接口当前没有额外的接口级限流,但仍共同受来源 IP 每分钟 300 次的全局限制;禁止按部门树逐节点递归高频读取。

13.2 检查同步状态

GET /openapi/v1/organization/sync-state

curl "${OPENAPI_BASE_URL}/openapi/v1/organization/sync-state" \
  -H "Authorization: Bearer ${APP_ACCESS_TOKEN}"
curl "${OPENAPI_BASE_URL}/openapi/v1/organization/sync-state" \
  -H "Authorization: Bearer ${APP_ACCESS_TOKEN}" \
  -H 'If-None-Match: "PREVIOUS_SYNC_TOKEN"'
ETag: "CURRENT_SYNC_TOKEN"

{
  "code": 200,
  "message": "",
  "data": {
    "need_sync": true,
    "sync_token": "CURRENT_SYNC_TOKEN",
    "reason": "ORGANIZATION_CHANGED"
  },
  "request_id": "request_xxx"
}
reason 含义 处理
INITIAL_SYNC 未提供上次 Token 执行完整同步
PUBLICATION_SCOPE_CHANGED 管理员修改了应用可读边界 完整同步;新快照中删除已越界数据
ORGANIZATION_CHANGED 组织、人员或任职数据变化 完整同步
BOTH_CHANGED 发布范围与组织目录均变化 完整同步
TOKEN_EXPIRED Token 非法、过期、属于其他应用或无法识别 不要修补 Token,执行完整同步

目录与发布范围均未变化时返回 HTTP 304 和当前 ETag,无 JSON body。sync_token 是经过认证的不透明字符串,不要解析、比较内容或自行构造;应用只保存并原样回传。

13.3 完整同步算法

sync-state → 建立临时快照 → 遍历部门 → 分页读取部门用户 → 按需读取用户详情 → sync-complete → 原子替换正式快照
  1. 调用 sync-state。200 且 need_sync=true 时记录响应 Token 为“进行中 Token”,不要立刻覆盖“上次完成 Token”。
  2. 创建新的临时表或带批次号的临时快照。不要边同步边删除线上旧数据。
  3. 不带 id 调用 departments,一次获取当前发布范围内的全量组织架构。响应已经包含完整可见子树,不得再按返回 ID 逐层调用 departments。请求带进行中 Token。
  4. 对每个部门分页读取直接任职用户;按业务需要补充读取 user-detail。
  5. 任一读取返回 HTTP 409 / 170703 时,说明同步过程中目录或发布范围又发生变化:丢弃整个临时批次,重新从 sync-state 开始。
  6. 全部读取成功后调用 sync-complete。仅当返回 completed=true 才在本地事务中用临时快照替换正式快照,并保存该 Token 为上次完成 Token。

为什么必须调用 sync-completeToken 表示候选快照水位,不表示应用真的读完。complete 会再次检查目录和发布范围是否变化;变化时本轮不能提交。它不锁定后续目录变化,完成同步后仍须继续轮询。

13.4 部门列表

GET /openapi/v1/organization/departments[?id={department_id}]

curl "${OPENAPI_BASE_URL}/openapi/v1/organization/departments" \
  -H "Authorization: Bearer ${APP_ACCESS_TOKEN}" \
  -H "X-Matrix-Directory-Sync-Token: ${CURRENT_SYNC_TOKEN}"
{
  "code": 200,
  "message": "",
  "data": {
    "departments": [{
      "id": "20001",
      "parent": "10000",
      "name": "研发中心",
      "name_en": "R&D Center",
      "leader_user_ids": ["10001"],
      "order": 10,
      "has_children": true,
      "updated_at": 1788537600000
    }]
  },
  "request_id": "request_xxx"
}

不传 id 时一次返回应用发布范围内的全量组织架构;传正整数 id 时返回该部门自身及其全部 active 后代。响应是按组织顺序排列的扁平数组,应用通过 id/parent 在本地重建树,禁止对返回的每个部门再次递归调用本接口。部分组织范围的入口节点 parent 可以是范围外 ID,用于保留原始层级;应用拿该范围外 ID 查询详情仍会得到 404。

13.5 部门详情

GET /openapi/v1/organization/department-detail?id={department_id}

id 必填且只能出现一次,必须是正整数。成功时 data.department 包含 id/parent/name/name_en/leader_user_ids/order/updated_at;详情不返回 has_children,需要通过列表关系判断。部门 parent 可为 null(无父节点);非空时为十进制字符串。

13.6 部门用户

GET /openapi/v1/organization/department-users?id={id}&page={page}&page_size={size}

curl "${OPENAPI_BASE_URL}/openapi/v1/organization/department-users?id=20001&page=1&page_size=100" \
  -H "Authorization: Bearer ${APP_ACCESS_TOKEN}" \
  -H "X-Matrix-Directory-Sync-Token: ${CURRENT_SYNC_TOKEN}"
{
  "code": 200,
  "message": "",
  "data": {
    "users": [{
      "user_id": "10001",
      "name": "张三",
      "employee_no": "E10001",
      "contact_hint": null,
      "assignments_in_department": [{
        "assignment_type": "primary",
        "assignment_label_code": null,
        "assignment_label_name": null,
        "is_key_contact": false,
        "is_leader": true
      }],
      "updated_at": 1788537600000
    }],
    "page": 1,
    "page_size": 100,
    "has_more": false
  },
  "request_id": "request_xxx"
}
参数 必填 规则
id 范围内部门正整数 ID
page 省略时按 1;显式提供时必须为正整数,page=0 返回参数错误
page_size 省略或低于 100 按 100;高于 500 按 500

只返回该部门的直接任职关系,不自动把子部门成员混入。应用按 has_more 继续翻页;同一轮所有页必须使用同一个同步 Token。

13.7 用户详情

GET /openapi/v1/organization/user-detail?user_id={user_id}

{
  "code": 200,
  "message": "",
  "data": {
    "user": {
      "user_id": "10001",
      "name": "张三",
      "employee_no": "E10001",
      "contact_hint": null,
      "assignments": [{
        "department_id": "20001",
        "assignment_type": "primary",
        "assignment_label_code": null,
        "assignment_label_name": null,
        "is_key_contact": false,
        "is_leader": true
      }],
      "updated_at": 1788537600000
    }
  },
  "request_id": "request_xxx"
}

user_id 必填且必须是正整数。只有有效用户集合内的用户可读取;返回的 assignments 也受可见组织边界约束。employee_no/contact_hint/name_en/assignment_label_* 可能为 JSON null,调用方必须按可空字段处理。

部门用户分页只用于枚举组织成员,不是应用全部可见用户列表。若发布范围包含不属于任何可见部门的指定用户,遍历部门不会发现这些用户;当前没有枚举全部可见用户的 OpenAPI,只有已知 user_id 时可查询 user-detail。不要把部门枚举结果当作所有可触达用户。

13.8 确认同步完成

POST /openapi/v1/organization/sync-complete

curl -X POST "${OPENAPI_BASE_URL}/openapi/v1/organization/sync-complete" \
  -H "Authorization: Bearer ${APP_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"sync_token":"CURRENT_SYNC_TOKEN"}'

{
  "code": 200,
  "message": "",
  "data": {"completed": true},
  "request_id": "request_xxx"
}

13.9 HTTP 状态与错误码

HTTP code message 处理
400 170700 invalid_request 修正 query、分页、JSON 或 Token 参数,不重试原请求
401/403 Token/Scope 相关码 按鉴权响应 重新取得 App Token或联系管理员授权 contact:organization:read
404 170701 organization_resource_not_found 目标不存在、停用或越出发布范围;从本地快照删除/忽略
409 170703 organization_sync_version_changed 丢弃本轮临时快照,从 sync-state 重新开始
503 170702 organization_service_unavailable 有限次数指数退避,保留上次完成快照