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

MethodPathScopePurpose
GET/custom-coursescustom-courses:readList all custom courses in the organization.
GET/custom-courses/{id}custom-courses:readRetrieve a single custom course with its ordered items.
POST/custom-coursescustom-courses:writeCreate a new custom course.
PATCH/custom-courses/{id}custom-courses:writeUpdate metadata and/or replace the item list.
DELETE/custom-courses/{id}custom-courses:writeSoft-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.

FieldTypeMeaning
itemTypestringEither "topic" (a practice topic like sql-injection) or "scenario" (a learn-mode scenario).
itemIdstringThe catalog id of the topic or scenario.
orderIndexintegerZero-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-courses

Get 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-2c5e1b3a8f02

Create 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/json

Request 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-courses

Update 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/json

Request 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-2c5e1b3a8f02

Delete 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-2c5e1b3a8f02

Assigning 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/assignments

The learner sees the items in orderIndex order. Practice topics and learn scenarios are tracked under their respective progress endpoints — see Progress.