Skip to content

Read activity, connection history, and billing events

Archive or analyze what the Teams dashboard shows in Activity Logs and Connection Logs, or read billing changes. For example, export each day's connections to CSV, or keep connection history before removing a 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 work. 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 need Node.js 18 or later and use top-level await: save them as .mjs files.

Time ranges and paging

All three history endpoints take 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 Events per request. Up to 1001 for the team endpoints, up to 100 for the per-computer endpoint.
offset Number of events to skip.

Set limit and desc explicitly rather than relying on defaults.

The two team endpoints return hasMoreEvents. When it's 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 entries in events. Each has a header (event time and, where recorded, tokenInfo about who made the change) plus one field naming the event type, such as teamUserAddedEvent, teamDeviceRemovedEvent, teamUserLoggedInEvent, or teamSettingsChangedEvent. The reference lists every event type.

This 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 deviceEvents. Leave out ids for every computer on the team, or 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's 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.

The same connectionID ties together the events of one connection. Connection details include tunnelID (fluid, rdp, vnc, or unknown), sourceType (cloud, directip, or unknown), and peerInfo with the connecting user's email and ipAddress. Authentication and close events carry these details in associatedIncomingConnectionEvent.

The number of days of history the Teams dashboard's Connection Logs show is planFeatures.deviceEventsMaxRetrievalDuration (in seconds) in team information. Removing a computer from the team deletes its connection history, so export first.

This sample reads the last 24 hours 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. If you still call it:

  • Events are in events, without deviceID.
  • There is no hasMoreEvents or nextOffset. Increase offset until a response has no events.
  • limit is at most 100, and desc defaults to false.

Export yesterday's connection history to CSV

This Python script (using requests) writes every connection event from yesterday (UTC) to connections.csv, following nextOffset until hasMoreEvents is false. It only reads data, and it overwrites connections.csv if it 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 confirm it worked, open connections.csv and compare a few rows with Connection Logs in the Teams dashboard for the same day.

Read the billing ledger

GET /v1/team/{teamID}/billing/events returns the 10 most recent team events that affect the billable amount, such as payment succeeded or failed, subscription updated or canceled, user count updated, and users added or removed. The response's events array holds the entries, newest first. Each entry has id, teamid, timestamp, and event, where event has one field naming the event type.

The published OpenAPI spec 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