Skip to content

Users (team members)

Users in the BenchKey API represent your team members, the people who log in to your BenchKey account. The resource exposes your shop’s staff roster: technicians, admins, the account owner, and members holding tenant-defined custom roles. Sensitive auth fields (passwords, session tokens, third-party IDs) are never exposed.

Roles are slugs, the four builtins (owner, admin, tech, viewer) plus any custom roles your workspace defines. Discover the full catalog with the Roles resource.

{
"object": "user",
"id": 42,
"email": "alex@shopname.com",
"role": "tech",
"display_name": "Alex Rivera",
"queues": [{ "id": 1, "name": "General", "is_primary": true }],
"queue_ids": [1],
"primary_queue_id": 1,
"location_ids": []
}
FieldTypeDescription
idintegerUnique team member ID
emailstringThe team member’s login email
rolestringThe member’s role slug, a builtin (owner, admin, tech, viewer) or a custom role slug. Resolve slugs to labels via GET /roles
display_namestring|nullDisplay name, or null if not set
queuesarrayQueue assignments with id, name, and is_primary per entry
queue_idsinteger[]IDs of assigned queues
primary_queue_idinteger|nullThe member’s primary queue ID, or null
location_idsinteger[]IDs of assigned locations; empty array means unrestricted access

GET /api/v1/users

Returns a cursor-paginated list of team members, sorted by id descending.

Scope required: users.read

ParameterTypeDescription
rolestringFilter by role slug, a builtin (owner, admin, tech, viewer) or a custom role slug. A well-formed slug that matches no member returns an empty list; a malformed value (not lowercase letters/digits/-/_, max 64 chars) returns 400 invalid_query
limitintegerPage size, 1–100 (default: 25)
cursorstringOpaque cursor from a previous response’s next_cursor
Terminal window
curl "https://app.benchkey.com/api/v1/users?role=tech" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch(
"https://app.benchkey.com/api/v1/users?role=tech",
{ headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } }
);
const { data, has_more, next_cursor } = await res.json();
{
"object": "list",
"data": [
{
"object": "user",
"id": 42,
"email": "alex@shopname.com",
"role": "tech",
"display_name": "Alex Rivera",
"queues": [{ "id": 1, "name": "General", "is_primary": true }],
"queue_ids": [1],
"primary_queue_id": 1,
"location_ids": []
}
],
"has_more": false,
"next_cursor": null
}

See Pagination for how to page through results.


GET /api/v1/users/:id

Returns a single team member by their integer id.

Scope required: users.read

