Getting started
Every request is authenticated with a company API key. A key identifies exactly one organisation, and every endpoint returns only that organisation's data — with two legacy exceptions noted where they apply.
Base URLs
Base URL | Notes |
| Current API. Use this for new integrations. |
| Legacy. Two endpoints only, with an older response format. |
Authentication
Send your key in the THM-API-KEY header on every request. Keys are issued per organisation from your management dashboard.
curl -H "THM-API-KEY: your-key-here" \
https://tryhackme.com/api/v2/external/users
A missing, unrecognised or deactivated key returns 403. Deactivating a key in the dashboard takes effect immediately across both base URLs.
Response format
Endpoints under /api/v2/external wrap their payload in a status envelope. Legacy endpoints return their object directly, with no wrapper — worth handling explicitly if you call both.
// /api/v2/external/*
{ "status": "success", "data": { … } }
// /external/api/*
{ "roomInfo": [ … ] }
Errors
Status | Meaning |
| A parameter is missing or malformed. The message names the parameter. |
| API key missing, invalid, expired, or the learner you asked for is not in your organisation. |
| The room, learner or assignment does not exist. |
Pagination
Paginated endpoints take page (1-based) and return page, total and totalPages alongside the results. Page size is fixed at 10 on the v2 endpoints, except Completed rooms, which accepts its own limit. Legacy endpoints are not paginated.
All endpoints
Method | Path | Returns |
GET |
| Your seated learners |
GET |
| One learner's profile |
GET |
| One learner's room activity |
GET |
| Public rooms a learner finished |
GET |
| Your assignments |
GET |
| Per-learner assignment results |
GET |
| Your learners' scores in a room |
PUT |
| Assign a learner to a seat |
DELETE |
| Free a learner's seat |
POST |
| Time spent per learner |
GET |
| Your learners (legacy) |
GET |
| Public + your admins' rooms (legacy) |
Users
GET List learners
/api/v2/external/users
Returns every learner occupying a seat in your organisation, ten per page. Learners who have left, or who never claimed a seat, are not included.
Query parameters
Name | Type | Description |
|
| Page number, 1-based. Defaults to |
|
| Filters by username or email. Case-insensitive, max 100 characters. |
|
|
|
|
|
|
Response 200
Field | Type | Description |
|
| One entry per seated learner. |
|
| Stable identifier. Use this for the other user endpoints. |
|
| Public handle on the platform. |
|
| Address the seat was assigned to. |
|
| Real name, where the learner has set one. |
|
| URL of the profile image. |
|
| When the account was created, not when the seat was assigned. |
|
| Groups the learner belongs to, as |
|
| Lifetime points earned. |
|
| Points earned this calendar month. |
|
| Most recent activity on the platform. |
|
| Learners matching the query across all pages. |
|
| Page you received. |
|
| Pages available at the current filter. |
|
|
|
Example
curl -H "THM-API-KEY: $KEY" \
"https://tryhackme.com/api/v2/external/users?page=1&sortBy=email"
{
"status": "success",
"data": {
"users": [
{
"userId": "6501f2a4c9e1b83d7a4f1120",
"username": "ada.byron",
"email": "[email protected]",
"fullName": "Ada Byron",
"avatar": "https://tryhackme-images.s3.amazonaws.com/user-avatars/…",
"userDateSignUp": "2025-11-04T09:12:33.000Z",
"groups": [ { "groupId": "6501f2a4c9e1b83d7a4f0001", "title": "SOC Team" } ],
"totalPoints": 18420,
"monthlyPoints": 940,
"lastAction": "2026-09-14T17:41:02.000Z"
}
],
"total": 34,
"page": 1,
"totalPages": 4,
"privacy": { "individualDataVisible": true, "memberNamesVisible": true }
}
}privacy reflects your organisation's settings. When individualDataVisible is false, per-learner identity and activity fields are withheld rather than zeroed — a missing field means hidden, not empty.
GET Get a learner
/api/v2/external/users/:userId
Returns the profile of one learner in your organisation, including earned badges. Requesting a learner outside your organisation returns 403, not 404.
Path parameters
Name | Type | Description |
|
| 24-character hex id, as returned by List learners. Not the username. |
Response 200
Field | Type | Description |
|
| Stable identifier. |
|
| Public handle. |
|
| Address the seat was assigned to. |
|
| Real name, where set. |
|
| URL of the profile image. |
|
| When the account was created. |
|
| Groups the learner belongs to. |
|
| Earned badges, as |
|
| Lifetime points earned. |
|
| Most recent activity on the platform. |
Errors
Status | Meaning |
| userId is not a valid ObjectId. |
| The learner is not a member of your organisation. |
| No such learner. |
Example
curl -H "THM-API-KEY: $KEY" \
https://tryhackme.com/api/v2/external/users/6501f2a4c9e1b83d7a4f1120
GET Get a learner's room activity
/api/v2/external/users/:userId/rooms
Returns one learner's progress across every room they have joined, with lifetime activity totals and their ten most recent actions. Room progress is paginated ten per page; the totals and recent actions are not.
Path parameters
Name | Type | Description |
|
| 24-character hex id. |
Query parameters
Name | Type | Description |
|
| Page of |
|
| Filters rooms by name. Max 100 characters. |
|
|
|
|
|
|
Response 200
Field | Type | Description |
|
| Stable identifier. |
|
| Public handle. |
|
| URL of the profile image. |
|
| When the account was created. |
|
| Rooms entered, lifetime. |
|
| Rooms finished, lifetime. |
|
| Answers submitted, lifetime. |
|
| Correct answers, lifetime. |
|
| Virtual machines started, lifetime. |
|
| Ten most recent actions: |
|
| This page of rooms. |
|
| Room title. |
|
| Room's URL slug. |
|
| Whether every task is finished. |
|
| First entry into the room. |
|
| When it was first completed, or |
|
| Completions, counting resets. |
|
| Rooms across all pages. |
|
| Page you received. |
|
| Pages available. |
Example
curl -H "THM-API-KEY: $KEY" \
"https://tryhackme.com/api/v2/external/users/6501f2a4c9e1b83d7a4f1120/rooms?page=1"
GET List a learner's completed rooms
/api/v2/external/users/:username/completed-rooms
Returns the public rooms a learner has completed, newest first. Private and organisation-only rooms are excluded, so this is narrower than the roomsCompleted count from room activity. Addressed by username, not id.
Path parameters
Name | Type | Description |
|
| The learner's public handle. |
Query parameters
Name | Type | Description |
|
| Page number, 1-based. Defaults to |
|
| Rooms per page, max |
Response 200
Field | Type | Description |
|
| Completed rooms on this page. |
|
| Completed public rooms in total. |
|
| Page size applied. |
|
| Page you received. |
|
| Pages available. |
|
| Whether a further page exists. |
|
| Whether an earlier page exists. |
|
| Next page number, or |
|
| Previous page number, or |
|
| Index of the first row on this page. |
Errors
Status | Meaning |
| The learner is not a member of your organisation. |
| No such learner, or the account is suspended. |
Example
curl -H "THM-API-KEY: $KEY" \
"https://tryhackme.com/api/v2/external/users/ada.byron/completed-rooms?limit=50"
Assignments
GET List assignments
/api/v2/external/assignments
Returns every assignment created by your organisation, ten per page, with the number of learners assigned to each.
Query parameters
Name | Type | Description |
|
| Page number, 1-based. Defaults to |
|
| Filters by title. Case-insensitive, max 100 characters. |
|
| One of |
|
|
|
|
|
|
|
| Only assignments created on or after this date. |
|
| Only assignments created on or before this date. Must not precede |
Response 200
Field | Type | Description |
|
| Assignments on this page. |
|
| Use this with the scoreboard endpoint below. |
|
| Name given when the assignment was created. |
|
| Determines the shape of the scoreboard rows. |
|
| When the assignment was set. |
|
| Whether a deadline is in force. |
|
| The deadline, when one is set. |
|
| Learners assigned, counting group membership. |
|
| Assignments matching the query. |
|
| Page you received. |
|
| Pages available. |
Example
curl -H "THM-API-KEY: $KEY" \
"https://tryhackme.com/api/v2/external/assignments?assignmentType=room&sortBy=deadline"
GET Get an assignment scoreboard
/api/v2/external/assignments/:assignmentId/scoreboard
Returns per-learner results for one assignment, ten per page, covering everyone it was assigned to — directly or through a group. The shape of each row depends on the assignment type; check assignmentType before reading the rows.
Path parameters
Name | Type | Description |
|
| The |
Query parameters
Name | Type | Description |
|
| Page number, 1-based. Defaults to |
|
| Filters learners by name or email. Max 100 characters. |
Response 200
Field | Type | Description |
|
| The assignment itself: |
|
| Repeated at the top level so you can branch before parsing rows. |
|
| Learner rows — see the field sets below. |
|
| Learners assigned in total. |
|
| Page you received. |
|
| Pages available. |
|
|
|
Row fields — always present
Field | Type | Description |
|
| The learner's identifier. |
|
| Public handle. |
|
| Address on the seat. |
|
| Real name, where set. |
|
| URL of the profile image. |
|
| Whether they still hold a seat. Past members appear with |
|
| Points earned on this assignment. |
|
| Time spent, in seconds. |
|
| Completion time, or |
|
| Submission state, for example |
Row fields — by assignment type
Type | Additional fields |
|
|
|
|
|
|
Example
curl -H "THM-API-KEY: $KEY" \
https://tryhackme.com/api/v2/external/assignments/6501f2a4c9e1b83d7a4f2200/scoreboard
{
"status": "success",
"data": {
"assignment": {
"id": "6501f2a4c9e1b83d7a4f2200",
"title": "Week 3 — Network Fundamentals",
"assignmentType": "room",
"created": "2026-08-31T08:00:00.000Z",
"deadlineToggled": true,
"deadlineDate": "2026-09-07T23:59:59.000Z"
},
"assignmentType": "room",
"users": [
{
"id": "6501f2a4c9e1b83d7a4f1120",
"username": "ada.byron",
"email": "[email protected]",
"fullName": "Ada Byron",
"avatar": "https://tryhackme-images.s3.amazonaws.com/user-avatars/…",
"hasSeat": true,
"completedQuestions": 12,
"totalQuestions": 14,
"totalAttempts": 19,
"pointsAwarded": 280,
"timeSpentSeconds": 4310,
"completedAt": null,
"assignmentStatus": "not-submitted"
}
],
"total": 22,
"page": 1,
"totalPages": 3,
"privacy": { "individualDataVisible": true, "memberNamesVisible": true }
}
}Scoreboards
GET Get a room scoreboard
/api/v2/external/scoreboard?roomCode={code}
Returns the scoreboard for one room, filtered to learners in your organisation — not the platform-wide leaderboard. Learners who joined but scored nothing are included, so you can tell "not started" from "not assigned". Capped at 500 rows and not paginated.
Query parameters
Name | Type | Description |
|
| Required. The room's URL slug, for example |
Response 200
Field | Type | Description |
|
| Public handle. |
|
| URL of the profile image. |
|
| The learner's platform level. |
|
| Points scored in this room. |
|
| Position within this scoreboard, not platform-wide. |
|
| Per-task completion, keyed by task number. |
|
| When the score was last updated. |
|
| The learner's identifier. |
Errors
Status | Meaning |
|
|
| No room with that code, or it has been removed. |
Example
curl -H "THM-API-KEY: $KEY" \
"https://tryhackme.com/api/v2/external/scoreboard?roomCode=networkingessentials"
Seats
PUT Assign a learner to a seat
/api/v2/external/seats/users
Places an existing TryHackMe account into the first free seat in your organisation. The account must already exist — this endpoint does not create one.
Body
Field | Type | Description |
|
| Required. Email of the existing account to seat. |
Response 200
Field | Type | Description |
|
| Handle of the learner now occupying the seat. |
Errors
Status | Meaning |
|
|
|
|
Example
curl -X PUT -H "THM-API-KEY: $KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}' \
https://tryhackme.com/api/v2/external/seats/users
{ "status": "success", "data": { "username": "ada.byron" } }
DELETE Free a learner's seat
/api/v2/external/seats/users
Removes a learner from their seat in your organisation and from its subscription. The TryHackMe account itself is not deleted, and the learner keeps their progress.
Body
Field | Type | Description |
|
| Required. Email of the seated learner to remove. |
Response 200
Field | Type | Description |
|
| Handle of the learner removed. |
Errors
Status | Meaning |
|
|
|
|
Example
curl -X DELETE -H "THM-API-KEY: $KEY" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]"}' \
https://tryhackme.com/api/v2/external/seats/users
Reports
POST Time spent report
/api/v2/external/reports/time
Returns total time spent on rooms, per learner, over a date range you choose. Every address in userEmails must belong to your organisation — if any does not, the whole request fails and the response names the offending addresses.
Body
Field | Type | Description |
|
| Required. At least one address. All must be learners in your organisation. |
|
| Required. Any of |
|
| Required. Start of the range, for example |
|
| Required. End of the range. |
Response 200
Field | Type | Description |
|
| One entry per learner with recorded time, sorted by email. |
|
| The learner's identifier. |
|
| Public handle. |
|
| Address you asked for. |
|
| Real name, or an empty string. |
|
| Time spent across the selected room types. |
|
| Lowest |
|
| Highest |
Errors
Status | Meaning |
| A field is missing, or a date is not valid ISO 8601. |
|
|
Example
curl -X POST -H "THM-API-KEY: $KEY" \
-H "Content-Type: application/json" \
-d '{
"userEmails": ["[email protected]", "[email protected]"],
"roomTypes": ["challenge", "walkthrough"],
"from": "2026-08-01T00:00:00.000Z",
"to": "2026-09-01T00:00:00.000Z"
}' \
https://tryhackme.com/api/v2/external/reports/time
{
"status": "success",
"data": {
"minSeconds": 1240,
"maxSeconds": 18300,
"users": [
{ "_id": "6501f2a4c9e1b83d7a4f1120", "username": "ada.byron",
"email": "[email protected]", "fullName": "Ada Byron", "totalSeconds": 18300 },
{ "_id": "6501f2a4c9e1b83d7a4f1121", "username": "g.hopper",
"email": "[email protected]", "fullName": "Grace Hopper", "totalSeconds": 1240 }
]
}
}Learners with no recorded time in the range are omitted from users rather than returned with zero. When nothing matches at all, users is empty and both bounds are 0.
Legacy endpoints
GET Retrieve users
/external/api/users
Returns every learner on your organisation's seats and API-user list, in one response. Not paginated, so the payload grows with your organisation. List learners is the paginated equivalent.
Response 200
Field | Type | Description |
|
| Always |
|
| All learners, unpaginated. |
|
| Public handle. |
|
| Address on the seat. |
|
| URL of the profile image. |
|
| When the account was created. |
|
| Lifetime points. Note the singular field name. |
|
| Points earned this calendar month. |
|
| Your own external id for the learner, where one was registered. |
Example
curl -H "THM-API-KEY: $KEY" https://tryhackme.com/external/api/users
{
"success": true,
"users": [
{
"username": "ada.byron",
"email": "[email protected]",
"avatar": "https://tryhackme-images.s3.amazonaws.com/user-avatars/…",
"dateSignedUp": "2025-11-04T09:12:33.000Z",
"totalPoint": 18420,
"monthlyPoints": 940,
"extUserId": "EMP-4471"
}
]
}
GET Retrieve rooms
/external/api/rooms
Returns every public room on the TryHackMe platform, plus every room created by your organisation's admins — not only your organisation's rooms. Expect well over a thousand entries. Not paginated.
Response 200
Field | Type | Description |
|
| All matching rooms, unpaginated. |
|
| Room's URL slug. Pass this as |
|
| Display name. |
|
| Summary shown on the room page. |
|
|
|
Example
curl -H "THM-API-KEY: $KEY" https://tryhackme.com/external/api/rooms
{
"roomInfo": [
{
"code": "networkingessentials",
"title": "Networking Essentials",
"description": "Learn about the essential protocols that make networks work.",
"public": true
}
]
}Filter on public to separate the platform catalogue from your organisation's own rooms.
Questions about your API key or access? Contact your TryHackMe account manager.