Skip to content

cURL Examples

Command-line examples for the FSA LMS Partner API.

Setup

Set environment variables for convenience:

export CLIENT_ID="your_client_id"
export CLIENT_SECRET="your_client_secret"
export API_KEY="your_api_key"
export BASE_URL="https://sandbox.lms.api.fsri.org/api/partner/v1"
export TOKEN_URL="https://fsri-partner-api-sandbox-357795418386.auth.us-west-2.amazoncognito.com/oauth2/token"

Authentication

Get Access Token

# Encode credentials
CREDENTIALS=$(echo -n "$CLIENT_ID:$CLIENT_SECRET" | base64)

# Request token
curl -X POST "$TOKEN_URL" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "Authorization: Basic $CREDENTIALS" \
  -d "grant_type=client_credentials&scope=partner-api/read partner-api/write"

Save the token:

export TOKEN="eyJraWQiOiJ..."

Health Check

curl -X GET "$BASE_URL/ping" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY"

User Management

Register User

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

Save the UUID:

export USER_UUID="550e8400-e29b-41d4-a716-446655440000"

Get User Profile

curl -X GET "$BASE_URL/users/$USER_UUID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Update User Profile

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

PATCH uses JSON Merge Patch

PATCH /users/{uuid} requires Content-Type: application/merge-patch+json (RFC 7396). Sending "field": null clears that field; omit a field to leave it unchanged.

Soft-Deactivate / Reactivate User (v3.2)

# Soft deactivate (reversible)
curl -X PATCH "$BASE_URL/users/$USER_UUID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H "X-User-ID: $USER_UUID" \
  -d '{"status": "inactive"}'

# Reactivate
curl -X PATCH "$BASE_URL/users/$USER_UUID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/merge-patch+json" \
  -H "X-User-ID: $USER_UUID" \
  -d '{"status": "active"}'

Hard Delete User (v3.2 — GDPR erasure)

curl -X DELETE "$BASE_URL/users/$USER_UUID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"
# → 204 No Content

Irreversible — prefer soft-deactivate

Only use DELETE for documented GDPR / right-to-be-forgotten requests. Post-DELETE behavior for related transcripts/certificates/progress is not yet finalized — see User Offboarding.

Organizations

List Organizations

curl -X GET "$BASE_URL/organizations" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Course Catalog

List Categories

curl -X GET "$BASE_URL/categories" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

List Courses

# All courses
curl -X GET "$BASE_URL/courses" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

# Filter by category
curl -X GET "$BASE_URL/courses?category=1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

# With pagination
curl -X GET "$BASE_URL/courses?page=1&itemsPerPage=10" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Get Course Details

curl -X GET "$BASE_URL/courses/1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Modules

List Modules

# All modules
curl -X GET "$BASE_URL/modules" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

# Filter by course
curl -X GET "$BASE_URL/modules?course=1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Get Module Details

curl -X GET "$BASE_URL/modules/101" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Access Module Content

curl -X GET "$BASE_URL/modules/101/content" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Refresh Content URL (v3.3)

Signed content_url values expire 60 minutes after issuance. Re-sign an expiring URL without resetting progress:

curl -X GET "$BASE_URL/modules/101/refresh-url" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Mark Module Complete (v3.4)

Signal learner completion for video, document, and resource modules — no request body; FSRI emits the xAPI completed statement server-side:

curl -X POST "$BASE_URL/modules/101/complete" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"
# → 202 Accepted (idempotent; 409 Conflict for SCORM modules)

Progress

Get Course Progress

# All progress
curl -X GET "$BASE_URL/course-progresses" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

# Filter by status
curl -X GET "$BASE_URL/course-progresses?status=in_progress" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

# Filter by course
curl -X GET "$BASE_URL/course-progresses?course=1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Transcripts

List Transcripts

curl -X GET "$BASE_URL/transcripts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

Download Full Transcript PDF

curl -X GET "$BASE_URL/transcript/pdf" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID" \
  -o transcript.pdf

Download Certificate PDF

curl -X GET "$BASE_URL/transcripts/1/certificate/pdf" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID" \
  -o certificate.pdf

Delta Sync (v3.1)

Filter collections by record modification date to fetch only what's changed since your last sync. Supported on /categories, /courses, /modules, /course-progresses, and /transcripts.

# Courses updated on or after April 10, 2026
curl -X GET "$BASE_URL/courses?updatedAt%5Bafter%5D=2026-04-10T00:00:00Z" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

# Course progress changed in the last hour (exclusive lower bound)
CURSOR=$(date -u -v-1H +"%Y-%m-%dT%H:%M:%SZ")
curl -X GET "$BASE_URL/course-progresses?updatedAt%5Bstrictly_after%5D=$CURSOR" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

# Combine with pagination
curl -X GET "$BASE_URL/transcripts?updatedAt%5Bafter%5D=2026-04-10T00:00:00Z&page=2" \
  -H "Authorization: Bearer $TOKEN" \
  -H "x-api-key: $API_KEY" \
  -H "X-User-ID: $USER_UUID"

URL-encoding brackets

Square brackets in updatedAt[after] must be URL-encoded as %5B and %5D when used directly on the command line.

Helper Script

Create a helper script for common operations:

#!/bin/bash
# lms-api.sh - FSA LMS API helper

# Configuration
CLIENT_ID="${CLIENT_ID:-your_client_id}"
CLIENT_SECRET="${CLIENT_SECRET:-your_client_secret}"
API_KEY="${API_KEY:-your_api_key}"
BASE_URL="${BASE_URL:-https://sandbox.lms.api.fsri.org/api/partner/v1}"
TOKEN_URL="${TOKEN_URL:-https://fsri-partner-api-sandbox-357795418386.auth.us-west-2.amazoncognito.com/oauth2/token}"

# Get token
get_token() {
    local credentials=$(echo -n "$CLIENT_ID:$CLIENT_SECRET" | base64)
    curl -s -X POST "$TOKEN_URL" \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -H "Authorization: Basic $credentials" \
        -d "grant_type=client_credentials&scope=partner-api/read partner-api/write" \
        | jq -r '.access_token'
}

# API request helper
api_request() {
    local method=$1
    local endpoint=$2
    local data=$3
    local token=$(get_token)

    if [ -n "$data" ]; then
        curl -s -X "$method" "$BASE_URL$endpoint" \
            -H "Authorization: Bearer $token" \
            -H "x-api-key: $API_KEY" \
            -H "Content-Type: application/json" \
            -H "X-User-ID: $USER_UUID" \
            -d "$data" | jq
    else
        curl -s -X "$method" "$BASE_URL$endpoint" \
            -H "Authorization: Bearer $token" \
            -H "x-api-key: $API_KEY" \
            -H "X-User-ID: $USER_UUID" | jq
    fi
}

# Usage
case "$1" in
    ping)
        api_request GET "/ping"
        ;;
    courses)
        api_request GET "/courses"
        ;;
    register)
        api_request POST "/users/register" "$2"
        ;;
    *)
        echo "Usage: $0 {ping|courses|register}"
        ;;
esac

Usage:

chmod +x lms-api.sh

# Test connectivity
./lms-api.sh ping

# List courses
export USER_UUID="550e8400-e29b-41d4-a716-446655440000"
./lms-api.sh courses

# Register user
./lms-api.sh register '{"email":"test@example.com","firstName":"Test","lastName":"User"}'