Terminal window
curl "https://app.benchkey.com/api/v1/users/42" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch(
"https://app.benchkey.com/api/v1/users/42",
{ headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" } }
);
const user = await res.json();

Returns the user object. Returns 404 if no team member with that ID exists.


POST /api/v1/users

Sends a team invite to an email address and adds them to the tenant. Seat limits and role restrictions are enforced by the platform. The owner and admin roles cannot be granted through this endpoint.

Scope required: users.write

FieldRequiredDescription
emailyesEmail address to invite
rolenotech or viewer (default: tech). admin and owner cannot be granted via the API.
Terminal window
curl -X POST https://app.benchkey.com/api/v1/users \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \
-H "Content-Type: application/json" \
-d '{ "email": "sam@shopname.com", "role": "tech" }'
const res = await fetch("https://app.benchkey.com/api/v1/users", {
method: "POST",
headers: {
Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa",
"Content-Type": "application/json",
},
body: JSON.stringify({ email: "sam@shopname.com", role: "tech" }),
});
const user = await res.json(); // HTTP 201

Returns the user object with HTTP 201.


PATCH /api/v1/users/by-email/:email

Updates a team member’s display name, queue assignments, primary queue, and/or location assignments. The :email path parameter is the team member’s login email, URL-encode it.

Scope required: team.write

At least one field must be included:

FieldTypeDescription
display_namestringDisplay name (must be a non-null string; passing null returns 400 invalid_field)
queue_idsinteger[]IDs of the queues this member is assigned to (each must be a positive integer in [1, 2,147,483,647]; non-positive or out-of-range values return 400 invalid_field)
primary_queue_idintegerThe member’s primary queue (must be a positive integer in [1, 2,147,483,647]; non-positive or out-of-range values return 400 invalid_field)
location_idsinteger[]IDs of the locations this member is assigned to; requires expected_revision
expected_revisionstringRequired with location_ids, invalid without it. The opaque 64-character lowercase hexadecimal token from the latest X-Team-Locations-Revision response header

To change locations, first read the users list or the user with users.read and retain the X-Team-Locations-Revision response header. Review the current assignments, then include that token as expected_revision alongside location_ids in the PATCH body. A client with only team.write can omit the token once to receive a nonmutating 428 location_revision_required response carrying the current header, then resubmit with it.

If the update returns 409 stale_location_revision, reload the assignments and revision, review the intervening changes, and submit the intended assignments with the new token. Do not automatically overwrite another person’s changes. Updates that do not include location_ids do not need a revision.

Terminal window
curl -X PATCH "https://app.benchkey.com/api/v1/users/by-email/sam%40shopname.com" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \
-H "Content-Type: application/json" \
-d '{ "display_name": "Sam Chen", "queue_ids": [1, 3] }'
const email = "sam@shopname.com";
const res = await fetch(
`https://app.benchkey.com/api/v1/users/by-email/${encodeURIComponent(email)}`,
{
method: "PATCH",
headers: {
Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa",
"Content-Type": "application/json",
},
body: JSON.stringify({ display_name: "Sam Chen", queue_ids: [1, 3] }),
}
);
const user = await res.json();

Returns the updated user object.


PATCH /api/v1/users/by-email/:email/role

Changes a team member’s role. The owner role cannot be changed via this endpoint, and admin cannot be granted via the API. Valid target roles are tech and viewer.

Members holding custom roles are fully visible in reads (their custom slug appears in role), but assigning a custom role through the public API is not currently supported, use the Roles resource to discover the catalog, and the in-app team settings to assign custom roles.

Scope required: team.write

FieldRequiredDescription
roleyesNew role: tech or viewer. admin and owner cannot be granted via the API.
Terminal window
curl -X PATCH "https://app.benchkey.com/api/v1/users/by-email/sam%40shopname.com/role" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \
-H "Content-Type: application/json" \
-d '{ "role": "tech" }'
const email = "sam@shopname.com";
const res = await fetch(
`https://app.benchkey.com/api/v1/users/by-email/${encodeURIComponent(email)}/role`,
{
method: "PATCH",
headers: {
Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa",
"Content-Type": "application/json",
},
body: JSON.stringify({ role: "tech" }),
}
);
const user = await res.json();

Returns the updated user object.


DELETE /api/v1/users/:id

Removes a team member from the tenant. The account owner (role: "owner") cannot be removed.

Scope required: users.write

Terminal window
curl -X DELETE "https://app.benchkey.com/api/v1/users/42" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch("https://app.benchkey.com/api/v1/users/42", {
method: "DELETE",
headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },
});
const result = await res.json();
// { "object": "user", "id": 42, "deleted": true }
{
"object": "user",
"id": 42,
"deleted": true
}

HTTP statusCodeMeaning
400invalid_idThe :id is not a valid positive integer
400invalid_queryA list filter parameter was supplied as an array or object instead of a single scalar value
400invalid_fieldA field value is invalid (e.g. bad role, invalid email format, bad email path param)
400missing_fieldA required field (email) is absent
400cannot_remove_ownerThe tenant owner cannot be removed
400no_fieldsPATCH body contains no recognized updatable fields
400permission_deniedInsufficient permissions to grant, change, or remove that role
400invite_failedInvite could not be completed, see message for details
400role_change_failedRole change was rejected by the server, see message for details
400update_failedProfile update was rejected by the server, see message for details
400location_update_failedLocation assignment was rejected by the server, see message for details
400feature_requiredLocation assignment requires the multi-location feature, which is not enabled on this plan
404not_foundNo team member with that ID or email exists
402seat_limit_reachedPlan seat cap reached, upgrade required
409already_memberThe email is already a member of this team
428location_revision_requiredA location update omitted expected_revision; no location or profile changes were made. Read the current token from X-Team-Locations-Revision
409stale_location_revisionLocation assignments changed since the supplied revision. Reload and review before resubmitting
403insufficient_scopeAPI key lacks users.read, users.write, or team.write

See Errors for the full error envelope format.

System status