Skip to content

Read activity, connection history, and billing events#

The REST API exposes the team activity history and connection history shown in the Teams dashboard's Activity Logs and Connection Logs, plus the team's billing ledger. Use these endpoints to archive or analyze logs and billing changes, for example to export each day's connections to CSV or to retain a computer's connection history before removing the computer deletes it.

Data Endpoint Teams dashboard
Team activity (users, computers, settings, billing changes) GET /v1/team/{teamID}/history Activity Logs
Connection history for the team or selected computers GET /v1/team/{teamID}/history/devices Connection Logs
Connection history for one computer (deprecated) GET /v1/team/{teamID}/device/{deviceID}/history
Billing ledger GET /v1/team/{teamID}/billing/events

Applies to#

  • A Jump Desktop for Teams team, and an API token from a team Admin or Admin (Read-Only). Every request on this page is a GET, so read-only tokens are sufficient. See Get started with the REST API for tokens, team IDs, and error responses.
  • Samples use placeholder IDs T-EXAMPLE123 (team) and D-EXAMPLE456 (computer), and read the token from JUMP_API_TOKEN.
  • Python samples use the requests package. JavaScript samples require Node.js 18 or later and use top-level await, so save them as .mjs files.

Time ranges and paging#

All three history endpoints accept the same query parameters:

Parameter Meaning
startTime Start of the range, in seconds since the Unix epoch. Inclusive.
stopTime End of the range, in seconds since the Unix epoch. Inclusive.
desc true returns the newest events first, false the oldest first.
limit Number of events to return per request.
offset Number of events to skip.

Set limit and desc explicitly instead of relying on default values.

The two team endpoints return hasMoreEvents. When hasMoreEvents is true, repeat the request with offset set to the returned nextOffset. For a stable export, use a fixed time range and desc=false.

Timestamps in responses use different units:

Where Field Unit
Activity events header.eventTimestampUnixNano Nanoseconds
Connection events timestamp Milliseconds
Billing ledger entries timestamp Seconds

Read team activity history#

GET /v1/team/{teamID}/history returns activity entries in the events array. Each entry has a header and one field naming the event type, such as teamUserAddedEvent, teamDeviceRemovedEvent, teamUserLoggedInEvent, or teamSettingsChangedEvent. The header contains the event time and, where recorded, tokenInfo identifying who made the change. The REST API reference lists every event type.

The following sample prints the 50 most recent events:

curl --fail-with-body -sS \
  "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history?limit=50&desc=true" \
  -H "Authorization: Bearer $JUMP_API_TOKEN"
import os

import requests

resp = requests.get(
    "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history",
    headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
    params={"limit": 50, "desc": "true"},
)
if not resp.ok:
    raise SystemExit(f"{resp.status_code} {resp.text}")

body = resp.json()
for entry in body.get("events") or []:
    nanos = entry.get("header", {}).get("eventTimestampUnixNano", 0)
    kinds = [key for key in entry if key != "header"]
    print(nanos // 1_000_000_000, ", ".join(kinds))
print("More events:", body.get("hasMoreEvents"), "next offset:", body.get("nextOffset"))
const res = await fetch(
  "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history?limit=50&desc=true",
  { headers: { Authorization: `Bearer ${process.env.JUMP_API_TOKEN}` } },
);
if (!res.ok) {
  console.error(res.status, await res.text());
  process.exit(1);
}

const body = await res.json();
for (const entry of body.events ?? []) {
  const seconds = Math.floor((entry.header?.eventTimestampUnixNano ?? 0) / 1e9);
  const kinds = Object.keys(entry).filter((key) => key !== "header");
  console.log(seconds, kinds.join(", "));
}
console.log("More events:", body.hasMoreEvents, "next offset:", body.nextOffset);

Invoke-RestMethod throws on a non-2xx response.

$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$uri = "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history?limit=50&desc=true"

try {
    $body = Invoke-RestMethod -Uri $uri -Headers $headers
} catch {
    Write-Error "$($_.Exception.Response.StatusCode.value__) $($_.ErrorDetails.Message)"
    exit 1
}

foreach ($entry in $body.events) {
    $kinds = $entry.PSObject.Properties.Name | Where-Object { $_ -ne "header" }
    "{0} {1}" -f [math]::Floor($entry.header.eventTimestampUnixNano / 1e9), ($kinds -join ", ")
}

Read connection history#

GET /v1/team/{teamID}/history/devices returns connection events in the deviceEvents array. To include every computer on the team, omit ids; to select computers, repeat ids once per computer (ids=D-EXAMPLE456&ids=D-EXAMPLE457).

Each event has id, timestamp, deviceID, computerHostName, and one of these fields:

Field Emitted when
incomingConnectionRequest A connection is requested, before it is established.
incomingConnectionEvent A connection is established.
authSucceededEvent Authentication for a Fluid connection succeeds.
authFailedEvent Authentication for a Fluid connection fails. Includes reason.
connectionClosedEvent A connection closes.

All events of one connection share the same connectionID. Connection details include the following fields:

  • tunnelID: fluid, rdp, vnc, or unknown.
  • sourceType: cloud, directip, or unknown.
  • peerInfo: the connecting user's email and ipAddress.

Authentication and close events carry these details in associatedIncomingConnectionEvent.

The length of history displayed in Connection Logs in the Teams dashboard is planFeatures.deviceEventsMaxRetrievalDuration (in seconds) in team information. Removing a computer from the team deletes its connection history, so export the history before removing the computer.

The following sample reads the last 24 hours of connection history for one computer:

now=$(date +%s)
curl --fail-with-body -sS \
  "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history/devices?ids=D-EXAMPLE456&startTime=$((now - 86400))&stopTime=$now&limit=100" \
  -H "Authorization: Bearer $JUMP_API_TOKEN"
import os
import time

import requests

now = int(time.time())
resp = requests.get(
    "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history/devices",
    headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
    params={"ids": ["D-EXAMPLE456"], "startTime": now - 86400, "stopTime": now, "limit": 100},
)
resp.raise_for_status()

for event in resp.json().get("deviceEvents") or []:
    print(event["timestamp"], event["deviceID"], event.get("computerHostName"))
const now = Math.floor(Date.now() / 1000);
const params = new URLSearchParams({ startTime: now - 86400, stopTime: now, limit: 100 });
params.append("ids", "D-EXAMPLE456");

const res = await fetch(
  `https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history/devices?${params}`,
  { headers: { Authorization: `Bearer ${process.env.JUMP_API_TOKEN}` } },
);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);

