Detections and replays
All paths are relative to https://api.thorium.ac/api/v1 and need
Authorization: Bearer thor_.... See the overview for paging, rate limits
and errors.
A detection is one incident: one check firing for one player, on one server, in one
session. If the check keeps firing in that session, the same detection’s count goes up
rather than a new one being created. Detections are returned within your plan’s
history window.
The detection object
{ "id": "9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab", "alertType": "MCSpeed", "count": 3, "acknowledged": false, "acknowledgedReason": "", "createdAt": "2026-09-28T17:50:02.114Z", "updatedAt": "2026-09-28T17:51:40.902Z", "sessionId": "e41b6c7d-0a92-4f3e-8d15-6b2c9f0e7a31", "playerId": "0f6e2c1a-8b4d-4e57-9a3c-2d1f7b6e5a90", "playerUsername": "Notch", "playerSteamId": "", "playerUuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "serverId": "11111111-2222-4333-8444-555555555555", "serverName": "Survival"}| Field | Meaning |
|---|---|
id | Detection id. Also the replay id and the alert_id in webhooks. |
alertType | The check, e.g. MCSpeed, MCMultiAura. See detections. |
count | How many times the check fired in this incident. |
acknowledged | true once reviewed. |
acknowledgedReason | The reviewer’s note, empty until reviewed. |
createdAt | First time the check fired. |
updatedAt | Last time it fired or was reviewed; null if neither has happened since creation. |
sessionId | The player session it happened in. |
playerUsername, serverName | "Unknown" if the player or server no longer exists. |
List detections
GET /players/paginated/detections · scope ViewAlerts
Newest activity first by default. Dismissed detections are not returned.
| Query | Default | Meaning |
|---|---|---|
orgId | required | Network id |
page, pageSize | 1, 50 | pageSize max 100 |
search | Check name (substring), username (word match) or exact Minecraft UUID | |
check | all | Exact check name, e.g. MCSpeed |
status | all | open (not reviewed) or reviewed |
serverId | all | Only this server |
since | Only detections created at or after this time (ISO 8601) | |
sort | lastFlag | lastFlag, check, player, server, flags or status |
dir | desc | asc or desc |
curl -s -H "Authorization: Bearer $THORIUM_API_KEY" \ "https://api.thorium.ac/api/v1/players/paginated/detections?orgId=$ORG_ID&status=open&since=2026-09-28T00:00:00Z&pageSize=1"{ "detections": [ { "id": "9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab", "alertType": "MCSpeed", "count": 3, "acknowledged": false, "acknowledgedReason": "", "createdAt": "2026-09-28T17:50:02.114Z", "updatedAt": "2026-09-28T17:51:40.902Z", "sessionId": "e41b6c7d-0a92-4f3e-8d15-6b2c9f0e7a31", "playerId": "0f6e2c1a-8b4d-4e57-9a3c-2d1f7b6e5a90", "playerUsername": "Notch", "playerSteamId": "", "playerUuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "serverId": "11111111-2222-4333-8444-555555555555", "serverName": "Survival" } ], "history": { "days": 90, "retentionDays": 120, "since": "2026-06-30T18:04:05.000Z" }, "pagination": { "currentPage": 1, "pageSize": 1, "totalDetections": 27, "totalPages": 27, "hasNextPage": true, "hasPreviousPage": false }}To poll for new detections, keep the newest createdAt you have seen and pass it as since
next time, or use webhooks instead of polling.
Detection stats
GET /players/alerts/stats · scope ViewAlerts
Counts over the last days days (default 7, max 365, and never further back than your plan’s
history window).
curl -s -H "Authorization: Bearer $THORIUM_API_KEY" \ "https://api.thorium.ac/api/v1/players/alerts/stats?orgId=$ORG_ID&days=3"{ "total": 58, "acknowledged": 21, "dismissed": 4, "byType": [ { "type": "MCSpeed", "count": 19 }, { "type": "MCMultiAura", "count": 12 }, { "type": "MCReach", "count": 9 } ], "byDay": [ { "date": "2026-09-26", "count": 17 }, { "date": "2026-09-27", "count": 22 }, { "date": "2026-09-28", "count": 15 } ]}total includes dismissed detections; byType and byDay do not. Days are UTC.
Mark a detection reviewed
PATCH /players/alerts/{alertId}/acknowledge · scope ManageDetections
Marks the detection as reviewed with a note. It stays in lists and counts, with
acknowledged: true.
curl -s -X PATCH \ -H "Authorization: Bearer $THORIUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "Checked replay, player banned"}' \ "https://api.thorium.ac/api/v1/players/alerts/9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab/acknowledge?orgId=$ORG_ID"{ "id": "9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab", "alertType": "MCSpeed", "count": 3, "acknowledged": true, "acknowledgedReason": "Checked replay, player banned", "createdAt": "2026-09-28T17:50:02.114Z", "updatedAt": "2026-09-28T18:12:44.301Z", "playerId": "0f6e2c1a-8b4d-4e57-9a3c-2d1f7b6e5a90", "serverId": "11111111-2222-4333-8444-555555555555", "sessionId": "e41b6c7d-0a92-4f3e-8d15-6b2c9f0e7a31", "details": "{\"reason\":\"speed\",\"obs\":0.412,\"lim\":0.351,\"mods\":[\"eff:speed:1\"],\"ticks\":6,\"ping\":85}"}details is the check’s evidence as a JSON-encoded string. Every alert has reason. Checks that
measure one value against one limit add obs (what the player did) and lim (the limit on this
server, before tolerance). mods lists what moved that limit off vanilla, such as eff:speed:1
(Speed II), attr:entity_interaction_range:4 or gm:1 (creative). Other keys vary by check.
400 Reason is required. without a reason, 404 Alert not found. if the id is not on this
network or was dismissed.
Dismiss a detection
PATCH /players/alerts/{alertId}/dismiss · scope ManageDetections
Marks the detection as a false positive and removes it from lists and charts. The reason is
stored as FALSE_POSITIVE: <reason>. This cannot be undone through the API.
curl -s -X PATCH \ -H "Authorization: Bearer $THORIUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "Lag spike, player was on a boat"}' \ "https://api.thorium.ac/api/v1/players/alerts/9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab/dismiss?orgId=$ORG_ID"Returns 204 No Content.
Review many at once
PATCH /players/alerts/bulk · scope ManageDetections
Acknowledges or dismisses up to 100 detections with one reason. Ids that are not on this network, or are already dismissed, are skipped.
curl -s -X PATCH \ -H "Authorization: Bearer $THORIUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "ids": ["9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab", "4e8f2a6c-1b3d-4f5a-9c7e-0d2b4f6a8c1e"], "action": "acknowledge", "reason": "Weekly review" }' \ "https://api.thorium.ac/api/v1/players/alerts/bulk?orgId=$ORG_ID"{ "updated": 2 }action is acknowledge or dismiss. 400 for a missing reason, another action, or an
empty or oversized ids list.
Replays
Many Minecraft detections come with a replay: the few seconds of movement, combat and world
data around the detection. A replay’s id is the detection’s id. The replay endpoints are
not rate limited.
Check a replay exists
HEAD /replay/exists?replayId={id}&orgId={orgId} · scope ViewAlerts
200 if there is a replay, 404 if not.
curl -s -o /dev/null -w "%{http_code}\n" -I \ -H "Authorization: Bearer $THORIUM_API_KEY" \ "https://api.thorium.ac/api/v1/replay/exists?replayId=9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab&orgId=$ORG_ID"Replay metadata
GET /replay/alert?replayId={id}&orgId={orgId} · scope ViewAlerts
{ "id": "9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab", "alertType": "MCSpeed", "acknowledged": false, "createdAt": "2026-09-28T17:50:02.114Z", "details": "{\"reason\":\"speed\",\"obs\":0.412,\"lim\":0.351,\"mods\":[\"eff:speed:1\"],\"ticks\":6,\"ping\":85}", "serverId": "11111111-2222-4333-8444-555555555555", "serverName": "Survival", "playerUuid": "069a79f4-44e9-4726-a5be-fca90e38aaf5", "playerUsername": "Notch"}Download a replay
GET /replay/download?replayId={id}&orgId={orgId} · scope ViewAlerts
Returns the replay as application/octet-stream, named {id}.bin.
curl -s -H "Authorization: Bearer $THORIUM_API_KEY" -o 9a1d0a0e.bin \ "https://api.thorium.ac/api/v1/replay/download?replayId=9a1d0a0e-2f3b-5c4d-8e6f-0123456789ab&orgId=$ORG_ID"The file is Thorium’s own capture format, for archiving or attaching to a ticket. To watch
it, open https://thorium.ac/dashboard/replay/{id} in the dashboard. Replays follow your
plan’s history window.