BetterIAM
Server API

departments

Departments are the organization's reporting structure: Engineering, Finance, Sales, and their sub-departments.

Departments are the organization's reporting structure: Engineering, Finance, Sales, and their sub-departments. Each person belongs to at most one department, a department can name a head, a code, and a cost center, and teams can be filed under a department. Teams, the working units, are the teams group.

Departments in policies

Every evaluation for a person in their own tenant can read two condition keys, loaded only when a policy names them:

  • principal.departments: the person's department ID and the IDs of every department above it.
  • principal.departmentId: the person's own department; absent without one.
{
  "effect": "allow",
  "actions": ["documents:write"],
  "resources": ["*"],
  "conditions": { "StringEquals": { "principal.departmentId": "${resource.departmentId}" } }
}

{ "ArrayContains": { "principal.departments": ["<engineering id>"] } } admits everyone in Engineering and its sub-departments. Sessions of an assumed role see an empty list.

Managers from the org chart

syncManagers sets each person's manager (managerId) to the head of their department, and a head's to the nearest head above. Approvals routed to managers (eligible bindings and access packages with managerApproval, certification campaigns with reviewerMode: 'manager') then follow the org chart.

Birthright packages

Access package rules may test identity.departments: a person's department ID and the IDs of the departments above it. assign, unassign, importFromAttribute, moving or deleting a department, and syncManagers (rules may test identity.managerId) re-evaluate the rules for the people they touch once they commit: joiners get their department's packages at once, and movers and leavers lose them.

