Skip to main content

TryHackMe Enterprise & Education API

Read your organisation's learners, assignments and progress, and manage seats, over HTTP.

J
Written by Johanna Kuhlee

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

https://tryhackme.com/api/v2/external

Current API. Use this for new integrations.

https://tryhackme.com/external/api

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

400

A parameter is missing or malformed. The message names the parameter.

403

API key missing, invalid, expired, or the learner you asked for is not in your organisation.

404

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

/users

Your seated learners

GET

/users/:userId

One learner's profile

GET

/users/:userId/rooms

One learner's room activity

GET

/users/:username/completed-rooms

Public rooms a learner finished

GET

/assignments

Your assignments

GET

/assignments/:assignmentId/scoreboard

Per-learner assignment results

GET

/scoreboard

Your learners' scores in a room

PUT

/seats/users

Assign a learner to a seat

DELETE

/seats/users

Free a learner's seat

POST

/reports/time

Time spent per learner

GET

/external/api/users

Your learners (legacy)

GET

/external/api/rooms

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

integer

Page number, 1-based. Defaults to 1.

search

string

Filters by username or email. Case-insensitive, max 100 characters.

sortBy

enum

username or email. Defaults to username.

sortOrder

enum

asc or desc. Defaults to asc.

Response 200

Field

Type

Description

users[]

array

One entry per seated learner.

users[].userId

string

Stable identifier. Use this for the other user endpoints.

users[].username

string

Public handle on the platform.

users[].email

string

Address the seat was assigned to.

users[].fullName

string

Real name, where the learner has set one.

users[].avatar

string

URL of the profile image.

users[].userDateSignUp

date

When the account was created, not when the seat was assigned.

users[].groups[]

array

Groups the learner belongs to, as groupId and title.

users[].totalPoints

number

Lifetime points earned.

users[].monthlyPoints

number

Points earned this calendar month.

users[].lastAction

date

Most recent activity on the platform.

total

number

Learners matching the query across all pages.

page

number

Page you received.

totalPages

number

Pages available at the current filter.

privacy

object

individualDataVisible and memberNamesVisible — see the note below.

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

userId

ObjectId

24-character hex id, as returned by List learners. Not the username.

Response 200

Field

Type

Description

userId

string

Stable identifier.

username

string

Public handle.

email

string

Address the seat was assigned to.

fullName

string

Real name, where set.

avatar

string

URL of the profile image.

userDateSignUp

date

When the account was created.

groups[]

array

Groups the learner belongs to.

badges[]

array

Earned badges, as name, image and earnedAt.

totalPoints

number

Lifetime points earned.

lastAction

date

Most recent activity on the platform.

Errors

Status

Meaning

400

userId is not a valid ObjectId.

403

The learner is not a member of your organisation.

404

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

userId

ObjectId

24-character hex id.

Query parameters

Name

Type

Description

page

integer

Page of roomProgress. Defaults to 1.

search

string

Filters rooms by name. Max 100 characters.

sortBy

enum

roomName or roomCode. Defaults to roomName.

sortOrder

enum

asc or desc. Defaults to asc.

Response 200

Field

Type

Description

userId

string

Stable identifier.

username

string

Public handle.

avatar

string

URL of the profile image.

userDateSignUp

date

When the account was created.

roomsJoined

number

Rooms entered, lifetime.

roomsCompleted

number

Rooms finished, lifetime.

questionsAnswered

number

Answers submitted, lifetime.

questionsAnsweredCorrectly

number

Correct answers, lifetime.

vmsDeployed

number

Virtual machines started, lifetime.

recentActions[]

array

Ten most recent actions: roomCode, roomName, occurredAt, action.

roomProgress[]

array

This page of rooms.

roomProgress[].name

string

Room title.

roomProgress[].code

string

Room's URL slug.

roomProgress[].roomComplete

boolean

Whether every task is finished.

roomProgress[].joinedAt

date

First entry into the room.

roomProgress[].completedAt

date

When it was first completed, or null.

roomProgress[].timesCompleted

number

Completions, counting resets.

totalRoomProgress

number

Rooms across all pages.

page

number

Page you received.

totalPages

number

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

username

string

The learner's public handle.

Query parameters

Name

Type

Description

page

integer

Page number, 1-based. Defaults to 1.

limit

integer

Rooms per page, max 100. Defaults to 16.

Response 200

Field

Type

Description

docs[]

array

Completed rooms on this page.

totalDocs

number

Completed public rooms in total.

limit

number

Page size applied.

page

number

Page you received.

totalPages

number

Pages available.

hasNextPage

boolean

Whether a further page exists.

hasPrevPage

boolean

Whether an earlier page exists.

nextPage

number

Next page number, or null.

prevPage

number

Previous page number, or null.

pagingCounter

number

Index of the first row on this page.

Errors

Status

Meaning

403

The learner is not a member of your organisation.

404

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

integer

Page number, 1-based. Defaults to 1.

search

string

Filters by title. Case-insensitive, max 100 characters.

assignmentType

enum

