Manage users and access with the REST API¶
Invite, change, and remove team users, and manage Access Groups. Use it to automate onboarding and offboarding, for example to invite new staff into the right Access Groups or remove people who leave. To set who can connect to a single computer, see Manage computers with the REST API.
Applies to¶
- A Jump Desktop for Teams team, and an API token from a team Admin. Admin (Read-Only) users and read-only tokens can only make
GETrequests. See Get started with the REST API for tokens, team IDs, and error responses. - Access Groups: the number of groups you can create depends on your plan. See Control access with Access Groups.
- Samples use placeholder IDs
T-EXAMPLE123(team),U-EXAMPLE111(user),G-EXAMPLE789(Access Group), andD-EXAMPLE456(computer), and read the token fromJUMP_API_TOKEN. - Python samples use the
requestspackage. JavaScript samples need Node.js 18 or later and use top-levelawait: save them as.mjsfiles.
Find a user ID¶
Look up a user by email with GET /v1/team/{teamID}/users. The email filter matches the whole address, not part of it. With no filter, the endpoint returns every user on the team.
import os
import requests
resp = requests.get(
"https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/users",
headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
params={"email": "ana@example.com"},
)
if not resp.ok:
raise SystemExit(f"{resp.status_code} {resp.text}")
users = resp.json()["users"]
print(users[0]["id"] if users else "No user with that email")
const url = new URL("https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/users");
url.searchParams.set("email", "ana@example.com");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.JUMP_API_TOKEN}` },
});
if (!res.ok) {
console.error(res.status, await res.text());
process.exit(1);
}
const { users } = await res.json();
console.log(users.length ? users[0].id : "No user with that email");
Invoke-RestMethod throws on a non-2xx response.
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$uri = "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/users?email=ana@example.com"
try {
$resp = Invoke-RestMethod -Uri $uri -Headers $headers
} catch {
Write-Error "$($_.Exception.Response.StatusCode.value__) $($_.ErrorDetails.Message)"
exit 1
}
if ($resp.users.Count -gt 0) { $resp.users[0].id } else { "No user with that email" }
An empty users array means no match. Each user includes id, email, role, remoteAccess, totpEnabled, and annotations.
The published OpenAPI spec lists role as admin or user; the service also returns admin-read-only for an Admin (Read-Only).
Invite users¶
Create an invite with POST /v1/team/{teamID}/invite/user.
| Field | Use |
|---|---|
sendTo |
Email address to send the invite to. Leave it out to create a public invite link. |
singleUse |
true for email invites. Requires sendTo. |
name |
Invite name shown in the Teams dashboard. Up to 64 characters. |
groups |
Access Group IDs the user joins when they accept. |
devices |
Computer IDs the user can connect to when they accept. |
This sample emails a single-use invite that adds the user to one Access Group:
import os
import requests
resp = requests.post(
"https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/invite/user",
headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
json={"sendTo": "ana@example.com", "singleUse": True, "groups": ["G-EXAMPLE789"]},
)
resp.raise_for_status()
print(resp.json()["secret"])
const res = await fetch("https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/invite/user", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JUMP_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ sendTo: "ana@example.com", singleUse: true, groups: ["G-EXAMPLE789"] }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log((await res.json()).secret);
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$body = @{ sendTo = "ana@example.com"; singleUse = $true; groups = @("G-EXAMPLE789") } | ConvertTo-Json
$invite = Invoke-RestMethod -Method Post -Uri "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/invite/user" `
-Headers $headers -ContentType "application/json" -Body $body
$invite.secret
The response is the invite, including its secret. New members join with the user role. See what invitees see.
Delete an invite¶
Call DELETE /v1/team/{teamID}/invite/user/{secret} with the invite's secret. The invite link stops working. Pending invites and their secrets are listed in userInvites from GET /v1/team/{teamID}.
curl --fail-with-body -sS -X DELETE \
https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/invite/user/INVITE-SECRET \
-H "Authorization: Bearer $JUMP_API_TOKEN"
Change a user's role or remote access¶
Send one or both fields to PATCH /v1/team/{teamID}/user/{userID}. Fields you leave out keep their current value. The response is the updated user.
| Field | Values |
|---|---|
role |
admin, admin-read-only, or user |
remoteAccess |
enabled or disabled |
The published OpenAPI spec lists only admin and user; the service also accepts admin-read-only for Admin (Read-Only).
Turning remote access off disconnects the user's active sessions. For what roles and remote access mean, including billing, see Team roles and remote access.
const res = await fetch("https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.JUMP_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ remoteAccess: "disabled" }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log((await res.json()).remoteAccess);
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$body = @{ remoteAccess = "disabled" } | ConvertTo-Json
$user = Invoke-RestMethod -Method Patch -Uri "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111" `
-Headers $headers -ContentType "application/json" -Body $body
$user.remoteAccess
The request fails with:
team-requires-adminorteam-requires-active-userif it would leave the team without an Admin or without a user with remote access turned on.active-users-exceed-licensed-quanityif turning remote access on would exceed the team's licensed users.
Remove a user¶
Warning
Removing a user takes effect immediately. The user loses access to all of the team's computers, their active sessions are disconnected, and they're removed from the team's Access Groups. To pause access instead, turn remote access off.
Call DELETE /v1/team/{teamID}/user/{userID}. The user's Jump Desktop account and their other teams aren't affected. You can't remove the team's last Admin or last user with remote access turned on.
curl --fail-with-body -sS -X DELETE \
https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111 \
-H "Authorization: Bearer $JUMP_API_TOKEN"
Add SSO users without an invite¶
If your team uses single sign-on, you can add people who sign in with it directly, with no invite or email.
Requirements:
- The token belongs to an Admin who signs in with SSO and is an Admin of both this team and the team that owns that SSO connection.
-
The user you add signs in with the same SSO.
-
List users who sign in with the same SSO connection with
GET /v1/team/{teamID}/saml/users. The response is an array of users withid,email,firstName, andlastName. Users provisioned with SCIM also havescimUserInfo. - Add a user with
POST /v1/team/{teamID}/users/{userID}. No request body is needed.
curl --fail-with-body -sS -X POST \
https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/users/U-EXAMPLE111 \
-H "Authorization: Bearer $JUMP_API_TOKEN"
The user joins with the user role and remote access turned on. The request returns 409 if the user is already on the team, and active-users-exceed-licensed-quanity if the team has no licensed users left.
Check a user's sign-in security¶
GET /v1/team/{teamID}/user/{userID}/info returns:
| Field | Contains |
|---|---|
totp.enabled |
Whether 2-factor authentication is on. |
authProviders |
The user's sign-in methods in provider. Provider email addresses and avatars are masked. |
tokenLog |
The user's sign-in tokens, with title, scopes, lastUsed, lastUsedIPAddress, and expiresAt. |
For profile details, use Find a user ID.
Ask a user to turn off 2-factor authentication¶
This is a Teams Enterprise feature. If a user loses their 2-factor authentication device, call POST /v1/team/{teamID}/user/{userID}/removetotp. It doesn't turn off 2-factor authentication itself:
- Jump Desktop emails the user a link to turn off 2-factor authentication on their account.
- The user clicks the link within 24 hours. Until they do, nothing changes.
- When they confirm, 2-factor authentication is turned off for their Jump Desktop account, not only for your team.
The request fails with totp-not-enabled if the user doesn't have 2-factor authentication turned on, or doesn't sign in with a Jump Desktop email and password (for example, SSO-only users).
curl --fail-with-body -sS -X POST \
https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111/removetotp \
-H "Authorization: Bearer $JUMP_API_TOKEN"
To confirm, check totp.enabled with Check a user's sign-in security after the user clicks the link.
Manage Access Groups¶
An Access Group gives its users remote access to its computers. See Control access with Access Groups.
To list groups and their IDs, call GET /v1/team/{teamID}. The groups object is keyed by group ID; each group has name, users, devices, and scimManaged.
Create, rename, or delete a group¶
| Task | Request | Body |
|---|---|---|
| Create | POST /v1/team/{teamID}/group |
{"name": "..."}, 1 to 64 characters |
| Rename | PATCH /v1/team/{teamID}/group/{groupID} |
{"name": "..."}, up to 64 characters |
| Delete | DELETE /v1/team/{teamID}/group/{groupID} |
None |
Creating a group returns it with its new id. It fails with team-too-many-groups when the team has reached its plan's group limit. Deleting a group removes the access it granted; its users and computers stay on the team.
const res = await fetch("https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JUMP_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Finance" }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log((await res.json()).id);
Add or remove members of one group¶
| Task | Request |
|---|---|
| Add | POST /v1/team/{teamID}/group/{groupID}/members |
| Remove | POST /v1/team/{teamID}/group/{groupID}/members/delete |
Both take users (user IDs) and devices (computer IDs). Either can be left out. Members not listed aren't changed.
const res = await fetch(
"https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/G-EXAMPLE789/members",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JUMP_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ users: ["U-EXAMPLE111"], devices: ["D-EXAMPLE456"] }),
},
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$body = @{ users = @("U-EXAMPLE111"); devices = @("D-EXAMPLE456") } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/G-EXAMPLE789/members" `
-Headers $headers -ContentType "application/json" -Body $body
Add or remove members across several groups¶
| Task | Request |
|---|---|
| Add | PATCH /v1/team/{teamID}/group/members |
| Remove | PATCH /v1/team/{teamID}/group/members/remove |
Both take groups (group IDs), plus the users and devices to add to or remove from every listed group. The response returns 200 even if some groups fail; check its errors array. Each error has groupID, code, and detail.
import os
import requests
resp = requests.patch(
"https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/members",
headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
json={"groups": ["G-EXAMPLE789", "G-EXAMPLE790"], "users": ["U-EXAMPLE111"]},
)
resp.raise_for_status()
for error in resp.json().get("errors") or []:
print(error["groupID"], error["code"], error["detail"])
const res = await fetch("https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/members", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.JUMP_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ groups: ["G-EXAMPLE789", "G-EXAMPLE790"], users: ["U-EXAMPLE111"] }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { errors = [] } = await res.json();
for (const error of errors) console.error(error.groupID, error.code, error.detail);
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$body = @{ groups = @("G-EXAMPLE789", "G-EXAMPLE790"); users = @("U-EXAMPLE111") } | ConvertTo-Json
$result = Invoke-RestMethod -Method Patch -Uri "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/members" `
-Headers $headers -ContentType "application/json" -Body $body
$result.errors | ForEach-Object { "$($_.groupID) $($_.code) $($_.detail)" }
SCIM-managed groups¶
Groups pushed from your identity provider with SCIM have scimManaged set to true. Their name and users come from your identity provider, so the REST API:
- Rejects renaming or deleting the group.
- Rejects adding or removing users with the single-group endpoints. In the bulk endpoints, users are skipped for that group with the error code
group-scim-managed. - Allows adding and removing computers in a single-group request that lists no users. A single-group request that lists any users is rejected entirely, including its computers.
Add labels with annotations¶
Annotations are key-value pairs on users and Access Groups, for example an employee number or cost center. Keys are 1 to 64 characters; values are up to 2,048 characters.
All paths start with /v1/team/{teamID}. Add or update takes {"annotations": [{"key": "...", "value": "..."}]} and overwrites the value of any key that already exists. Remove takes {"annotationKeys": ["..."]}.
In the Teams dashboard, a user's custom fields are annotations whose key is the field's title, so writing that key changes the field.
const res = await fetch(
"https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111/annotations",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.JUMP_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ annotations: [{ key: "Cost center", value: "4410" }] }),
},
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$body = @{ annotations = @(@{ key = "Cost center"; value = "4410" }) } | ConvertTo-Json -Depth 3
Invoke-RestMethod -Method Post -Uri "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111/annotations" `
-Headers $headers -ContentType "application/json" -Body $body
Requests over the annotation limit fail with team-too-many-annotations.