English
English
Appearance
English
English
Appearance
The Training Metadata API lets you pull SkillsTown catalog metadata directly into your own LMS, replacing the CSV files that were previously exchanged by hand. For you as an integrator it is a read-only, machine-to-machine API: two GET endpoints, no writes, no user session.
This page covers two things, in order:
To make this work, you need your company's API key. You can request it via [email protected] or through your technical contact person.
Once you have a key, every request must carry it in the X-Api-Key header:
X-Api-Key: <your-api-key>The key identifies the calling company — there is no companyId in the key itself and no user identity in the request. As a customer integration, you never need to send a companyId query parameter; if you send one anyway it is silently ignored. (A companyId parameter is only relevant to SkillsTown's internal super-user callers, who have no company bound to their token and must supply it explicitly.)
A key that is missing, empty, or unmatched fails authorization before the request reaches the endpoint.
Routes are versioned via the URL path. The current API version is 1.0, which is addressed in the path as v1 — so every URL on this page takes this form:
/admin/v1/BundleCatalogGET /admin/v1/BundleCatalog Lists every bundle visible to your company, including SkillsTown's global bundles. Results are ordered by bundleId descending, so the newest bundles come first.
Response — 200 OK
[
{ "bundleId": 412, "bundleName": "Leadership Essentials" },
{ "bundleId": 118, "bundleName": "Compliance 2026" }
]bundleName is an empty string when not set, never null. A company with no visible bundles gets 200 with an empty array [], not a 404.
Example
curl -sS \
-H "X-Api-Key: $SKILLSTOWN_API_KEY" \
https://<host>/admin/v1/BundleCatalogGET /admin/v1/BundleCatalog/{bundleId}/CourseMetadata Returns metadata for every direct course in the given bundle.
Path parameters
| Name | Type | Notes |
|---|---|---|
bundleId | int | A non-numeric value returns 404, not 400 |
A bundle that exists but belongs to another company, and a bundle id that does not exist at all, both return an identical 404.
Response — 200 OK
[
{
"courseId": 1042,
"bundleSubscriptionTypeId": 100,
"bundleSubscriptionName": "Premium",
"title": "Leidinggeven aan verandering",
"targetAudience": "Team leads",
"learningObjective": "Recognise resistance patterns and respond to them",
"courseDescription": "<p>Full rich-text description.</p>",
"courseIntroduction": "<p>Short intro paragraph.</p>",
"learningTime": 45,
"courseType": "webinar",
"segment": "Leiderschap",
"courseLanguage": "Nederlands",
"courseTags": ["Leadership", "Change"],
"ltiIdentifier": "st-course-1042",
"publishDate": "2026-01-15T00:00:00",
"courseImage": "https://cdn.example.com/course-1042.png"
}
]Example
curl -sS \
-H "X-Api-Key: $SKILLSTOWN_API_KEY" \
https://<host>/admin/v1/BundleCatalog/412/CourseMetadataAll 16 fields are always present in the JSON. Nullable fields are emitted as null rather than omitted.
| Field | Type | Notes |
|---|---|---|
courseId | int | SkillsTown course id — stable, use it as your correlation key |
bundleSubscriptionTypeId | int | Subscription type the course is attached to within the bundle |
bundleSubscriptionName | string | Empty string when unset, never null |
title | string | From the latest course version; empty string when unset |
targetAudience | string | null | Course-level, not version-level |
learningObjective | string | null | The course version's study goals |
courseDescription | string | null | Contains HTML |
courseIntroduction | string | null | Contains HTML |
learningTime | int | null | Stored as a plain integer; confirm the intended unit with SkillsTown before displaying it |
courseType | string | null | Journey category system name, e.g. webinar |
segment | string | null | Localized segment name — see the resolution rules below |
courseLanguage | string | null | Display name of the course culture, e.g. Nederlands |
courseTags | string[] | Empty array when the course has no tags; blank tag values are filtered out |
ltiIdentifier | string | null | The identifier to match against your LMS |
publishDate | date-time | null | |
courseImage | string | null | Absolute image URL |
HTML content
courseDescription and courseIntroduction contain HTML. Sanitize or render them accordingly; do not assume plain text.
Version-derived fields (title, learningObjective, courseDescription, courseIntroduction, learningTime, courseType, segment, and publishDate) are all read from the latest course version. Older versions are not exposed.
How segment is resolved. segment is a human-readable name, not the numeric segment id, resolved in this order:
nl-NL translation.null.No other language is ever substituted, so the value is stable across requests.
| Situation | Status | Body |
|---|---|---|
| Bundle has courses | 200 | Array of course metadata |
| Bundle is accessible but has no direct courses | 200 | [] |
Non-numeric bundleId | 404 | Indistinguishable from an unknown bundle |
| Bundle does not exist or belongs to another company | 404 | Identical in both cases |
Missing, empty, or invalid X-Api-Key | 401 / 403 | Authorization failure |
| Unexpected server-side failure | 5xx | An error occurred while processing the request |
Treat 200 with [] as "this bundle currently has no courses" — a valid steady state, not an error, and not a reason to retry.
GET /admin/v1/BundleCatalog to discover the bundles you can see.GET /admin/v1/BundleCatalog/{bundleId}/CourseMetadata.ltiIdentifier, keying your own records on courseId.200 with [] as a valid, non-error state.404 as "this bundle is not yours or no longer exists" and stop requesting it.There is no pagination on either endpoint: each returns the full collection in one response. Neither endpoint exposes a change timestamp or delta mechanism, so a sync is a full re-read. Poll at a cadence appropriate to how often your catalog actually changes — daily is usually plenty.