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:
Health Check¶
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:
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: