Skip to content

API Reference

All paths are relative to the base URL (sandbox: https://sandbox.lms.api.fsri.org/api/partner/v1). Every request needs Authorization: Bearer {token} and x-api-key: {{apiKey}} (Authentication) except where noted. Scope: read = partner-api/read, write = partner-api/write.

Machine-readable spec + try-it-out UI: interactive docs.

Quick Reference

Method Path Scope X-User-ID
GET /ping (API key only)
POST /users/register write
GET /users/{uuid} read
PATCH /users/{uuid} write
DELETE /users/{uuid} write
GET /organizations · /organizations/{id} read
GET /categories · /categories/{id} read
GET /courses · /courses/{id} read
GET /modules · /modules/{id} read
GET /modules/{id}/content read
GET /modules/{id}/refresh-url read
POST /modules/{id}/complete write
GET /course-progresses · /course-progresses/{id} read
GET /transcripts · /transcripts/{id} read
GET /transcript/pdf read
GET /transcripts/{id}/certificate/pdf read

Health

GET /ping

Service health. Requires only the API key (no JWT — by design, so you can verify connectivity in isolation). Returns 200 with "status": "healthy" and dependency checks.


Users

POST /users/register

Register a learner; returns their profile including their UUID — persist it, it's the learner's identity in every subsequent call. Idempotent per partner+email: registering the same email again returns the existing record, so retries after timeouts are safe.

curl -s -X POST "$BASE_URL/users/register" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane.doe@yourcompany.example",
    "firstName": "Jane",
    "lastName": "Doe",
    "organizationId": 123
  }'

Returns 201 with the profile.

Profile fields (response, and PATCH-able): email, firstName, lastName, organizationId, countryCode (ISO 3166-1 alpha-2), address, address2, city, stateProv, postalCode, phone, status (active | inactive) — plus read-only uuid, createdAt, updatedAt.

GET /users/{uuid}

Fetch a learner's profile (fields above).

PATCH /users/{uuid}

Update profile fields. Requires Content-Type: application/merge-patch+json — send only the fields you're changing:

curl -s -X PATCH "$BASE_URL/users/$USER_UUID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -d '{"firstName": "Janet"}'

Deactivate / reactivate — same PATCH with {"status": "inactive"} (or "active"). Deactivation is reversible; while inactive, user-scoped calls with that UUID are rejected. Transcripts, certificates, and progress are preserved.

DELETE /users/{uuid}

Permanent GDPR erasure. Returns 204. The learner's identifying data is removed; their transcripts, certificates, and progress records are retained but anonymized (de-identified, relinked to a placeholder) so already-issued credentials remain verifiable. Not reversible. A UUID you don't own returns 404.


Organizations

GET /organizations · GET /organizations/{id}

Read-only reference data. Use organizations to populate a dropdown at registration; pass the chosen id as organizationId when registering. Managed by FSRI — the API cannot create or modify them.


Catalog: Categories, Courses, Modules

GET /categories · /categories/{id}GET /courses · /courses/{id}GET /modules · /modules/{id}

The content catalog. All three collections support pagination and updatedAt delta-sync filters. Course detail includes its modules.

Only published content is visible through the API.

Module records include passingScore (integer 0–100, or null for non-assessed content) — surface it so learners know the threshold before starting.


Content Delivery

GET /modules/{id}/contentuser-scoped

Returns a signed, time-limited content URL for a learner:

{
  "@type": "ModuleContent",
  "module_id": 467,
  "content_type": "scorm",
  "content_url": "https://…/module_0467/index_lms.html?…",
  "expires_at": "2026-07-26T15:30:00Z",
  "duration_seconds": 600,
  "mime_type": "text/html"
}
  • content_type is one of video, document, scorm, resource.
  • Embed content_url in an <iframe> (SCORM/documents) or player (video). FSRI hosts and renders SCORM — your app never interprets the package.
  • The URL is signed and expires (expires_at, ~60 minutes). Fetch at click-time; never persist a content URL.
  • Side effect: the learner's progress record is auto-created (status in_progress) on first content access — you never create progress records yourself.

Status codes: 200 published + user is yours · 400 missing X-User-ID · 404 module not reachable through a published course, or the UUID isn't yours.

GET /modules/{id}/refresh-urluser-scoped

Fresh signed URL for content already playing — same response shape as /content. For long-form video, call this when expires_at − now < 60s so playback never breaks mid-session.

POST /modules/{id}/completeuser-scoped

Completion notification for video, document, and resource modules — your UI's "Mark as Complete" control calls this. No request body.

curl -s -X POST "$BASE_URL/modules/$MODULE_ID/complete" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"
Status Meaning
202 Accepted Completion recorded (FSRI emits the learning-record statement server-side)
202 (repeat call) Idempotent no-op — already complete, safe to retry
409 Conflict Module is SCORM — SCORM reports its own completion; don't call this for SCORM
400 / 403 / 404 Missing/invalid X-User-ID, inactive user, or module/user not found

Course Progress

GET /course-progresses · GET /course-progresses/{id}user-scoped

The learner's progress records. Read-only — records are created on first content access and updated by FSRI as the learner works (SCORM statements or your /complete calls).

Fields include status (not_started | in_progress | completed), completionPercent, lastActivityAt, startedAt, completedAt, createdAt, updatedAt. Course-level completion is derived from the learner's transcript — a course shows completed once FSRI's system of record says so.

Supports updatedAt delta-sync filters — poll on a schedule with those rather than refetching everything.


Transcripts & Certificates

GET /transcripts · GET /transcripts/{id}user-scoped

The learner's completion records: course/module reference and name, completionDate, score (0–100 or null), isCourse, certificateAvailable. Supports updatedAt filters.

When certificateAvailable is true, the certificate PDF endpoint will serve it.

GET /transcript/pdfuser-scoped

The learner's full transcript (all completions) as application/pdf. Generated on demand, typically 1–3 seconds — call when the user asks for the document; don't pre-fetch in bulk.

GET /transcripts/{id}/certificate/pdfuser-scoped

Single course certificate as application/pdf. The transcript must belong to the X-User-ID learner.