Organization
Your organization's structural hierarchy.
Structures & Hierarchy
POST /departments
Create a new structure (Area, Department, or Unit)
User keyRequest body
| Field | Type | Flags | Required | Notes |
|---|---|---|---|---|
| level | integer | — | Required | 1 (Area), 2 (Department), or 3 (Unit). |
| title | string | — | Required | The new structure's display name. |
| name | string | — | Required | The short code — auto-uppercased, spaces stripped. Must be unique in the tenant. |
| description | string | — | Optional | A longer, free-text description of what this structure covers. |
| parent_acronym | string | — | Optional | Required for level 2 or 3 — the direct parent structure's acronym, one level up. |
| leader_user_id | integer | — | Optional | Defaults to a tenant-level fallback if omitted. |
Example request
curl -X POST "https://api.openstudio.one/v1/departments" \
-H "X-Api-User-Key: YOUR_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"level":123,"title":"...","name":"...","description":"...","parent_acronym":"...","leader_user_id":123}'<?php
$ch = curl_init('https://api.openstudio.one/v1/departments');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'level' => 123,
'title' => '...',
'name' => '...',
'description' => '...',
'parent_acronym' => '...',
'leader_user_id' => 123,
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-User-Key: YOUR_USER_KEY', 'Content-Type: application/json']);
$response = curl_exec($ch);
$data = json_decode($response, true);import requests
headers = {
"X-Api-User-Key": "YOUR_USER_KEY",
}
body = {
'level': 123,
'title': '...',
'name': '...',
'description': '...',
'parent_acronym': '...',
'leader_user_id': 123,
}
response = requests.post("https://api.openstudio.one/v1/departments", headers=headers, json=body)
data = response.json()fetch('https://api.openstudio.one/v1/departments', {
method: 'POST',
headers: {
'X-Api-User-Key': 'YOUR_USER_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'level': 123,
'title': '...',
'name': '...',
'description': '...',
'parent_acronym': '...',
'leader_user_id': 123,
})
})
.then(res => res.json())
.then(data => console.log(data));Response fields
| Field | Type | Flags | Notes |
|---|---|---|---|
| department | object | — | — |
Example response
{
"success": true,
"data": {
"department": {
"id": 42,
"acronym": "MKT",
"title": "Marketing",
"description": "Empty on creation unless one was sent.",
"level": 1,
"leader_user_id": 88
}
},
"error": null
}GET /departments
List organizational structures (Areas, Departments, Units)
User key PaginatedQuery parameters (7)
Filtering
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ?filter[level] | integer | Optional | Filter by structure level.1 = Area2 = Department3 = Unit |
| ?filter[parent_name] | string | Optional | Filter by the direct parent structure's code. |
| ?filter[acronym] | string | Optional | Return only the department matching this exact acronym. |
| ?filter[title][like] | string | Optional | Substring match on the department's title. |
Sorting
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ?sort | string | Optional | Comma-separated sort fields: id, level, acronym, title, created_at, updated_at. Prefix with - for descending, e.g. sort=-created_at. See the "Filtering & sorting" guide. |
Pagination
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ?page | integer | Optional | Page number, starting at 1. Defaults to 1. |
| ?limit | integer | Optional | Rows per page, 1-200. Defaults to 50. |
Example request
curl -X GET "https://api.openstudio.one/v1/departments?filter%5Blevel%5D=123&filter%5Bparent_name%5D=...&filter%5Bacronym%5D=...&filter%5Btitle%5D%5Blike%5D=...&sort=...&page=123&limit=123" \
-H "X-Api-User-Key: YOUR_USER_KEY"<?php
$ch = curl_init('https://api.openstudio.one/v1/departments?filter%5Blevel%5D=123&filter%5Bparent_name%5D=...&filter%5Bacronym%5D=...&filter%5Btitle%5D%5Blike%5D=...&sort=...&page=123&limit=123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-User-Key: YOUR_USER_KEY']);
$response = curl_exec($ch);
$data = json_decode($response, true);import requests
headers = {
"X-Api-User-Key": "YOUR_USER_KEY",
}
response = requests.get("https://api.openstudio.one/v1/departments?filter%5Blevel%5D=123&filter%5Bparent_name%5D=...&filter%5Bacronym%5D=...&filter%5Btitle%5D%5Blike%5D=...&sort=...&page=123&limit=123", headers=headers)
data = response.json()fetch('https://api.openstudio.one/v1/departments?filter%5Blevel%5D=123&filter%5Bparent_name%5D=...&filter%5Bacronym%5D=...&filter%5Btitle%5D%5Blike%5D=...&sort=...&page=123&limit=123', {
method: 'GET',
headers: {
'X-Api-User-Key': 'YOUR_USER_KEY'
}
})
.then(res => res.json())
.then(data => console.log(data));ⓘRuns a real request from your browser directly to departments. Keys are only held in this page's memory while you're here — never stored, never sent anywhere else.
Response fields
| Field | Type | Flags | Notes |
|---|---|---|---|
| departments | array of objects | — | Array of department objects, ordered by creation order within the tenant. |
| ↳id | integer | — | Tenant-scoped structure ID (stable, used in dashboard URLs). |
| ↳acronym | string | — | Short uppercase identifier, e.g. "ADMIN". Immutable after creation. |
| ↳title | string | — | Full display name. |
| ↳description | string | — | Free-text description. May be empty. |
| ↳level | integer | — | Structure level.1 = Area2 = Department3 = Unit |
| ↳area | string | The top-level Area code this structure belongs to. Null for L1 Areas themselves. | |
| ↳area_title | string | Display title of area, resolved server-side so it's available even if that Area itself isn't in this caller's own visible list (scope: "own"). Null exactly when area is null. |
|
| ↳parent_name | string | Direct parent structure code. Null only for L1 Areas. | |
| ↳parent_title | string | Display title of parent_name, resolved server-side for the same reason as area_title. Null exactly when parent_name is null. |
|
| ↳leader_user_id | integer | The account id of the structure's leader/manager, if assigned. | |
| ↳member_count | integer | — | Distinct active members of this structure AND every structure beneath it (same "active" definition as GET /users/{id}/memberships: until IS NULL OR until >= today) — an Area's count includes everyone assigned to one of its Departments/Units, not just people assigned directly to the Area itself. Someone assigned at more than one level in the same lineage is only counted once. |
| ↳created_at | string | — | Creation timestamp, YYYY-MM-DD HH:MM:SS. |
| ↳updated_at | string | Last update timestamp. Null if never updated since creation. | |
| scope | string | — | full if the caller (founder or any manage_personal tier > 0) sees every structure in the tenant; own if the list was narrowed to structures they lead, co-manage, or hold an active membership in.fullown |
| general_director | object | The tenant's current General Director, if one resolves (an active departments_directors row, or the tenant's founder as a fallback). Null if neither exists. Tenant name/logo/structure-level labels moved to their own GET /tenant/profile — see that route. |
|
| ↳user_id | integer | — | The user currently holding this role — either an explicit appointment, or the tenant's founder via the fallback. |
| ↳username | string | — | Login username — always present, regardless of privacy mode (see display_name). |
| ↳display_name | string | — | The name to actually show in UI. Real first+last name from the person's identity data when available and they're not in privacy mode; falls back to username otherwise. |
| ↳avatar_url | string | — | Always a real, loadable URL — a shared blank-avatar placeholder when there's no real photo on file, or when the person is in privacy mode. |
| ↳from_date | string | Null when resolved via the founder fallback (no formal appointment record). | |
| ↳appointed_by_user_id | integer | The id of whoever made the appointment. Null via the founder fallback, or if the appointment itself never recorded one. | |
| ↳is_interim | boolean | — | True when resolved via the founder fallback rather than an explicit departments_directors appointment. |
Pagination
| Field | Type | Notes |
|---|---|---|
| page | integer | Current page number. |
| limit | integer | Rows per page. |
| total | integer | Total matching rows across all pages. |
| total_pages | integer | Total number of pages. |
| next_page_url | string | Ready-to-call URL for the next page, same filters applied. Null on the last page. |
| prev_page_url | string | Ready-to-call URL for the previous page, same filters applied. Null on the first page. |
Example response
{
"success": true,
"data": {
"departments": [
{
"id": 2,
"acronym": "ADMIN",
"title": "Administration",
"description": "This is the first structure of your organization...",
"level": 2,
"area": "MAIN",
"area_title": "Main Area",
"parent_name": "MAIN",
"parent_title": "Main Area",
"leader_user_id": 2371,
"member_count": 14,
"created_at": "2023-06-18 09:17:39",
"updated_at": null
},
{
"id": 1,
"acronym": "MAIN",
"title": "Main Area",
"description": "This is the main Area in your organization...",
"level": 1,
"area": null,
"area_title": null,
"parent_name": null,
"parent_title": null,
"leader_user_id": 2371,
"member_count": 32,
"created_at": "2023-06-18 09:17:39",
"updated_at": null
}
],
"scope": "full",
"general_director": {
"user_id": 2371,
"username": "jane.doe",
"display_name": "Jane Doe",
"avatar_url": "https://www.openstudio.one/assets/img/avatars/blank_avatar.jpg",
"from_date": "2023-06-18",
"appointed_by_user_id": null,
"is_interim": false
}
},
"error": null,
"pagination": {
"page": 1,
"limit": 50,
"total": 134,
"total_pages": 3,
"next_page_url": "https://api.openstudio.one/v1/departments?page=2&limit=50",
"prev_page_url": null
}
}PATCH /departments/{id}
Edit a structure's title, description, or leader
User keyRequest body
| Field | Type | Flags | Required | Notes |
|---|---|---|---|---|
| title | string | — | Optional | The structure's new display name. |
| description | string | — | Optional | A longer, free-text description of what this structure covers. |
| leader_user_id | integer | — | Optional | The structure's new leader, by user id. |
Example request
curl -X PATCH "https://api.openstudio.one/v1/departments/{id}" \
-H "X-Api-User-Key: YOUR_USER_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"...","description":"...","leader_user_id":123}'<?php
$ch = curl_init('https://api.openstudio.one/v1/departments/{id}');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'title' => '...',
'description' => '...',
'leader_user_id' => 123,
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-User-Key: YOUR_USER_KEY', 'Content-Type: application/json']);
$response = curl_exec($ch);
$data = json_decode($response, true);import requests
headers = {
"X-Api-User-Key": "YOUR_USER_KEY",
}
body = {
'title': '...',
'description': '...',
'leader_user_id': 123,
}
response = requests.patch("https://api.openstudio.one/v1/departments/{id}", headers=headers, json=body)
data = response.json()fetch('https://api.openstudio.one/v1/departments/{id}', {
method: 'PATCH',
headers: {
'X-Api-User-Key': 'YOUR_USER_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'title': '...',
'description': '...',
'leader_user_id': 123,
})
})
.then(res => res.json())
.then(data => console.log(data));Response fields
| Field | Type | Flags | Notes |
|---|---|---|---|
| department | object | — | — |
Example response
{
"success": true,
"data": {
"department": {
"id": 42,
"acronym": "MKT",
"title": "Marketing",
"description": "Empty here unless a description was actually sent in this call.",
"level": 1,
"leader_user_id": 88
}
},
"error": null
}DELETE /departments/{id}
Delete a structure — only when it is currently empty
User keyExample request
curl -X DELETE "https://api.openstudio.one/v1/departments/{id}" \
-H "X-Api-User-Key: YOUR_USER_KEY"<?php
$ch = curl_init('https://api.openstudio.one/v1/departments/{id}');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-User-Key: YOUR_USER_KEY']);
$response = curl_exec($ch);
$data = json_decode($response, true);import requests
headers = {
"X-Api-User-Key": "YOUR_USER_KEY",
}
response = requests.delete("https://api.openstudio.one/v1/departments/{id}", headers=headers)
data = response.json()fetch('https://api.openstudio.one/v1/departments/{id}', {
method: 'DELETE',
headers: {
'X-Api-User-Key': 'YOUR_USER_KEY'
}
})
.then(res => res.json())
.then(data => console.log(data));Response fields
| Field | Type | Flags | Notes |
|---|---|---|---|
| deleted | boolean | — | — |
Example response
{
"success": true,
"data": {
"deleted": true
},
"error": null
}GET /departments/{acronym}/members
Real name/photo for a structure's leader and active members
User key PaginatedQuery parameters (2)
Pagination
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ?page | integer | Optional | Page number, starting at 1. Defaults to 1. Applies to members only. |
| ?limit | integer | Optional | Rows per page, 1-200. Defaults to 50. |
Example request
curl -X GET "https://api.openstudio.one/v1/departments/{acronym}/members?page=123&limit=123" \
-H "X-Api-User-Key: YOUR_USER_KEY"<?php
$ch = curl_init('https://api.openstudio.one/v1/departments/{acronym}/members?page=123&limit=123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-User-Key: YOUR_USER_KEY']);
$response = curl_exec($ch);
$data = json_decode($response, true);import requests
headers = {
"X-Api-User-Key": "YOUR_USER_KEY",
}
response = requests.get("https://api.openstudio.one/v1/departments/{acronym}/members?page=123&limit=123", headers=headers)
data = response.json()fetch('https://api.openstudio.one/v1/departments/{acronym}/members?page=123&limit=123', {
method: 'GET',
headers: {
'X-Api-User-Key': 'YOUR_USER_KEY'
}
})
.then(res => res.json())
.then(data => console.log(data));ⓘRuns a real request from your browser directly to departments/{acronym}/members. Keys are only held in this page's memory while you're here — never stored, never sent anywhere else.
Response fields
| Field | Type | Flags | Notes |
|---|---|---|---|
| department | object | — | The structure this route was called for, echoed back for convenience. |
| ↳id | integer | — | Tenant-scoped structure ID. |
| ↳acronym | string | — | Short uppercase identifier — the same value passed in the URL. |
| ↳title | string | — | Full display name. |
| leader | object | Null if the structure has no leader assigned. Not paginated — a structure has at most one. | |
| ↳user_id | integer | — | Matches the leader's user_id on GET /departments. |
| ↳username | string | — | Login username — always present, regardless of privacy mode (see display_name). |
| ↳display_name | string | — | The name to actually show in UI. Real first+last name from the person's identity data when available and they're not in privacy mode; falls back to username otherwise (privacy mode, or no identity data on file yet). |
| ↳avatar_url | string | — | Always a real, loadable URL — a shared blank-avatar placeholder when there's no real photo on file, or when the person is in privacy mode (their real photo, if any, is withheld the same way their real name is). |
| members | array of objects | — | Everyone with an active (non-expired) membership in this structure, primary members first, then by user id. |
| ↳user_id | integer | — | Usable with GET /users/{id} if the caller separately has manage_personal access. |
| ↳username | string | — | Login username — always present, regardless of privacy mode (see display_name). |
| ↳display_name | string | — | The name to actually show in UI. Real first+last name from the person's identity data when available and they're not in privacy mode; falls back to username otherwise (privacy mode, or no identity data on file yet). |
| ↳avatar_url | string | — | Always a real, loadable URL — a shared blank-avatar placeholder when there's no real photo on file, or when the person is in privacy mode. |
| ↳is_primary | boolean | — | Whether this structure is the member's PRIMARY assignment — see the /me memberships list for the same flag from the member's own point of view. |
Pagination
| Field | Type | Notes |
|---|---|---|
| page | integer | Current page number. |
| limit | integer | Rows per page. |
| total | integer | Total matching rows across all pages. |
| total_pages | integer | Total number of pages. |
| next_page_url | string | Ready-to-call URL for the next page, same filters applied. Null on the last page. |
| prev_page_url | string | Ready-to-call URL for the previous page, same filters applied. Null on the first page. |
Example response
{
"success": true,
"data": {
"department": {
"id": 2,
"acronym": "ADMIN",
"title": "Administration"
},
"leader": {
"user_id": 2371,
"username": "jane.doe",
"display_name": "Jane Doe",
"avatar_url": "https://www.openstudio.one/assets/img/avatars/blank_avatar.jpg"
},
"members": [
{
"user_id": 2371,
"username": "jane.doe",
"display_name": "Jane Doe",
"avatar_url": "https://www.openstudio.one/assets/img/avatars/blank_avatar.jpg",
"is_primary": true
},
{
"user_id": 2455,
"username": "john.smith",
"display_name": "John Smith",
"avatar_url": "https://www.openstudio.one/assets/img/avatars/blank_avatar.jpg",
"is_primary": false
}
]
},
"error": null,
"pagination": {
"page": 1,
"limit": 50,
"total": 134,
"total_pages": 3,
"next_page_url": "https://api.openstudio.one/v1/departments?page=2&limit=50",
"prev_page_url": null
}
}Directory
GET /directory
Find a colleague — search the tenant's user directory
User key PaginatedQuery parameters (12)
Pagination
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ?page | integer | Optional | Page number, starting at 1. Defaults to 1. |
| ?limit | integer | Optional | Rows per page, 1-50. Defaults to 20. |
Other
| Parameter | Type | Required | Notes |
|---|---|---|---|
| ?q | string | Optional | Combined name/username search term, minimum 2 characters. Required unless structure, q_name, q_surname, q_email, or q_number is given instead. |
| ?q_name | string | Optional | First-name-only search (separate from q, matches address_book.php's own q_name field). Combinable with q/q_surname/others. |
| ?q_surname | string | Optional | Last-name-only search (separate from q, matches address_book.php's own q_surname field). |
| ?structure | string | Optional | Restrict results to active members of this structure acronym (matches GET /departments' own acronym). |
| ?include_sub | boolean | Optional | With structure, also include that structure's sub-departments/units (one/two levels down). |
| ?include_leaders | boolean | Optional | With structure, also include that structure's leader even if they are not formally an active member of it. |
| ?duty | string | Optional | Filter by organizational role: dept_leader (leads any department/unit) or area_head (leads an area). Matches address_book.php's own duty filter (FIX #21). |
| ?q_email | string | Optional | Substring match against work contact records of type email (contacts.data). |
| ?q_number | string | Optional | Substring match against work contact records of type phone/extension (contacts.data). |
| ?q_type | string | Optional | Restrict q_email/q_number matching (or standalone) to one contact-channel type, e.g. business_email, telephone_short, service_phone. |
Example request
curl -X GET "https://api.openstudio.one/v1/directory?q=...&q_name=...&q_surname=...&structure=...&include_sub=true&include_leaders=true&duty=...&q_email=...&q_number=...&q_type=...&page=123&limit=123" \
-H "X-Api-User-Key: YOUR_USER_KEY"<?php
$ch = curl_init('https://api.openstudio.one/v1/directory?q=...&q_name=...&q_surname=...&structure=...&include_sub=true&include_leaders=true&duty=...&q_email=...&q_number=...&q_type=...&page=123&limit=123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-User-Key: YOUR_USER_KEY']);
$response = curl_exec($ch);
$data = json_decode($response, true);import requests
headers = {
"X-Api-User-Key": "YOUR_USER_KEY",
}
response = requests.get("https://api.openstudio.one/v1/directory?q=...&q_name=...&q_surname=...&structure=...&include_sub=true&include_leaders=true&duty=...&q_email=...&q_number=...&q_type=...&page=123&limit=123", headers=headers)
data = response.json()fetch('https://api.openstudio.one/v1/directory?q=...&q_name=...&q_surname=...&structure=...&include_sub=true&include_leaders=true&duty=...&q_email=...&q_number=...&q_type=...&page=123&limit=123', {
method: 'GET',
headers: {
'X-Api-User-Key': 'YOUR_USER_KEY'
}
})
.then(res => res.json())
.then(data => console.log(data));ⓘRuns a real request from your browser directly to directory. Keys are only held in this page's memory while you're here — never stored, never sent anywhere else.
Response fields
| Field | Type | Flags | Notes |
|---|---|---|---|
| results | array of objects | — | Matching users, same shape as the top-level fields of GET /directory/{id} (no memberships/staff_details — fetch those separately per result if the caller taps into one). |
| ↳user_id | integer | — | — |
| ↳username | string | — | — |
| ↳display_name | string | — | — |
| ↳avatar_url | string | — | — |
| ↳primary_structure_title | string | The person's primary active structure, if any — included specifically to disambiguate homonyms in a results list without opening each profile. |
Pagination
| Field | Type | Notes |
|---|---|---|
| page | integer | Current page number. |
| limit | integer | Rows per page. |
| total | integer | Total matching rows across all pages. |
| total_pages | integer | Total number of pages. |
| next_page_url | string | Ready-to-call URL for the next page, same filters applied. Null on the last page. |
| prev_page_url | string | Ready-to-call URL for the previous page, same filters applied. Null on the first page. |
Example response
{
"success": true,
"data": {
"results": [
{
"user_id": 2455,
"username": "john.smith",
"display_name": "John Smith",
"avatar_url": "https://www.openstudio.one/assets/img/avatars/blank_avatar.jpg",
"primary_structure_title": "Administration Finance & Accounting"
}
]
},
"error": null,
"pagination": {
"page": 1,
"limit": 50,
"total": 134,
"total_pages": 3,
"next_page_url": "https://api.openstudio.one/v1/departments?page=2&limit=50",
"prev_page_url": null
}
}GET /directory/{id}
Clickable-profile view of another user
User keyExample request
curl -X GET "https://api.openstudio.one/v1/directory/{id}" \
-H "X-Api-User-Key: YOUR_USER_KEY"<?php
$ch = curl_init('https://api.openstudio.one/v1/directory/{id}');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-Api-User-Key: YOUR_USER_KEY']);
$response = curl_exec($ch);
$data = json_decode($response, true);import requests
headers = {
"X-Api-User-Key": "YOUR_USER_KEY",
}
response = requests.get("https://api.openstudio.one/v1/directory/{id}", headers=headers)
data = response.json()fetch('https://api.openstudio.one/v1/directory/{id}', {
method: 'GET',
headers: {
'X-Api-User-Key': 'YOUR_USER_KEY'
}
})
.then(res => res.json())
.then(data => console.log(data));ⓘRuns a real request from your browser directly to directory/{id}. Keys are only held in this page's memory while you're here — never stored, never sent anywhere else.
Response fields
| Field | Type | Flags | Notes |
|---|---|---|---|
| user_id | integer | — | The user's unique numeric ID. |
| username | string | — | Login username — always present, regardless of privacy mode (see display_name). |
| display_name | string | — | The name to actually show in UI. Real first+last name when available and the person isn't in privacy mode; falls back to username otherwise. |
| avatar_url | string | — | Always a real, loadable URL — a shared blank-avatar placeholder when there's no real photo on file, or when the person is in privacy mode. |
| memberships | array of objects | — | Currently active organizational structure assignments, primary first — identical shape to GET /users/{id}/memberships. |
| ↳id | integer | This assignment's own id. null if this row exists ONLY because the person leads or manages this structure without actually being a member of it (is_leader/is_manager) — there's no departments_users row to reference in that case. |
|
| ↳structure_acronym | string | — | Matches the acronym field on GET /departments. |
| ↳structure_title | string | — | Full display name of the structure. |
| ↳level | integer | — | Structure level.1 = Area2 = Department3 = Unit |
| ↳parent_title | string | Display title of the structure's immediate parent, resolved server-side (same as parent_title on GET /departments). Null only for an L1 Area. |
|
| ↳area_title | string | Display title of the structure's top-level Area (same as area_title on GET /departments) — for an L3 Unit this differs from parent_title (its immediate L2 parent); for an L2 Department the two are equal. Null only for an L1 Area. |
|
| ↳is_primary | boolean | — | At most one primary assignment per user at a time. Always false on a leadership-only row (no actual membership). |
| ↳is_leader | boolean | — | Whether this person is this structure's leader (matches leader_user_id on GET /departments) — independent of whether they're also a member of it. |
| ↳is_manager | boolean | — | Whether this person is one of this structure's co-managers — independent of whether they're also a member of it. |
| ↳since | string | When this assignment started. null on a leadership-only row. |
|
| ↳until | string | null = open-ended (also always null on a leadership-only row). |
|
| ↳notes | string | Free-text note attached when this assignment was created, if any. | |
| staff_details | object | Present only when the CALLER is a STRUCTURED_USERS member or founder. null for every other caller — its absence of data, not the target's, so a null here says nothing about the target themselves. |
|
| ↳email | string | The target's account email. | |
| ↳phone_number | string | Work phone, if on file. | |
| ↳mobile_number | string | Work mobile, if on file. | |
| ↳contact_channels | array of objects | — | Every other active, visible contact entry on file for this person (institutional email, internal extension, service mobile, fax, ...) — a person can have several of the same type (e.g. two internal extensions in different structures), so this is a list alongside (not a replacement for) email/phone_number/mobile_number above. |
| ↳type | string | — | Internal channel type key, e.g. business_email, telephone_short, service_phone, fax. |
| ↳label | string | — | Human-readable label for type, already translated/humanized server-side. |
| ↳value | string | — | The actual email address, phone number, or extension. |
| ↳structure | string | The structure acronym this specific entry is scoped to, if any (e.g. a department-specific extension). Null for a person-level entry not tied to any one structure. | |
| ↳director | object | Present only if the TARGET currently holds the tenant's General Director role — same shape as /me's own director field. |
|
| ↳role | string | — | —general_director |
| ↳from_date | string | — | |
| ↳to_date | string | Always null — only the current directorship is ever represented here. |
|
| ↳appointed_by_user_id | integer | — | |
| ↳is_interim | boolean | — | — |
Example response
{
"success": true,
"data": {
"user_id": 2455,
"username": "john.smith",
"display_name": "John Smith",
"avatar_url": "https://www.openstudio.one/assets/img/avatars/blank_avatar.jpg",
"memberships": [
{
"id": 41,
"structure_acronym": "AFA",
"structure_title": "Administration Finance & Accounting",
"level": 2,
"parent_title": "Main Area",
"area_title": "Main Area",
"is_primary": true,
"is_leader": false,
"is_manager": false,
"since": "2019-08-19",
"until": null,
"notes": null
}
],
"staff_details": {
"email": "john.smith@example.com",
"phone_number": null,
"mobile_number": null,
"contact_channels": [
{
"type": "telephone_short",
"label": "Internal Extension",
"value": "204",
"structure": "AFA"
}
],
"director": null
}
},
"error": null
}