Custom Courses
A custom course is a learner-facing curated sequence of topics and scenarios drawn from the SecureCodingHub content catalog. Once created, a custom course can be assigned just like a built-in topic or category. Requires custom-courses:read or custom-courses:write depending on the verb.
Endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /custom-courses | custom-courses:read | List all custom courses in the organization. |
GET | /custom-courses/{id} | custom-courses:read | Retrieve a single custom course with its ordered items. |
POST | /custom-courses | custom-courses:write | Create a new custom course. |
PATCH | /custom-courses/{id} | custom-courses:write | Update metadata and/or replace the item list. |
DELETE | /custom-courses/{id} | custom-courses:write | Soft-deactivate a custom course. |
All paths are relative to https://api.limeplate.com/api/public/v1.
The item model
Every custom course contains an ordered list of items. Each item is a reference to a topic or scenario from the catalog — see Content catalog for the identifiers.
| Field | Type | Meaning |
|---|---|---|
itemType | string | Either "topic" (a practice topic like sql-injection) or "scenario" (a learn-mode scenario). |
itemId | string | The catalog id of the topic or scenario. |
orderIndex | integer | Zero-based display order in the learner UI. Lower values appear first. |
On PATCH, omit the items field to leave items untouched, or send a full replacement list to overwrite. There is no partial item update — re-send the whole sequence in the order you want it stored.
List custom courses
Returns one entry per active custom course in the organization, sorted by most recently updated.
GET /api/public/v1/custom-courses
Authorization: Bearer scs_live_…Response
[
{
"id": "8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02",
"name": "Backend Onboarding — Q2",
"description": "Required reading for new backend hires.",
"icon": "shield",
"color": "#3b82f6",
"itemCount": 6,
"usageCount": 12,
"createdByUserId": "1a2b3c4d-5e6f-7081-9203-4d5e6f708192",
"createdByName": "Jane Smith",
"createdAt": "2026-04-10T09:22:14Z",
"updatedAt": "2026-05-18T15:01:47Z"
}
]Example
curl -sS \
-H "Authorization: Bearer $SCH_API_KEY" \
https://api.limeplate.com/api/public/v1/custom-coursesGet a custom course
Returns the course with its ordered items expanded. resolvedTitle is the human-readable title of the underlying topic or scenario at the time of read — convenient for rendering without a second catalog lookup.
GET /api/public/v1/custom-courses/{customCourseId}
Authorization: Bearer scs_live_…Response
{
"id": "8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02",
"name": "Backend Onboarding — Q2",
"description": "Required reading for new backend hires.",
"icon": "shield",
"color": "#3b82f6",
"items": [
{
"id": "c1d2e3f4-5060-7081-9203-4d5e6f708192",
"itemType": "topic",
"itemId": "sql-injection",
"resolvedTitle": "SQL Injection",
"orderIndex": 0
},
{
"id": "d2e3f405-6070-8192-0304-5e6f70819203",
"itemType": "scenario",
"itemId": "auth-bypass-walkthrough",
"resolvedTitle": "Auth Bypass Walkthrough",
"orderIndex": 1
},
{
"id": "e3f40506-7081-9203-0405-6f7081920304",
"itemType": "topic",
"itemId": "xss",
"resolvedTitle": "Cross-Site Scripting",
"orderIndex": 2
}
],
"usageCount": 12,
"createdByUserId": "1a2b3c4d-5e6f-7081-9203-4d5e6f708192",
"createdByName": "Jane Smith",
"createdAt": "2026-04-10T09:22:14Z",
"updatedAt": "2026-05-18T15:01:47Z"
}Example
curl -sS \
-H "Authorization: Bearer $SCH_API_KEY" \
https://api.limeplate.com/api/public/v1/custom-courses/8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02Create a custom course
Creates a course and returns the detail view. The API key's organization owns the new course; the user who issued the key is recorded as the creator.
POST /api/public/v1/custom-courses
Authorization: Bearer scs_live_…
Content-Type: application/jsonRequest body
{
"name": "Backend Onboarding — Q2",
"description": "Required reading for new backend hires.",
"icon": "shield",
"color": "#3b82f6",
"items": [
{ "itemType": "topic", "itemId": "sql-injection", "orderIndex": 0 },
{ "itemType": "scenario", "itemId": "auth-bypass-walkthrough", "orderIndex": 1 },
{ "itemType": "topic", "itemId": "xss", "orderIndex": 2 }
]
}name is required and must be unique within the organization. description, icon, and color are optional — color takes any CSS hex string. items may be empty at creation; you can populate it later via PATCH.
Response
Same shape as Get a custom course.
Example
curl -sS -X POST \
-H "Authorization: Bearer $SCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Backend Onboarding — Q2",
"description": "Required reading for new backend hires.",
"icon": "shield",
"color": "#3b82f6",
"items": [
{ "itemType": "topic", "itemId": "sql-injection", "orderIndex": 0 },
{ "itemType": "topic", "itemId": "xss", "orderIndex": 1 }
]
}' \
https://api.limeplate.com/api/public/v1/custom-coursesUpdate a custom course
All fields on the request are optional. Pass only the fields you want to change. Sending items replaces the entire item list — to reorder, re-send every item with new orderIndex values.
PATCH /api/public/v1/custom-courses/{customCourseId}
Authorization: Bearer scs_live_…
Content-Type: application/jsonRequest body
{
"name": "Backend Onboarding — Q3",
"items": [
{ "itemType": "topic", "itemId": "sql-injection", "orderIndex": 0 },
{ "itemType": "topic", "itemId": "ssrf", "orderIndex": 1 },
{ "itemType": "scenario", "itemId": "jwt-tampering", "orderIndex": 2 }
]
}Response
The updated detail object.
Example
curl -sS -X PATCH \
-H "Authorization: Bearer $SCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Backend Onboarding — Q3" }' \
https://api.limeplate.com/api/public/v1/custom-courses/8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02Delete a custom course
Soft-deactivates the course. It stops appearing in GET /custom-courses and learner UIs. Existing assignments that referenced the course continue to track historical progress but are not delivered to new assignees.
DELETE /api/public/v1/custom-courses/{customCourseId}
Authorization: Bearer scs_live_…Response
{ "message": "Custom course deactivated" }Example
curl -sS -X DELETE \
-H "Authorization: Bearer $SCH_API_KEY" \
https://api.limeplate.com/api/public/v1/custom-courses/8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02Assigning a custom course
A custom course is not delivered to learners until it is assigned. Use Assignments with targetType: "custom-course" and targetId set to the custom course id:
curl -sS -X POST \
-H "Authorization: Bearer $SCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"assigneeType": "user",
"assigneeId": "1a2b3c4d-5e6f-7081-9203-4d5e6f708192",
"contentArea": "practice",
"targetType": "custom-course",
"targetId": "8b1f0c2e-3a45-4d99-9a7e-2c5e1b3a8f02",
"deadline": "2026-06-15T00:00:00Z",
"isMandatory": true
}' \
https://api.limeplate.com/api/public/v1/assignmentsThe learner sees the items in orderIndex order. Practice topics and learn scenarios are tracked under their respective progress endpoints — see Progress.