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.

429

Rate-limit budget exceeded. Wait before retrying; see Rate limits.

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.

Rate limits

Rate-limit budgets are keyed by a one-way digest of the company API key. Requests using the same key share a budget across source IPs; different API keys have independent budgets. Each endpoint has an independent budget unless explicitly documented otherwise. Categories below group endpoints for readability; they do not share a category-wide budget.

Paths below use /api/v2/external unless they start with /external/api.

Category

Endpoints

Initial limit

Cheap reads

GET /users, GET /users/:userId, GET /assignments

1,000/hour

Per-learner activity

GET /users/:userId/rooms

1,000/hour

Assignment computation

GET /assignments/:assignmentId/scoreboard

1,000/hour

Large room scoreboard

GET /scoreboard

300/hour

Expensive report

POST /reports/time

100/hour

Seat writes

PUT /seats/users, DELETE /seats/users

100/hour

Legacy bulk reads

GET /external/api/users, GET /external/api/rooms

100/hour

Completed rooms

GET /users/:username/completed-rooms

1,000/hour

Rate-limit headers

Header

Meaning

RateLimit-Limit

Maximum requests allowed in the current endpoint's window.

RateLimit-Remaining

Requests remaining in that window.

RateLimit-Reset

Seconds until the window resets.

RateLimit-Policy

Request budget and window duration; for example, 1000;w=3600.

Retry-After

Seconds to wait before retrying after a 429 response.

When you receive 429, wait for the duration in Retry-After, or the reset duration in RateLimit-Reset, before retrying.

Response 429

{
"message": "Too many requests"
}

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 | null

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 | null

Lifetime points earned.

users[].monthlyPoints

number | null

Points earned this calendar month.

users[].lastAction

date | null

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 }
}
}

When individualDataVisible is false, protected activity fields remain present with a null value. Treat null as hidden or unavailable, not as zero activity. Identity and structural fields remain populated. memberNamesVisible indicates whether fullName is available. Group-level GDPR does not hide data from API-key requests because the API key represents company-admin access.

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 | null

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 | null

Lifetime points earned.

lastAction

date | null

Most recent activity on the platform.

privacy

object

Visibility metadata for this response.

privacy.individualDataVisible

boolean

Whether individual achievement, points, and activity values are available.

privacy.memberNamesVisible

boolean

Whether fullName is available.

Under company-level GDPR, protected fullName, totalPoints, and lastAction values remain present as null; badges remains present as [] when achievements are hidden. Identity and structural fields remain populated. Use privacy.individualDataVisible to distinguish hidden achievements from a learner who has earned no badges. Group-level GDPR does not hide data from API-key requests because the API key represents company-admin access.

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 | null

Rooms entered, lifetime.

roomsCompleted

number | null

Rooms finished, lifetime.

questionsAnswered

number | null

Answers submitted, lifetime.

questionsAnsweredCorrectly

number | null

Correct answers, lifetime.

vmsDeployed

number | null

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 | null

Rooms across all pages.

page

number

Page you received.

totalPages

number | null

Pages available.

privacy

object

Visibility metadata for this response.

privacy.individualDataVisible

boolean

Whether the learner's room activity is available.

When privacy.individualDataVisible is false, identity fields remain available, activity totals and pagination totals are null, and recentActions and roomProgress are empty arrays. These values mean the data is hidden; they do not mean the learner has no activity. Group-level GDPR does not hide data from API-key requests because the API key represents company-admin access.

Example

curl -H "THM-API-KEY: $KEY" \
"https://tryhackme.com/api/v2/external/users/6501f2a4c9e1b83d7a4f1120/rooms?page=1"

Company-level GDPR response

{
"status": "success",
"data": {
"userId": "6501f2a4c9e1b83d7a4f1120",
"username": "ada.byron",
"avatar": "https://example.com/avatar.png",
"userDateSignUp": "2025-11-04T09:12:33.000Z",
"roomsJoined": null,
"roomsCompleted": null,
"questionsAnswered": null,
"questionsAnsweredCorrectly": null,
"vmsDeployed": null,
"recentActions": [],
"roomProgress": [],
"totalRoomProgress": null,
"page": 1,
"totalPages": null,
"privacy": {
"individualDataVisible": false
}
}
}

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 | null

Completed public rooms in total.

limit

number

Page size applied.

page

number

Page you received.

totalPages

number | null

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 | null

Index of the first row on this page.

privacy

object

Visibility metadata for this response.

privacy.individualDataVisible

boolean

Whether the learner's completed-room history is available.

When privacy.individualDataVisible is false, docs is empty because completed-room history is hidden. It does not necessarily mean that the learner has completed no rooms. Company-level GDPR keeps the response shape stable. Group-level GDPR does not hide data from API-key requests because the API key represents company-admin access.

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"

Company-level GDPR response

{
"status": "success",
"data": {
"docs": [],
"totalDocs": null,
"limit": 16,
"page": 1,
"totalPages": null,
"nextPage": null,
"prevPage": null,
"pagingCounter": null,
"hasPrevPage": false,
"hasNextPage": false,
"privacy": {
"individualDataVisible": false
}
}
}

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.

