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) 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.
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:
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:
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, withoutdeviceID. - There is no
hasMoreEventsornextOffset. Increaseoffsetuntil a response has no events. limitis at most 100, anddescdefaults tofalse.
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.