List Departments
Get a paginated list of direct child departments under the specified parent department.
API information
| Item | Description |
|---|---|
| API name | List Departments |
| Permission scope | contact.department.read (read organization structure) |
| HTTP method | POST / GET |
| Endpoint | https://{gateway_host}/open/v1/contact/department/list |
| Data format | application/json for POST or URL query |
| Authentication | App-level access_token (requires contact.department.read) |
Prerequisites
- The app has been published and enabled under "Internal Apps" in the admin console.
- A valid app-level
access_tokenhas been obtained. - The parent department must exist and be within the app's visibility scope.
Request parameters
Header
| Header | Required | Description |
|---|---|---|
Authorization | No | Bearer <access_token>, the recommended credential transport method; choose it instead of query or form authentication |
Content-Type | No | Send application/json when a POST request uses JSON |
Parameter list
Parameters may be sent in a POST JSON body, POST form, or GET query:
| Parameter | Type | Required | Description |
|---|---|---|---|
access_token | string | Yes | App-level token; required when it is not sent in the Authorization header |
dept_id | string | No | Parent department ID. Omit it or pass "0" to return top-level departments in the app's visibility scope |
cursor | string | No | Numeric pagination offset starting at 0, default 0; aliases next_cursor and nextCursor are also accepted |
size | integer | No | Number of items per page, from 1 to 100, default 100 |
language | string | No | Reserved localization parameter; it currently does not affect the response |
Request examples
POST example:
POST https://{gateway_host}/open/v1/contact/department/list
Authorization: Bearer <access_token>
Content-Type: application/json
{
"dept_id": "0",
"cursor": "0",
"size": 50
}
GET example:
GET https://{gateway_host}/open/v1/contact/department/list?dept_id=0&cursor=0&size=50
Authorization: Bearer <access_token>
Response parameters
Successful response (the platform's unified response envelope):
{
"code": 200,
"msg": "",
"data": {
"departments": [
{
"deptId": "20",
"name": "R&D department",
"parentId": "10",
"order": 1
},
{
"deptId": "30",
"name": "Marketing department",
"parentId": "10",
"order": 2
}
],
"hasMore": true,
"nextCursor": "2"
}
}
| Field | Type | Description |
|---|---|---|
code | integer | Business status code; 200 indicates success |
msg | string | Message; empty on success |
data.departments | object[] | Direct child department list |
data.departments[].deptId | string | Department ID |
data.departments[].name | string | Department name |
data.departments[].parentId | string | Parent department ID, truncated to "0" when the parent is outside the visibility scope |
data.departments[].order | integer | Sort weight among sibling departments |
data.hasMore | boolean | Whether more data is available; true means traversal is not complete |
data.nextCursor | string | Cursor for the next page; returned only when hasMore is true and sent as cursor in the next request |
Traversal and organization-tree guidance
- One level at a time: This API returns only the direct child departments of the specified department and does not recursively expand the tree.
- Recommended complete traversal:
- Step 1: call this API with
dept_id="0"to get all top-level departments in the app's visibility scope; - Step 2: recursively call this API for each department and traverse downward layer by layer;
- Step 3: call List Department Members for each department and assemble the complete contact data.
- Step 1: call this API with
- Pagination termination: use
hasMore == falseas the loop termination condition.
Visibility scope and organization traversal
The app visibility scope affects Workbench display, passwordless sign-in, and contact results. Members or departments outside the scope are treated as nonexistent.
Recommended traversal order
- Call the department-list API without
dept_idto get the top-level nodes within the visibility scope. - Recursively call the department-list API for each department until no child departments remain.
- Call the department-member-list API for each department and merge records by
userId. - Use event subscriptions as change signals and periodically perform a full reconciliation.
Semantic constraints
parentId="0"may only be a top-level node within the visibility scope and is not necessarily the real enterprise root.- After the visibility scope changes, subsequent requests are filtered by the new scope immediately; there is no need to exchange a new
access_token. - A member may belong to multiple departments;
deptIdListcontains only departments visible to the app. - Use
hasMoreas the source of truth; do not determine completion from whether the current page count is less thansize.
Organization traversal example
Shell
# Read top-level departments first, then pass nextCursor back unchanged for the next page.
curl -sS -X POST "https://{gateway_host}/open/v1/contact/department/list" -H "Authorization: Bearer access-token-xxx" -H "Content-Type: application/json" -d '{"cursor":"0","size":100}'
PHP
<?php
// Production code should traverse departments with a queue to avoid uncontrolled recursion depth.
$url = 'https://{gateway_host}/open/v1/contact/department/list?cursor=0&size=100'; $ch = curl_init($url); curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer access-token-xxx'], CURLOPT_RETURNTRANSFER => true]); echo curl_exec($ch); curl_close($ch);
Golang
package main
import "net/http"
func main() {
// A real synchronization job should use a queue and deduplicate by userId.
request, _ := http.NewRequest(http.MethodPost, "https://{gateway_host}/open/v1/contact/department/list?cursor=0&size=100", nil); request.Header.Set("Authorization", "Bearer access-token-xxx"); response, _ := http.DefaultClient.Do(request); defer response.Body.Close()
}
C++
#include <curl/curl.h>
int main() {
// Do not hard-code a single root department ID; start from the top-level nodes returned by the API.
CURL* handle = curl_easy_init(); struct curl_slist* headers = curl_slist_append(nullptr, "Authorization: Bearer access-token-xxx"); curl_easy_setopt(handle, CURLOPT_URL, "https://{gateway_host}/open/v1/contact/department/list?cursor=0&size=100"); curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(handle, CURLOPT_POST, 1L); const CURLcode result = curl_easy_perform(handle); curl_slist_free_all(headers); curl_easy_cleanup(handle); return result == CURLE_OK ? 0 : 1;
}
Events only indicate that a change may have occurred and cannot replace the final query; see Event subscription.
Data rules
- Data outside the visibility scope is treated as nonexistent; the app cannot distinguish "nonexistent" from "invisible" by error code.
- The department-list API returns direct child departments and the member-list API returns direct members; complete synchronization follows Visibility scope and organization traversal.
- Member IDs and department IDs are strings; unset optional fields may be omitted.
- List APIs use
cursor,size,hasMore, andnextCursorfor pagination.
See the Integration Guide.
Error codes
Authentication and middleware errors (HTTP status)
| HTTP status | msg error code | Description |
|---|---|---|
| 401 | INVALID_TOKEN | The token is missing, expired, revoked, or the app is unavailable |
| 403 | SCOPE_DENIED | The token lacks contact.department.read |
| 403 | IP_DENIED | The source IP is not in the IP allowlist configured in the app security settings |
Business errors (HTTP 200, code=500)
{
"code": 500,
"msg": "DEPARTMENT_NOT_FOUND",
"data": null
}
msg error code | Description and troubleshooting guidance |
|---|---|
INVALID_REQUEST | cursor is not a valid numeric offset or is out of range, or size is not between 1 and 100 |
DEPARTMENT_NOT_FOUND | The specified parent department does not exist, has been deleted, or is outside the app's visibility scope |
PERMISSION_DENIED | The app state is invalid (disabled, unpublished, or deleted) |
SERVER_ERROR | Internal server error; retry later |
Multilingual call examples
The following examples run on the server; never send app secrets or app tokens to a client.
Shell
curl -sS -X POST "https://{gateway_host}/open/v1/contact/department/list" \
-H "Authorization: Bearer access-token-xxx" \
-H "Content-Type: application/json" \
-d '{"dept_id":"0","cursor":"0","size":50}'
PHP
<?php
// Production code should check the HTTP status and response body before mapping business errors.
$handle = curl_init('https://{gateway_host}/open/v1/contact/department/list');
curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer access-token-xxx', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"dept_id":"0","cursor":"0","size":50}',
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($handle);
curl_close($handle);
Golang
package main
import (
"bytes"
"net/http"
)
func main() {
// Use the token only in a server-side request and follow the API transport contract.
request, _ := http.NewRequest(http.MethodPost, "https://{gateway_host}/open/v1/contact/department/list", bytes.NewBufferString(`{"dept_id":"0","cursor":"0","size":50}`))
request.Header.Set("Authorization", "Bearer access-token-xxx")
request.Header.Set("Content-Type", "application/json")
response, _ := http.DefaultClient.Do(request)
defer response.Body.Close()
}
C++
#include <curl/curl.h>
int main() {
// Enable TLS verification and handle HTTP errors in production.
CURL* handle = curl_easy_init();
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Authorization: Bearer access-token-xxx");
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(handle, CURLOPT_URL, "https://{gateway_host}/open/v1/contact/department/list");
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, R"({"dept_id":"0","cursor":"0","size":50})");
const CURLcode result = curl_easy_perform(handle);
curl_slist_free_all(headers);
curl_easy_cleanup(handle);
return result == CURLE_OK ? 0 : 1;
}