GDPR Compliance Mode does not change this response because it contains assignment metadata and aggregate assignee counts only. It does not include individual learner activity, progress, scores, or performance. Individual learner results are protected separately by the assignment scoreboard endpoint.

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.

privacy.individualDataVisible controls per-learner result fields. Assignment metadata, learner identity, hasSeat, pagination, and totalQuestions remain available. Protected result fields remain present with null values under company-level GDPR. Group-level GDPR does not hide data from API-key requests because the API key represents company-admin access.

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 | null

Points earned on this assignment.

timeSpentSeconds

number | null

Time spent, in seconds.

completedAt

date | null

Completion time, or null.

assignmentStatus

enum | null

Submission state, for example not-submitted.

Row fields — by assignment type

Type

Additional fields

path, module, room, skill-review

completedQuestions, totalQuestions, totalAttempts, completionPercentage

soc-sim

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

threat-hunting

completionPercentage, runId, runStatus, runCompletion

Nullable result fields by assignment type

Assignment type

Fields that remain present as null when individual data is hidden

room, module, path

completedQuestions, totalAttempts, completionPercentage, pointsAwarded, timeSpentSeconds, completedAt, assignmentStatus

soc-sim

mttr, pointsAwarded, timeSpentSeconds, runId, runStatus, runCompletion, completedAt, assignmentStatus

threat-hunting

completionPercentage, pointsAwarded, timeSpentSeconds, runId, runStatus, runCompletion, completedAt, assignmentStatus

Completion percentage — room, module, and path rows

completionPercentage is a number | null on a scale of 0 to 100. It is calculated as (completedQuestions / totalQuestions) * 100, using the same deadline-aware completedQuestions value returned in the row. When totalQuestions is zero, the result is 0.

The API follows the management dashboard's whole-number convention: round to the nearest integer, cap an incomplete learner at 99, and return 100 only for genuine completion. When privacy.individualDataVisible is false, the field remains present as null.

completedQuestions, totalQuestions, and totalAttempts remain in the response for backwards compatibility. completedQuestions and totalAttempts are nullable under GDPR; totalQuestions remains available.

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,
"completionPercentage": 86,
"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.

When company-level GDPR Compliance Mode hides individual data, this endpoint returns an empty scoreboard because leaderboard membership, ranking, scores, task progress, and scoring times are individual performance data. An empty array in this state does not mean that no learners participated. Group-level GDPR does not restrict company API-key access.

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"

Company-level GDPR response

{
"status": "success",
"data": []
}

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.

The report includes only the requested learners, room types, and date range. Every email must belong to the API-key company. minSeconds and maxSeconds describe the returned learner rows, not the whole company.

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 | null

Lowest totalSeconds in the result.

maxSeconds

number | null

Highest totalSeconds in the result.

privacy

object

Visibility metadata for this response.

privacy.individualDataVisible

boolean

Whether individual time-report data is available.

When privacy.individualDataVisible is false, users is empty and minSeconds and maxSeconds are null. This means the report is hidden, not necessarily that none of the requested learners recorded time. Group-level GDPR does not hide data from API-key requests because the API key represents company-admin access.

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": 18300,
"maxSeconds": 18300,
"users": [
{
"_id": "6501f2a4c9e1b83d7a4f1120",
"username": "ada.byron",
"email": "[email protected]",
"fullName": "Ada Byron",
"totalSeconds": 18300
}
],
"privacy": {
"individualDataVisible": true
}
}
}

When privacy.individualDataVisible is true, 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.

Company-level GDPR response

{
"status": "success",
"data": {
"minSeconds": null,
"maxSeconds": null,
"users": [],
"privacy": {
"individualDataVisible": false
}
}
}

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.

Request

No query parameters. This legacy endpoint returns all learners in the company's seat and API-user lists and is not paginated.

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 | null

Lifetime points. Note the singular field name.

users[].monthlyPoints

number | null

Points earned this calendar month.

users[].extUserId

string

Your own external id for the learner, where one was registered.

privacy

object

Visibility metadata for this response.

privacy.individualDataVisible

boolean

Whether individual points are available.

Under company-level GDPR, learner identity remains visible while totalPoint and monthlyPoints remain present as null. Group-level GDPR does not hide data from API-key requests because the API key represents company-admin access.

Example

curl -H "THM-API-KEY: $KEY" https://tryhackme.com/external/api/users
{
"success": true,
"users": [
{
"username": "ada.byron",
"email": "[email protected]",
"avatar": "https://example.com/avatar.png",
"dateSignedUp": "2025-11-04T09:12:33.000Z",
"totalPoint": 18420,
"monthlyPoints": 940,
"extUserId": "EMP-4471"
}
],
"privacy": {
"individualDataVisible": true
}
}

Company-level GDPR response

{
"success": true,
"users": [
{
"username": "ada.byron",
"email": "[email protected]",
"avatar": "https://example.com/avatar.png",
"dateSignedUp": "2025-11-04T09:12:33.000Z",
"totalPoint": null,
"monthlyPoints": null,
"extUserId": "EMP-4471"
}
],
"privacy": {
"individualDataVisible": false
}
}

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?