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
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /users/{userId}/practice-progress | progress:read | Completed practice challenges with scores and timestamps. |
GET | /users/{userId}/learn-progress | progress:read | Learn-mode scenarios with status. |
GET | /users/{userId}/assignments | progress:read | Active 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.phase1Scoreandphase2Scoreadd up toscore; 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-progressLearn 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 ofstartedorcompleted.completedAt—nulluntil 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-progressAssignments
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
}
]contentArea—practiceorlearn.targetType— for practice:category,module,topic, orcustom-course. For learn:course,scenario, orcustom-course.isOverdue—trueif 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/assignmentsPolling 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.