Skip to content

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 GET requests. 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), and D-EXAMPLE456 (computer), and read the token from JUMP_API_TOKEN.
  • Python samples use the requests package. JavaScript samples need Node.js 18 or later and use top-level await: save them as .mjs files.

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.

curl --fail-with-body -sS -G https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/users \
  -H "Authorization: Bearer $JUMP_API_TOKEN" \
  --data-urlencode "email=ana@example.com"
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:

curl --fail-with-body -sS -X POST \
  https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/invite/user \
  -H "Authorization: Bearer $JUMP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sendTo": "ana@example.com", "singleUse": true, "groups": ["G-EXAMPLE789"]}'
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.

curl --fail-with-body -sS -X PATCH \
  https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111 \
  -H "Authorization: Bearer $JUMP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"remoteAccess": "disabled"}'
import os

import requests

resp = requests.patch(
    "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111",
    headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
    json={"remoteAccess": "disabled"},
)
resp.raise_for_status()
print(resp.json()["remoteAccess"])
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-admin or team-requires-active-user if it would leave the team without an Admin or without a user with remote access turned on.
  • active-users-exceed-licensed-quanity if 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 with id, email, firstName, and lastName. Users provisioned with SCIM also have scimUserInfo.

  • 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:

  1. Jump Desktop emails the user a link to turn off 2-factor authentication on their account.
  2. The user clicks the link within 24 hours. Until they do, nothing changes.
  3. 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.

curl --fail-with-body -sS -X POST \
  https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group \
  -H "Authorization: Bearer $JUMP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Finance"}'
import os

import requests

resp = requests.post(
    "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group",
    headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
    json={"name": "Finance"},
)
resp.raise_for_status()
print(resp.json()["id"])
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);
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$body = @{ name = "Finance" } | ConvertTo-Json

$group = Invoke-RestMethod -Method Post -Uri "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group" `
    -Headers $headers -ContentType "application/json" -Body $body
$group.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.

curl --fail-with-body -sS -X POST \
  https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/G-EXAMPLE789/members \
  -H "Authorization: Bearer $JUMP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"users": ["U-EXAMPLE111"], "devices": ["D-EXAMPLE456"]}'
import os

import requests

resp = requests.post(
    "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/G-EXAMPLE789/members",
    headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
    json={"users": ["U-EXAMPLE111"], "devices": ["D-EXAMPLE456"]},
)
resp.raise_for_status()
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.

curl --fail-with-body -sS -X PATCH \
  https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/group/members \
  -H "Authorization: Bearer $JUMP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"groups": ["G-EXAMPLE789", "G-EXAMPLE790"], "users": ["U-EXAMPLE111"]}'
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.

Task User Access Group
Get GET .../user/{userID}/annotations GET .../group/{groupID}/annotations
Add or update POST .../user/{userID}/annotations POST .../group/{groupID}/annotations
Remove POST .../user/{userID}/annotations/delete POST .../group/{groupID}/annotations/delete

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.

curl --fail-with-body -sS -X POST \
  https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111/annotations \
  -H "Authorization: Bearer $JUMP_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"annotations": [{"key": "Cost center", "value": "4410"}]}'
import os

import requests

resp = requests.post(
    "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/user/U-EXAMPLE111/annotations",
    headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
    json={"annotations": [{"key": "Cost center", "value": "4410"}]},
)
resp.raise_for_status()
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.