Progress

Read a user's training progress: practice (per-challenge), learn (per-scenario), and active assignments. All endpoints are read-only and require the progress:read scope. Granularity is intentionally fine — most LMS-sync and BI pipelines just need to poll these three endpoints per user.

Endpoints

MethodPathScopePurpose
GET/users/{userId}/practice-progressprogress:readCompleted practice challenges with scores and timestamps.
GET/users/{userId}/learn-progressprogress:readLearn-mode scenarios with status.
GET/users/{userId}/assignmentsprogress:readActive assignments for the user with progress percent.

Note that these routes sit at the root level of the API — /api/public/v1/users/{userId}/…, not under /progress/. {userId} is the SecureCodingHub user id, returned by the Users API and webhook payloads.

Practice progress

One row per challenge the user has completed. Practice topics consist of multiple challenges (indexed from 0), and each challenge runs through two phases — vulnerability identification and patch selection — scored independently.

GET /api/public/v1/users/{userId}/practice-progress
Authorization: Bearer scs_live_…

Response

[
  {
    "topicId": "sql-injection",
    "challengeIndex": 0,
    "language": "python",
    "score": 100,
    "phase1Score": 50,
    "phase2Score": 50,
    "phase1HintUsed": false,
    "phase2HintUsed": false,
    "completedAt": "2026-05-20T14:32:08Z"
  },
  {
    "topicId": "sql-injection",
    "challengeIndex": 1,
    "language": "python",
    "score": 80,
    "phase1Score": 30,
    "phase2Score": 50,
    "phase1HintUsed": true,
    "phase2HintUsed": false,
    "completedAt": "2026-05-21T09:14:55Z"
  },
  {
    "topicId": "xss",
    "challengeIndex": 0,
    "language": "javascript",
    "score": 100,
    "phase1Score": 50,
    "phase2Score": 50,
    "phase1HintUsed": false,
    "phase2HintUsed": false,
    "completedAt": "2026-05-22T11:48:31Z"
  }
]
  • topicId — catalog id of the practice topic (e.g. sql-injection, xss, ssrf).
  • challengeIndex — zero-based index of the challenge within the topic.
  • language — language the user solved the challenge in.
  • score — total out of 100. phase1Score and phase2Score add up to score; each is out of 50.
  • phase1HintUsed / phase2HintUsed — boolean; the client decides what hint usage costs, the server only records the flag and the resulting score.
  • completedAt — ISO 8601 UTC timestamp of the most recent completion.

Only completed challenges appear in this list. In-flight attempts are not exposed.

Example

curl -sS \
  -H "Authorization: Bearer $SCH_API_KEY" \
  https://api.limeplate.com/api/public/v1/users/1a2b3c4d-5e6f-7081-9203-4d5e6f708192/practice-progress

Learn progress

One row per learn-mode scenario the user has opened. Learn scenarios are guided walkthroughs, tracked by step count and a coarse status field.

GET /api/public/v1/users/{userId}/learn-progress
Authorization: Bearer scs_live_…

Response

[
  {
    "scenarioId": "auth-bypass-walkthrough",
    "currentStep": 8,
    "totalSteps": 8,
    "status": "completed",
    "startedAt": "2026-05-18T13:02:11Z",
    "completedAt": "2026-05-18T13:41:07Z",
    "lastAccessAt": "2026-05-18T13:41:07Z"
  },
  {
    "scenarioId": "jwt-tampering",
    "currentStep": 3,
    "totalSteps": 7,
    "status": "started",
    "startedAt": "2026-05-23T16:20:00Z",
    "completedAt": null,
    "lastAccessAt": "2026-05-24T10:08:42Z"
  }
]
  • scenarioId — catalog id of the scenario.
  • currentStep / totalSteps — progress within the scenario. When equal, the scenario is finished.
  • status — one of started or completed.
  • completedAtnull until the scenario is finished.
  • lastAccessAt — last time the user opened the scenario, regardless of whether they made progress.

Example

curl -sS \
  -H "Authorization: Bearer $SCH_API_KEY" \
  https://api.limeplate.com/api/public/v1/users/1a2b3c4d-5e6f-7081-9203-4d5e6f708192/learn-progress

Assignments

Active assignments for the user, with a precomputed completion percentage so you don't need to join against practice and learn progress yourself. Includes user-level, team-level, and org-level assignments resolved down to this individual.

GET /api/public/v1/users/{userId}/assignments
Authorization: Bearer scs_live_…

Response

[
  {
    "id": "f1a2b3c4-d5e6-4708-8192-03405e6f7081",
    "contentArea": "practice",
    "targetType": "topic",
    "targetId": "sql-injection",
    "targetTitle": "SQL Injection",
    "deadline": "2026-06-15T00:00:00Z",
    "isMandatory": true,
    "isOverdue": false,
    "isCompleted": false,
    "totalItems": 5,
    "completedItems": 2,
    "progressPercent": 40.0,
    "note": "Assigned after CodeQL finding in payments-api."
  },
  {
    "id": "a2b3c4d5-e6f7-4081-9203-405e6f708192",
    "contentArea": "learn",
    "targetType": "scenario",
    "targetId": "auth-bypass-walkthrough",
    "targetTitle": "Auth Bypass Walkthrough",
    "deadline": "2026-05-31T00:00:00Z",
    "isMandatory": false,
    "isOverdue": false,
    "isCompleted": true,
    "totalItems": 1,
    "completedItems": 1,
    "progressPercent": 100.0,
    "note": null
  }
]
  • contentAreapractice or learn.
  • targetType — for practice: category, module, topic, or custom-course. For learn: course, scenario, or custom-course.
  • isOverduetrue if the deadline has passed and the assignment is not yet completed.
  • progressPercent — completion ratio scaled to 0–100.

Example

curl -sS \
  -H "Authorization: Bearer $SCH_API_KEY" \
  https://api.limeplate.com/api/public/v1/users/1a2b3c4d-5e6f-7081-9203-4d5e6f708192/assignments

Polling patterns

For LMS or BI pipelines, a once-per-hour batch poll is normally enough — practice and learn progress only change when a user finishes work. For tighter live dashboards on the assignment side, subscribe to the assignment.completed webhook instead of polling; see Webhooks. There is no practice.completed or learn.completed event in v1 — poll these endpoints for those.