Appearance
Users API
Users are workspace members. Each user has one or more roles that determine their permissions across projects and resources.
The list endpoint is available to all authenticated users — useful for resolving user IDs to names. Update, delete, and invitation endpoints require admin-level access.
Permissions
| Action | Required Permission | Scope |
|---|---|---|
| List | Users: Read | Visible users only — see Visibility scope |
| Update | Users: Update | Visible users only — out-of-scope targets return 403 |
| Resend Invitation | Users: Create | Visible users only — out-of-scope targets return 403 |
| Delete | Users: Delete | Visible users only — out-of-scope targets return 403 |
Admins bypass all permission checks. Users are tenant-scoped (not project-scoped), so the project owner bypass does not apply.
Visibility scope
/api/users and the three /api/users/{userId} action routes (update, delete, resend-invitation) only expose users the caller is allowed to see. The scope composes as:
- Self — the caller is always visible to themselves, even when they have no team or project membership.
- Admins — every admin is always visible, so cross-team name resolution (e.g. an admin commenting on an issue) keeps working.
- Users with
access_all_users— every user holding a role with theaccess_all_usersflag is always visible, regardless of project membership. - Project-affiliated users — users on any team that has access to a project the caller can access, plus direct project members.
Two role flags bypass the scope entirely:
| Flag | Effect on /api/users |
|---|---|
is_admin | Returns every user; action routes are not gated by visibility. |
access_all_users | Returns every user; action routes are not gated by visibility. |
access_all_projects | Widens the scope to every user affiliated with any project (every team member, every direct project member). Orphan invites with no project link remain hidden unless the caller also has access_all_users. |
See Roles API — Role Flags for how to grant the flags.
The action routes (POST /api/users/{userId}, POST /api/users/{userId}/resend-invitation, DELETE /api/users/{userId}) return 403 Forbidden (not 404) when the caller lacks visibility to the target.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /api/users | List all users |
POST | /api/users/{userId} | Update a user |
POST | /api/users/{userId}/resend-invitation | Resend invitation email |
DELETE | /api/users/{userId} | Delete a user |
GET | /api/users/{userId}/profile-picture/{variant} | Stream an avatar image. {variant} is avif or webp. Cached for 7 days via Cache-Control: public, max-age=604800 + ETag. |
List Users
GET /api/users
Returns the users visible to the caller (see Visibility scope — admins and access_all_users holders see everyone; all other callers see a filtered set). Admins receive the full response including email and detailed role permissions. All other users, including non-admin holders of access_all_users, receive the public response shown below.
bash
curl https://{tenant}.kendo.dev/api/users \
-H "Authorization: Bearer your-token"json
[
{
"id": 4,
"first_name": "Alice",
"last_name": "Johnson",
"roles": [
{"id": 1, "name": "Admin", "slug": "admin"}
],
"created_at": "2026-01-22T07:29:17.000000Z",
"deleted_at": null,
"profile_picture": {
"avif": "https://{tenant}.kendo.dev/api/users/4/profile-picture/avif",
"webp": "https://{tenant}.kendo.dev/api/users/4/profile-picture/webp"
},
"issue_ids": [1, 42, 105],
"project_ids": [1, 3],
"team_ids": [1],
"has_pending_invite": false
},
{
"id": 8,
"first_name": "Bob",
"last_name": "Smith",
"roles": [
{"id": 2, "name": "Member", "slug": "member"}
],
"created_at": "2026-02-01T09:00:00.000000Z",
"deleted_at": null,
"profile_picture": null,
"issue_ids": [23, 67],
"project_ids": [1],
"team_ids": [2],
"has_pending_invite": false
}
]Admin Response
When the requesting user is an admin, the response includes additional fields: email and expanded role permissions.
json
{
"id": 8,
"first_name": "Bob",
"last_name": "Smith",
"email": "bob@example.com",
"roles": [
{
"id": 2,
"name": "Member",
"slug": "member",
"is_admin": false,
"permissions": [
{
"resource": 2,
"can_create": true,
"can_read": true,
"can_update": 1,
"can_delete": 1
}
]
}
],
"created_at": "2026-02-01T09:00:00.000000Z",
"deleted_at": null,
"profile_picture": null,
"issue_ids": [23, 67],
"project_ids": [1],
"team_ids": [2],
"has_pending_invite": false
}See the Roles API for the full list of permission resources and scope values.
Update User
POST /api/users/{userId}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
first_name | string | Yes | First name, max 255 characters |
last_name | string | Yes | Last name, max 255 characters |
email | string | Yes | Email address, must be unique |
role_ids | integer[] | No | Role IDs to assign. At least 1 if provided. |
bash
curl -X POST https://{tenant}.kendo.dev/api/users/8 \
-H "Authorization: Bearer your-token" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Bob",
"last_name": "Smith",
"email": "bob@example.com",
"role_ids": [2, 5]
}'json
{
"id": 8,
"first_name": "Bob",
"last_name": "Smith",
"roles": [
{"id": 2, "name": "Member", "slug": "member"},
{"id": 5, "name": "Developer", "slug": "developer"}
],
"created_at": "2026-02-01T09:00:00.000000Z",
"deleted_at": null,
"profile_picture": null,
"issue_ids": [23, 67],
"project_ids": [1],
"team_ids": [2],
"has_pending_invite": false
}Resend Invitation
POST /api/users/{userId}/resend-invitation
Resends the invitation email for a user with a pending invite. Returns 204 No Content on success.
bash
curl -X POST https://{tenant}.kendo.dev/api/users/12/resend-invitation \
-H "Authorization: Bearer your-token"Delete User
DELETE /api/users/{userId}
Returns 204 No Content on success.
bash
curl -X DELETE https://{tenant}.kendo.dev/api/users/8 \
-H "Authorization: Bearer your-token"See Also
- Roles API — Manage roles and permissions assigned to users
- Issues API — Issues assigned to users via
assignee_id