Methods14
Serveriam.api.departments
Clientclient.departments
HTTPPOST /api/iam/departments/*
MethodWhat it doesAccess
assignPlaces up to 100 people (identityIds, or one identityId) in a department, moving them out of any other, with an optional title. Returns how many changed.Credential
createCreates a department, optionally under parentId, with a code, a headId (an active person of the organization), a costCenter, and a description.Credential
deleteDeletes a department. Its people become unassigned and its teams lose the link.Credential
getOne department with its path (the departments above it), sub-departments, head, and teams, and member counts with and without the departments below.Credential
importFromAttributePlaces every active person whose string identity attribute (such as department, filled by SCIM provisioning or an onboarding form) names a department, matched by name or code ignoring case. createMissing creates top-level departments for values nothing matches; dryRun reports without changing anything.Credential
listEvery department with member counts (with and without sub-departments), child and team counts, in name order.Credential
listMembersThe people of a department, heads first; includeSubdepartments adds those of every department below, each with their department. Each entry carries the person's title, since when they are in the department, and their manager.Credential
mineYour own place in the org chart and, if you head departments, the people you lead.Credential
ofIdentityA person's department with the path from the top, their title, since when, the department's head and cost center; null when they have none.Credential
suggestBirthrightRoles and groups that most of a department's people already hold by hand, proposed as a ready-made automatic access package whose rule names the department.Credential
syncManagersMakes department heads the managers of their departments' people (see above). Without overwrite only people without a manager change; departmentId limits the run to one department and those below it; dryRun reports only. Returns the changes with names, how many kept another manager, and how many had no head above them. Never creates a cycle.Credential
treeThe org chart: top-level departments with their sub-departments, heads, and member counts.Credential
unassignTakes a person out of their department.Credential
updateRenames, re-codes, moves (parentId, null for top level), or changes the head, cost center, or description of a department; null (or an empty string) clears an optional field.Credential

assign

Places up to 100 people (identityIds, or one identityId) in a department, moving them out of any other, with an optional title. Returns how many changed.

POST/api/iam/departments/assign
client.departments.assign()Credential

Used inTeams and departments

  • Permission: iam:departments:manage on iam/{departmentId}.
  • Audited as: iam:departments:manage and department:assign per person (previousDepartmentId when moved).
  • Errors: INVALID_INPUT for a service account or agent, or without people; NOT_FOUND.
Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/assign" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "departmentId": "<departmentId>"
}'
Signature
iam.api.departments.assign(
  credential: CredentialInput,
  input: {
    tenantId: string;
    departmentId: string;
    identityIds?: string[];
    identityId?: string;
    title?: string;
  },
): Promise<{ assigned: number; unchanged: number }>

create

Creates a department, optionally under parentId, with a code, a headId (an active person of the organization), a costCenter, and a description.

POST/api/iam/departments/create
client.departments.create()Credential

Used inTeams and departments

  • Permission: iam:departments:manage on the tenant.
  • Audited as: iam:departments:manage and department:create.
  • Errors: CONFLICT (409) when the name or code (ignoring case) is taken; INVALID_INPUT for a bad code, a head who is not a person, or more than twenty levels of nesting; LIMIT_EXCEEDED past 2000 departments.
const engineering = await iam.api.departments.create(credential, {
  tenantId,
  name: 'Engineering',
  code: 'ENG',
  headId,
  costCenter: 'CC-100',
});
Input

A DepartmentInput object:

Prop

Type

Returns

A DepartmentDetail object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/create" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "name": "<name>"
}'
Signature
iam.api.departments.create(
  credential: CredentialInput,
  input: DepartmentInput,
): Promise<DepartmentDetail>

delete

Deletes a department. Its people become unassigned and its teams lose the link.

POST/api/iam/departments/delete
client.departments.delete()Credential
  • Permission: iam:departments:manage on the department.
  • Audited as: iam:departments:manage and department:delete (unassigned, teams).
  • Errors: RESOURCE_IN_USE (409) while departments sit below it, or while an access package rule names it (identity.departments).
Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/delete" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "departmentId": "<departmentId>"
}'
Signature
iam.api.departments.delete(
  credential: CredentialInput,
  input: { tenantId: string; departmentId: string },
): Promise<{ unassigned: number; teams: number; deleted: true }>

get

One department with its path (the departments above it), sub-departments, head, and teams, and member counts with and without the departments below.

POST/api/iam/departments/get
client.departments.get()Credential
  • Permission: iam:departments:read on the department.
Input

Prop

Type

Returns

A DepartmentDetail object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/get" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "departmentId": "<departmentId>"
}'
Signature
iam.api.departments.get(
  credential: CredentialInput,
  input: { tenantId: string; departmentId: string },
): Promise<DepartmentDetail>

importFromAttribute

Places every active person whose string identity attribute (such as department, filled by SCIM provisioning or an onboarding form) names a department, matched by name or code ignoring case. createMissing creates top-level departments for values nothing matches; dryRun reports without changing anything.

POST/api/iam/departments/importFromAttribute
client.departments.importFromAttribute()Credential
  • Permission: iam:departments:manage on the tenant.
  • Audited as: iam:departments:manage, and department:create / department:assign for what changed.
  • Errors: INVALID_INPUT when the attribute is not a declared string identity attribute.
const result = await iam.api.departments.importFromAttribute(credential, {
  tenantId,
  attribute: 'department',
  createMissing: true,
  dryRun: true,
});
// { dryRun: true, created: ['Sales'], assigned: 12, unchanged: 30, unmatched: [], missing: 2 }
Input

Prop

Type

Returns

A DepartmentImportResult object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/importFromAttribute" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "attribute": "<attribute>"
}'
Signature
iam.api.departments.importFromAttribute(
  credential: CredentialInput,
  input: {
    tenantId: string;
    attribute: string;
    createMissing?: boolean;
    dryRun?: boolean;
  },
): Promise<DepartmentImportResult>

list

Every department with member counts (with and without sub-departments), child and team counts, in name order.

POST/api/iam/departments/list
client.departments.list()Credential
  • Permission: iam:departments:read on the tenant.
Input

Prop

Type

Returns

An array of DepartmentSummary.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/list" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.departments.list(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<DepartmentSummary[]>

listMembers

The people of a department, heads first; includeSubdepartments adds those of every department below, each with their department. Each entry carries the person's title, since when they are in the department, and their manager.

POST/api/iam/departments/listMembers
client.departments.listMembers()Credential
  • Permission: iam:departments:read on the department.
Input

Prop

Type

Returns

An array of DepartmentMemberView.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/listMembers" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "departmentId": "<departmentId>"
}'
Signature
iam.api.departments.listMembers(
  credential: CredentialInput,
  input: { tenantId: string; departmentId: string; includeSubdepartments?: boolean },
): Promise<DepartmentMemberView[]>

mine

Your own place in the org chart and, if you head departments, the people you lead.

POST/api/iam/departments/mine
client.departments.mine()Credential
  • Permission: None beyond an ordinary session (or API key) of a person in the organization.
  • Audited as: Not audited; it only reads.
  • Errors: ACCESS_DENIED for a service account, an agent, a temporary credential, or another tenant's session.

department is your department with the path from the top, your title, since when, its head, and its cost center (null without a department). leads lists each department you head with its people and those of every department below it (name, email, department, title, and manager), heads first. Use it for a "my team" page that managers can open without iam:departments:read.

Input

Prop

Type

Returns

A MyDepartment object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/mine" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.departments.mine(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<MyDepartment>

ofIdentity

A person's department with the path from the top, their title, since when, the department's head and cost center; null when they have none.

POST/api/iam/departments/ofIdentity
client.departments.ofIdentity()Credential
  • Permission: iam:departments:read on iam/{identityId}.
Input

Prop

Type

Returns

One of null | object; the object form has these fields:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/ofIdentity" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>"
}'
Signature
iam.api.departments.ofIdentity(
  credential: CredentialInput,
  input: { tenantId: string; identityId: string },
): Promise<{
  costCenter?: string | undefined;
  head?: DepartmentPerson | undefined;
  since: number;
  title?: string | undefined;
  department: DepartmentRef;
  path: DepartmentRef[];
} | null>

suggestBirthright

Roles and groups that most of a department's people already hold by hand, proposed as a ready-made automatic access package whose rule names the department.

POST/api/iam/departments/suggestBirthright
client.departments.suggestBirthright()Credential
  • Permission: iam:analysis:read on the tenant.
  • Audited as: Not audited; it only reads.
  • Errors: INVALID_INPUT for minShare outside 0.5-1; NOT_FOUND for an unknown departmentId.

A department's people are exactly who a rule naming it would match: active people placed in it or in a department below it. Only plain grants count (standing, permanent role bindings made to the person and permanent memberships of ordinary groups, none from an access package), an item must be held by at least minShare (default 0.8) of at least minPeople (default 3) people, and nothing is suggested twice: not what is suggested for a department above, not what an automatic package naming the department (or one above) grants, and not what most of the department already receives from any automatic package. Each suggestion carries the shares, wouldGrant (people who would gain something), existingPackages, and package, ready for packages.create:

const [suggestion] = await iam.api.departments.suggestBirthright(credential, {
  tenantId,
  departmentId: engineeringId,
});
if (suggestion) await iam.api.packages.create(credential, { tenantId, ...suggestion.package });
Input

Prop

Type

Returns

An array of BirthrightSuggestion.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/suggestBirthright" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.departments.suggestBirthright(
  credential: CredentialInput,
  input: {
    tenantId: string;
    departmentId?: string;
    minShare?: number;
    minPeople?: number;
  },
): Promise<BirthrightSuggestion[]>

syncManagers

Makes department heads the managers of their departments' people (see above). Without overwrite only people without a manager change; departmentId limits the run to one department and those below it; dryRun reports only. Returns the changes with names, how many kept another manager, and how many had no head above them. Never creates a cycle.

POST/api/iam/departments/syncManagers
client.departments.syncManagers()Credential
  • Permission: iam:identities:update on the tenant (or the department) and iam:departments:read.
  • Audited as: iam:identities:update and department:sync-managers.
Input

Prop

Type

Returns

A ManagerSyncResult object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/syncManagers" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.departments.syncManagers(
  credential: CredentialInput,
  input: {
    tenantId: string;
    departmentId?: string;
    overwrite?: boolean;
    dryRun?: boolean;
  },
): Promise<ManagerSyncResult>

tree

The org chart: top-level departments with their sub-departments, heads, and member counts.

POST/api/iam/departments/tree
client.departments.tree()Credential
  • Permission: iam:departments:read on the tenant.
Input

Prop

Type

Returns

An array of DepartmentNode.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/tree" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.departments.tree(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<DepartmentNode[]>

unassign

Takes a person out of their department.

POST/api/iam/departments/unassign
client.departments.unassign()Credential
  • Permission: iam:departments:manage on iam/{identityId}.
  • Audited as: iam:departments:manage and department:unassign.
  • Errors: NOT_FOUND when the person has no department.
Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/unassign" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>"
}'
Signature
iam.api.departments.unassign(
  credential: CredentialInput,
  input: { tenantId: string; identityId: string },
): Promise<{ deleted: true }>

update

Renames, re-codes, moves (parentId, null for top level), or changes the head, cost center, or description of a department; null (or an empty string) clears an optional field.

POST/api/iam/departments/update
client.departments.update()Credential
  • Permission: iam:departments:manage on the department.
  • Audited as: iam:departments:manage and department:update (fields).
  • Errors: INVALID_INPUT when moving under itself or a department below it, or past twenty levels; CONFLICT for a taken name or code.
Input

A DepartmentUpdate object:

Prop

Type

Returns

A DepartmentDetail object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/departments/update" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "departmentId": "<departmentId>"
}'
Signature
iam.api.departments.update(
  credential: CredentialInput,
  input: DepartmentUpdate,
): Promise<DepartmentDetail>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page