One of path, module, room, soc-sim, threat-hunting, skill-review.

sortBy

enum

title, deadline, assignmentType, created or users. Defaults to title.

sortOrder

enum

ascending or descending. Defaults to ascending.

startDate

YYYY-MM-DD

Only assignments created on or after this date.

endDate

YYYY-MM-DD

Only assignments created on or before this date. Must not precede startDate.

Response 200

Field

Type

Description

assignments[]

array

Assignments on this page.

assignments[].id

string

Use this with the scoreboard endpoint below.

assignments[].title

string

Name given when the assignment was created.

assignments[].assignmentType

enum

Determines the shape of the scoreboard rows.

assignments[].created

date

When the assignment was set.

assignments[].deadlineToggled

boolean

Whether a deadline is in force.

assignments[].deadlineDate

date

The deadline, when one is set.

assignments[].users

number

Learners assigned, counting group membership.

total

number

Assignments matching the query.

page

number

Page you received.

totalPages

number

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

assignmentId

ObjectId

The id from List assignments.

Query parameters

Name

Type

Description

page

integer

Page number, 1-based. Defaults to 1.

search

string

Filters learners by name or email. Max 100 characters.

Response 200

Field

Type

Description

assignment

object

The assignment itself: id, title, assignmentType, created, deadlineToggled, deadlineDate.

assignmentType

enum

Repeated at the top level so you can branch before parsing rows.

users[]

array

Learner rows — see the field sets below.

total

number

Learners assigned in total.

page

number

Page you received.

totalPages

number

Pages available.

privacy

object

individualDataVisible and memberNamesVisible.

Row fields — always present

Field

Type

Description

id

string

The learner's identifier.

username

string

Public handle.

email

string

Address on the seat.

fullName

string

Real name, where set.

avatar

string

URL of the profile image.

hasSeat

boolean

Whether they still hold a seat. Past members appear with false.

pointsAwarded

number

Points earned on this assignment.

timeSpentSeconds

number

Time spent, in seconds.

completedAt

date

Completion time, or null.

assignmentStatus

enum

Submission state, for example not-submitted.

Row fields — by assignment type

Type

Additional fields

path, module, room, skill-review

completedQuestions, totalQuestions, totalAttempts

soc-sim

mttr (mean time to resolve), runId, runStatus, runCompletion

threat-hunting

completionPercentage, runId, runStatus, runCompletion

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

roomCode

string

Required. The room's URL slug, for example networkingessentials.

Response 200

Field

Type

Description

username

string

Public handle.

avatar

string

URL of the profile image.

level

number

The learner's platform level.

score

number

Points scored in this room.

rank

number

Position within this scoreboard, not platform-wide.

tasks

object

Per-task completion, keyed by task number.

timeScored

date

When the score was last updated.

userId

string

The learner's identifier.

Errors

Status

Meaning

400

roomCode is missing or empty.

404

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

email

string

Required. Email of the existing account to seat.

Response 200

Field

Type

Description

username

string

Handle of the learner now occupying the seat.

Errors

Status

Meaning

400

email not found. — no TryHackMe account uses that address.

400

No available seats in dashboard. — every seat is occupied.

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

email

string

Required. Email of the seated learner to remove.

Response 200

Field

Type

Description

username

string

Handle of the learner removed.

Errors

Status

Meaning

400

email not found. — no TryHackMe account uses that address.

400

User is not currently in a company seat.

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

userEmails

string[]

Required. At least one address. All must be learners in your organisation.

roomTypes

enum[]

Required. Any of challenge, walkthrough, network, mini-challenge.

from

ISO 8601

Required. Start of the range, for example 2026-08-01T00:00:00.000Z.

to

ISO 8601

Required. End of the range.

Response 200

Field

Type

Description

users[]

array

One entry per learner with recorded time, sorted by email.

users[]._id

string

The learner's identifier.

users[].username

string

Public handle.

users[].email

string

Address you asked for.

users[].fullName

string

Real name, or an empty string.

users[].totalSeconds

number

Time spent across the selected room types.

minSeconds

number

Lowest totalSeconds in the result.

maxSeconds

number

Highest totalSeconds in the result.

Errors

Status

Meaning

400

A field is missing, or a date is not valid ISO 8601.

404

Following emails not found: … — one or more addresses are not in your organisation.

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

success

boolean

Always true on a 200.

users[]

array

All learners, unpaginated.

users[].username

string

Public handle.

users[].email

string

Address on the seat.

users[].avatar

string

URL of the profile image.

users[].dateSignedUp

date

When the account was created.

users[].totalPoint

number

Lifetime points. Note the singular field name.

users[].monthlyPoints

number

Points earned this calendar month.

users[].extUserId

string

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

roomInfo[]

array

All matching rooms, unpaginated.

roomInfo[].code

string

Room's URL slug. Pass this as roomCode elsewhere.

roomInfo[].title

string

Display name.

roomInfo[].description

string

Summary shown on the room page.

roomInfo[].public

boolean

true for a platform-wide room, false for one your admins created.

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.

Did this answer your question?