const body = await res.json();
for (const event of body.deviceEvents ?? []) {
  console.log(event.timestamp, event.deviceID, event.computerHostName);
}
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$uri = "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history/devices" +
    "?ids=D-EXAMPLE456&startTime=$($now - 86400)&stopTime=$now&limit=100"

(Invoke-RestMethod -Uri $uri -Headers $headers).deviceEvents | Select-Object timestamp, deviceID, computerHostName

One computer (deprecated endpoint)#

GET /v1/team/{teamID}/device/{deviceID}/history is deprecated. Use GET /v1/team/{teamID}/history/devices with ids instead. The deprecated endpoint differs as follows:

  • Events are in events, without deviceID.
  • The response has no hasMoreEvents or nextOffset. To page through results, increase offset until a response contains no events.
  • desc defaults to false.

Export yesterday's connection history to CSV#

The following Python script uses requests to write every connection event from yesterday (UTC) to connections.csv. The script follows nextOffset until hasMoreEvents is false. It only reads data from the REST API, and it overwrites connections.csv if the file exists.

import csv
import datetime
import os

import requests

URL = "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/history/devices"
HEADERS = {"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"}
EVENT_TYPES = [
    "incomingConnectionRequest",
    "incomingConnectionEvent",
    "authSucceededEvent",
    "authFailedEvent",
    "connectionClosedEvent",
]
FIELDS = [
    "time_utc", "device_id", "computer_host_name", "event", "connection_id",
    "protocol", "user_email", "ip_address", "local_user_name", "failure_reason",
]

# Yesterday, 00:00:00 to 23:59:59 UTC. stopTime is inclusive.
today = datetime.datetime.now(datetime.timezone.utc).replace(
    hour=0, minute=0, second=0, microsecond=0
)
start = int((today - datetime.timedelta(days=1)).timestamp())
stop = int(today.timestamp()) - 1


def fetch_page(offset):
    params = {"startTime": start, "stopTime": stop, "desc": "false", "limit": 1000, "offset": offset}
    resp = requests.get(URL, headers=HEADERS, params=params)
    resp.raise_for_status()
    return resp.json()


def to_row(event):
    kind = next((k for k in EVENT_TYPES if k in event), "")
    detail = event.get(kind) or {}
    connection = detail.get("associatedIncomingConnectionEvent", detail)
    peer = connection.get("peerInfo") or {}
    direct = connection.get("directConnectionInfo") or {}
    return {
        "time_utc": datetime.datetime.fromtimestamp(
            event["timestamp"] / 1000, datetime.timezone.utc
        ).isoformat(),
        "device_id": event.get("deviceID", ""),
        "computer_host_name": event.get("computerHostName", ""),
        "event": kind,
        "connection_id": detail.get("connectionID", ""),
        "protocol": connection.get("tunnelID", ""),
        "user_email": peer.get("email", ""),
        "ip_address": peer.get("ipAddress") or direct.get("ipaddress", ""),
        "local_user_name": detail.get("localUserName", ""),
        "failure_reason": detail.get("reason", ""),
    }


with open("connections.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=FIELDS)
    writer.writeheader()
    offset = 0
    while True:
        page = fetch_page(offset)
        for event in page.get("deviceEvents") or []:
            writer.writerow(to_row(event))
        if not page.get("hasMoreEvents"):
            break
        offset = page["nextOffset"]

To verify the export, open connections.csv and compare several rows with Connection Logs in the Teams dashboard for the same day.

Read the billing ledger#

GET /v1/team/{teamID}/billing/events returns the most recent team events affecting the billable amount, such as successful or failed payments, subscription updates or cancellations, user count updates, and user additions or removals. The response's events array contains the entries, newest first. Each entry has id, teamid, timestamp, and event; event contains one field naming the event type.

The published OpenAPI specification lists this endpoint as /teams/{teamid}/billing/events with an array response. The service uses the /v1/team/ path shown here and returns an object with an events array.

curl --fail-with-body -sS \
  https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/billing/events \
  -H "Authorization: Bearer $JUMP_API_TOKEN"
import json
import os

import requests

resp = requests.get(
    "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/billing/events",
    headers={"Authorization": f"Bearer {os.environ['JUMP_API_TOKEN']}"},
)
resp.raise_for_status()
print(json.dumps(resp.json(), indent=2))
const res = await fetch("https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/billing/events", {
  headers: { Authorization: `Bearer ${process.env.JUMP_API_TOKEN}` },
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(JSON.stringify(await res.json(), null, 2));
$headers = @{ Authorization = "Bearer $env:JUMP_API_TOKEN" }

Invoke-RestMethod -Uri "https://api.jumpdesktop.com/v1/team/T-EXAMPLE123/billing/events" `
    -Headers $headers | ConvertTo-Json -Depth 20