SCORM API Reference
Complete reference documentation for all SCORM API endpoints, request/response schemas, authentication requirements, and error handling.
Table of Contents
- Overview
- Authentication
- Base URLs
- API Versioning
- Rate Limiting
- Error Handling
- API Endpoints
- Request/Response Examples
Overview
The SCORM API is a RESTful API that provides endpoints for managing SCORM packages, learning sessions, analytics, and integrations. All endpoints return JSON responses and use standard HTTP status codes.
API Features
- SCORM 1.2 & 2004 Support: Full support for both SCORM versions
- Multi-tenant Architecture: Complete data isolation per tenant
- Optimistic Locking: Version-based concurrency control
- Rate Limiting: Per-tenant rate limits to ensure fair usage
- Webhooks: Real-time event notifications
- xAPI Integration: Automatic SCORM to xAPI conversion
Authentication
The SCORM API supports two authentication methods:
API Key Authentication
For programmatic access (server-to-server), use API key authentication:
X-API-Key: your-api-key-here
OR
Authorization: Bearer your-api-key-here
API Key Scopes:
| Scope | Permissions |
|---|---|
read |
GET requests only (retrieve packages, sessions) |
write |
POST, PUT requests (upload packages, update sessions) |
admin |
All operations including deletions |
Clerk Authentication
For web application users (browser-based), authentication is handled via Clerk session cookies. Customer routes (/api/customer/*) automatically filter data by the authenticated user's tenant.
See: API Key Security Guide for detailed authentication documentation.
Base URLs
Production: https://app.allureconnect.com
Development: http://localhost:3001
API Versioning
The API uses URL-based versioning:
- v1:
/api/v1/*- Current stable version - Customer Routes:
/api/customer/*- Web application routes (Clerk auth) - Admin Routes:
/api/admin/*- System administration routes
Rate Limiting
Rate limits are applied per tenant:
| User Type | Requests per Hour | Requests per Minute |
|---|---|---|
| API Key (read scope) | 1,000 | 20 |
| API Key (write scope) | 500 | 10 |
| API Key (admin scope) | 5,000 | 100 |
| Clerk (web users) | 2,000 | 40 |
Rate Limit Headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1697544600
See: Rate Limiting Guide for detailed information.
Error Handling
All errors follow this standard format:
{
"error": "Human-readable error message",
"code": "ERROR_CODE",
"details": {
"field": "Additional context"
}
}
HTTP Status Codes:
| Code | Meaning | Common Error Codes |
|---|---|---|
200 |
Success | - |
201 |
Created | - |
400 |
Bad Request | INVALID_REQUEST, MISSING_FILE, INVALID_FILE_TYPE |
401 |
Unauthorized | UNAUTHORIZED, INVALID_API_KEY |
403 |
Forbidden | FORBIDDEN, INSUFFICIENT_SCOPES |
404 |
Not Found | PACKAGE_NOT_FOUND, SESSION_NOT_FOUND |
409 |
Conflict | VERSION_CONFLICT |
413 |
Payload Too Large | FILE_TOO_LARGE |
429 |
Too Many Requests | RATE_LIMIT_EXCEEDED |
500 |
Internal Server Error | INTERNAL_ERROR |
See: Error Codes Reference for complete error code documentation.
API Endpoints
Health Check
GET /api/health
Check if the API is running and healthy.
Authentication: None required
Response (200):
{
"status": "ok",
"timestamp": "2025-01-15T10:30:00.000Z",
"version": "1.0.0",
"database": "connected",
"storage": "available"
}
Packages
POST /api/v1/packages
Not implemented in the current Connect route handler (GET lists packages only). Do not use this path for uploads.
Use instead:
POST /api/v1/packages/upload-url(or aliasPOST /api/v1/packages/upload-sessions) to mint a presigned URLPUTthe ZIP topresigned_urlwithrequired_put_headersPOST /api/v1/packages/processto validate and publish
Partner runbook: docs/API/package-upload-integration.md.
For small direct multipart uploads (subject to platform body limits), the app exposes POST /api/v1/packages/upload — see the OpenAPI spec (GET /api/docs/openapi).
GET /api/v1/packages
List all packages for a tenant.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUIDlimit(optional): Number of results (default: 50, max: 100)offset(optional): Pagination offset (default: 0)
Response (200):
{
"packages": [
{
"id": "pkg_abc123",
"title": "Introduction to Safety Training",
"version": "1.2",
"created_at": "2025-01-15T10:30:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
GET /api/v1/packages/{packageId}
Get detailed information about a specific package.
Authentication: API Key with read scope
Response (200):
{
"id": "pkg_abc123",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Introduction to Safety Training",
"version": "1.2",
"scorm_version": "1.2",
"launch_url": "index.html",
"manifest_url": "imsmanifest.xml",
"storage_path": "tenant-550e8400/packages/pkg_abc123",
"file_size_bytes": 5242880,
"metadata": {
"identifier": "com.example.course.001",
"schema": "ADL SCORM",
"schemaversion": "1.2",
"description": "Course description",
"sco_count": 5
},
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:30:00.000Z"
}
PATCH /api/v1/packages/{packageId}
Update package metadata.
Authentication: API Key with write scope
Request Body:
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"description": "Updated course description",
"duration": "45m",
"tags": ["onboarding", "2025"],
"custom_metadata": {
"level": "advanced"
}
}
Response (200): Updated package object
DELETE /api/v1/packages/{packageId}
Delete a package.
Authentication: API Key with admin scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (204): No content
POST /api/v1/packages/{packageId}/launch
Create a new session and get the player URL.
Authentication: API Key with write scope
Request Body:
{
"user_id": "660e8400-e29b-41d4-a716-446655440000",
"session_id": "770e8400-e29b-41d4-a716-446655440000"
}
Response (200):
{
"launch_url": "https://app.allureconnect.com/player/770e8400-e29b-41d4-a716-446655440000?token=eyJhbGciOi...",
"session_id": "770e8400-e29b-41d4-a716-446655440000",
"package_id": "pkg_abc123",
"learner_id": "660e8400-e29b-41d4-a716-446655440000",
"content_type": "scorm",
"expires_in_seconds": 600
}
launch_urlalready embeds the signed session token (?token=<jwt>). Treat it as opaque — do not strip query parameters or hand-reconstruct the player URL.
GET /api/v1/packages/{packageId}/versions
Get version history for a package.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (200):
{
"versions": [
{
"revision": 3,
"created_at": "2025-01-15T10:30:00.000Z",
"uploaded_by": "user-123",
"file_size_bytes": 5242880
}
]
}
POST /api/v1/packages/multipart/init
Initialize multipart upload for large packages.
Authentication: API Key with write scope
Request Body:
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"uploaded_by": "user-123",
"filename": "large-course.zip"
}
Response (200):
{
"upload_id": "upload_abc123",
"storage_path": "tenant/uploads/tmp_123.zip",
"part_size": 5242880,
"total_parts": 20
}
POST /api/v1/packages/multipart/part-url
Get presigned URL for uploading a part.
Authentication: API Key with write scope
Request Body:
{
"upload_id": "upload_abc123",
"part_number": 1
}
Response (200):
{
"part_number": 1,
"url": "https://storage.example.com/upload?presigned=...",
"expires_in": 3600
}
POST /api/v1/packages/multipart/complete
Complete multipart upload.
Authentication: API Key with write scope
Request Body:
{
"upload_id": "upload_abc123",
"parts": [
{ "part_number": 1, "etag": "\"etag1\"" },
{ "part_number": 2, "etag": "\"etag2\"" }
]
}
Response (200):
{
"storage_path": "tenant/uploads/tmp_123.zip",
"file_size_bytes": 104857600
}
POST /api/v1/packages/process
Process an uploaded package (from multipart or direct upload).
Authentication: API Key with write scope
Request Body:
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"uploaded_by": "user-123",
"storage_path": "tenant/uploads/tmp_123.zip",
"original_filename": "course.zip",
"validate_only": false
}
Response (200): Processing result (upload_id, manifest, optional package with package_id, etc.) — see OpenAPI ProcessPackageResponse at GET /api/docs/openapi.
POST /api/v1/packages/upload-url
Get a presigned URL for HTTP PUT direct upload to object storage (Cloudflare R2 when configured). Same behavior as POST /api/v1/packages/upload-sessions (alias).
Authentication: API key with write scope — Authorization: Bearer <api_key> or X-API-Key: <api_key>.
Request Body (see lib/contracts/connect-upload.ts uploadUrlRequestSchema):
| Field | Type | Required | Notes |
|---|---|---|---|
tenant_id |
string | Yes | Must match the tenant for the API key; server resolves tenant from the key. |
uploaded_by |
string | Yes | Defaults to the API key id if omitted or empty. |
filename |
string | Yes | Must end in .zip. |
file_size |
number | Yes | Size in bytes; must not exceed the tenant’s plan upload cap. |
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"uploaded_by": "user-123",
"filename": "course.zip",
"file_size": 10485760
}
Response (200): (uploadUrlResponseSchema)
{
"upload_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"storage_path": "tenant/uploads/abc/course.zip",
"presigned_url": "https://<r2-host>/<bucket>/...",
"storage_type": "r2",
"bucket": "allurelms",
"max_upload_mb": 10240,
"presigned_expires_in_seconds": 3600,
"presigned_expires_at": "2026-04-14T12:00:00.000Z",
"upload_method": "PUT",
"required_put_headers": {
"Content-Type": "application/zip"
},
"expires_at": "2026-04-14T12:00:00.000Z"
}
Send PUT to presigned_url with the ZIP body and at least Content-Type: application/zip as required by required_put_headers. Then call POST /api/v1/packages/process with storage_path and related fields.
Partner runbook: docs/API/package-upload-integration.md.
GET /api/v1/packages/upload-limit
Get the configured maximum upload size and storage backend flags.
Authentication: API Key with read scope
Response (200): (uploadLimitResponseSchema)
{
"max_upload_mb": 10240,
"storage_type": "r2",
"r2_enabled": true
}
Sessions
GET /api/v1/sessions
List and filter sessions.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUIDuser_id(optional): Filter by user IDpackage_id(optional): Filter by package IDcompletion_status(optional):not_attempted,incomplete,completedsuccess_status(optional):unknown,passed,faileddate_from(optional): ISO 8601 datetimedate_to(optional): ISO 8601 datetimepage(optional): Page number (default: 1)limit(optional): Results per page (default: 20, max: 100)sort_by(optional):created_at,updated_at,completion_status(default:updated_at)sort_order(optional):asc,desc(default:desc)
Response (200):
{
"sessions": [
{
"id": "session-789",
"package_id": "pkg_abc123",
"tenant_id": "tenant-456",
"user_id": "user-abc",
"completion_status": "completed",
"success_status": "passed",
"score": {
"scaled": 0.95,
"raw": 95,
"min": 0,
"max": 100
},
"attempts": 1,
"time_spent_seconds": 3600,
"session_time": "PT1H",
"created_at": "2025-01-15T00:00:00Z",
"updated_at": "2025-01-15T01:00:00Z",
"package": {
"title": "Course Title",
"version": "1.2"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"total_pages": 8
}
}
GET /api/v1/sessions/{sessionId}
Get session data and CMI information.
Authentication: API Key with read scope OR Launch token
Query Parameters:
token(optional): Launch token for player access
Response (200):
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "660e8400-e29b-41d4-a716-446655440000",
"package_id": "pkg_abc123",
"cmi_data": {
"cmi.core.lesson_status": "incomplete",
"cmi.core.score.raw": "75",
"cmi.core.score.max": "100",
"cmi.core.session_time": "PT15M30S"
},
"completion_status": "incomplete",
"success_status": "unknown",
"score": {
"scaled": 0.75,
"raw": 75,
"max": 100,
"min": 0
},
"time_spent_seconds": 930,
"version": 3,
"created_at": "2025-01-15T10:00:00.000Z",
"updated_at": "2025-01-15T10:15:30.000Z"
}
PUT /api/v1/sessions/{sessionId}
Update session data and CMI information.
Authentication: API Key with write scope
Request Body:
{
"version": 3,
"cmi_data": {
"cmi.core.lesson_status": "completed",
"cmi.core.score.raw": "85",
"cmi.core.score.max": "100",
"cmi.core.session_time": "PT20M45S"
},
"completion_status": "completed",
"success_status": "passed",
"score": {
"scaled": 0.85,
"raw": 85,
"max": 100,
"min": 0
},
"session_time": "PT20M45S"
}
Important: The version field is required for optimistic locking. If you receive a 409 Conflict error, fetch the latest session data and retry with the updated version.
Response (200): Updated session object
Error Responses:
409- Version conflict (fetch latest and retry)
POST /api/v1/sessions/external
Create an external session with signed launch URL (for external systems like TrainingOS).
Authentication: API Key with write scope
Request Body:
{
"token": "<external-jwt-token>",
"package_id": "pkg_abc123",
"launch_token_ttl_seconds": 600
}
Response (200):
{
"session_id": "session-456",
"launch_url": "https://app.allureconnect.com/player/session-456?token=...",
"launch_token": "eyJhbGciOiJIUzI1NiIs..."
}
Dispatches
GET /api/v1/dispatches
List dispatch packages.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUIDlimit(optional): Number of results (default: 50)offset(optional): Pagination offset (default: 0)
Response (200):
{
"dispatches": [
{
"id": "dispatch-123",
"package_id": "pkg_abc123",
"dispatch_name": "Client Distribution",
"created_at": "2025-01-15T10:30:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
POST /api/v1/dispatches
Create a dispatch package.
Authentication: API Key with write scope
Request Body:
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"package_id": "pkg_abc123",
"dispatch_name": "Client Distribution",
"client_name": "Acme Corp",
"registration_limit": 100,
"license_limit": 50,
"track_licenses": true,
"expires_in_hours": 720,
"allowed_domains": ["acme.com", "training.acme.com"]
}
Response (201):
{
"id": "dispatch-123",
"package_id": "pkg_abc123",
"dispatch_name": "Client Distribution",
"launch_url": "https://app.allureconnect.com/dispatch/launch?token=...",
"zip_download_url": "https://app.allureconnect.com/api/v1/dispatches/dispatch-123/zip",
"created_at": "2025-01-15T10:30:00.000Z"
}
GET /api/v1/dispatches/{dispatchId}
Get dispatch package details.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (200): Dispatch object
DELETE /api/v1/dispatches/{dispatchId}
Delete a dispatch package.
Authentication: API Key with admin scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (204): No content
GET /api/v1/dispatches/{dispatchId}/zip
Download dispatch package ZIP file.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (200): ZIP file download
GET /api/v1/dispatches/{dispatchId}/launches
Get launch statistics for a dispatch.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (200):
{
"total_launches": 45,
"unique_users": 30,
"completed": 25,
"in_progress": 10,
"not_started": 10
}
POST /api/v1/dispatches/launch
Launch a dispatch package (for third-party LMSs).
Authentication: Launch token (included in dispatch package)
Query Parameters:
token(required): Dispatch launch token
Response (200):
{
"launch_url": "https://app.allureconnect.com/player/session-456?token=eyJhbGciOi...",
"session_id": "session-456"
}
Launch Link Health Check (self-service)
Integrators who have a launch URL but aren't sure whether it will work can validate it structurally before embedding it. The endpoint is unauthenticated and read-only — it never returns session data, only whether the URL is well-formed and the token (if present) is valid.
GET /api/v1/launch-links/validate?url=…
POST /api/v1/launch-links/validate { "url": "…" }
Response (200): always 200. The status field distinguishes outcomes.
{
"status": "ok",
"message": "Session token is valid and not expired.",
"remediation": "No action needed — this URL should load for learners.",
"detail": {
"host": "app.allureconnect.com",
"pathname": "/player/ses_1",
"tokenPresent": true,
"linkType": "session"
}
}
status values:
| Status | Meaning |
|---|---|
ok |
URL is valid, token is valid, link should load. |
missing_token |
/player/<id> without a ?token=, or a truncated dispatch URL. |
expired_token |
Token parsed but past its TTL — re-mint. |
invalid_token |
Token failed signature verification — regenerate from the correct environment. |
not_a_player_url |
URL doesn't match a player surface path. |
invalid_url |
URL wasn't provided or couldn't be parsed. |
Use this endpoint from your integrator tooling (CLI, CI, integration tests) to catch broken launch URLs before they ship to an LMS.
Webhooks
GET /api/v1/webhooks
List webhooks for a tenant.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (200):
{
"webhooks": [
{
"id": "webhook-123",
"url": "https://hooks.example.com/scorm",
"event_type": "package.processing.completed",
"active": true,
"created_at": "2025-01-15T10:30:00.000Z"
}
]
}
POST /api/v1/webhooks
Create a webhook.
Authentication: API Key with write scope
Request Body:
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://hooks.example.com/scorm",
"secret": "supersecret",
"event_type": "package.processing.completed"
}
Response (201):
{
"id": "webhook-123",
"url": "https://hooks.example.com/scorm",
"event_type": "package.processing.completed",
"active": true,
"created_at": "2025-01-15T10:30:00.000Z"
}
See: Webhook Setup Guide for detailed webhook documentation.
xAPI
POST /api/v1/xapi/statements
Create xAPI statements (Learning Record Store).
Authentication: API Key with write scope
Request Body:
{
"actor": {
"mbox": "mailto:learner@example.com",
"name": "John Doe"
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/completed",
"display": { "en-US": "completed" }
},
"object": {
"id": "https://example.com/activities/course-123",
"definition": {
"name": { "en-US": "Safety Training" }
}
},
"result": {
"score": {
"scaled": 0.85,
"raw": 85,
"max": 100
},
"success": true,
"completion": true
}
}
Response (200):
{
"statement_id": "550e8400-e29b-41d4-a716-446655440000",
"stored": "2025-01-15T10:30:00.000Z"
}
GET /api/v1/xapi/statements
Query xAPI statements.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUIDactor(optional): Filter by actor (JSON)verb(optional): Filter by verb IDactivity(optional): Filter by activity IDsince(optional): ISO 8601 datetimeuntil(optional): ISO 8601 datetimelimit(optional): Results per page (default: 20, max: 100)format(optional):ids,exact,canonical(default:exact)
Response (200):
{
"statements": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"actor": { ... },
"verb": { ... },
"object": { ... },
"timestamp": "2025-01-15T10:30:00.000Z"
}
],
"more": "https://api.example.com/statements?cursor=..."
}
GET /api/v1/xapi/statements/{statementId}
Get a specific xAPI statement.
Authentication: API Key with read scope
Response (200): xAPI statement object
GET /api/v1/xapi/analytics/activities
Get activity analytics.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUIDactivity_id(optional): Filter by activitydate_from(optional): ISO 8601 datetimedate_to(optional): ISO 8601 datetime
Response (200):
{
"activities": [
{
"activity_id": "https://example.com/activities/course-123",
"total_statements": 150,
"unique_actors": 45,
"completions": 30
}
]
}
GET /api/v1/xapi/analytics/actors
Get actor analytics.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUIDactor(optional): Filter by actor (JSON)date_from(optional): ISO 8601 datetimedate_to(optional): ISO 8601 datetime
Response (200):
{
"actors": [
{
"actor": {
"mbox": "mailto:learner@example.com",
"name": "John Doe"
},
"total_statements": 25,
"activities_completed": 5
}
]
}
Quotas
GET /api/v1/quotas
Get quota information for a tenant.
Authentication: API Key with read scope
Query Parameters:
tenant_id(required): Tenant UUID
Response (200):
{
"package_quota": {
"limit": 100,
"used": 45,
"remaining": 55
},
"storage_quota": {
"limit_bytes": 53687091200,
"limit_gb": 50,
"used_bytes": 21474836480,
"used_gb": 20,
"remaining_bytes": 32212254720,
"remaining_gb": 30
}
}
See: Quota Management Guide for detailed information.
Content
GET /api/v1/content/{packageId}/...
Serve SCORM package content files.
Authentication: Launch token or API Key with read scope
Path Parameters:
packageId: Package UUID...: Relative path to content file within package
Response (200): File content with appropriate Content-Type header
Example:
GET /api/v1/content/pkg_abc123/index.html
GET /api/v1/content/pkg_abc123/assets/styles.css
Customer Routes
Customer routes use Clerk authentication and automatically filter data by the authenticated user's tenant.
GET /api/customer/reports/learner-progress
Get learner progress report.
Authentication: Clerk session (browser cookie)
Query Parameters:
range(optional):7d,30d,90d,all(default:7d)
Response (200):
{
"packages": [
{
"package_id": "pkg_abc123",
"package_title": "Introduction to Safety Training",
"total_sessions": 25,
"completed_sessions": 18,
"completion_rate": 72.0,
"avg_time_spent_seconds": 1250,
"avg_score": 0.85,
"passed": 15,
"failed": 3,
"unknown": 7
}
],
"summary": {
"total_packages": 5,
"total_sessions": 125,
"total_completed": 90,
"overall_completion_rate": 72.0,
"avg_time_spent_seconds": 1180
}
}
GET /api/customer/reports/learner-progress/export
Export learner progress as CSV.
Authentication: Clerk session
Query Parameters:
range(optional): Time range
Response (200): CSV file
POST /api/customer/reports/custom
Generate custom report.
Authentication: Clerk session
Request Body:
{
"type": "completion",
"dateFrom": "2025-01-01",
"dateTo": "2025-01-31",
"packageId": "pkg_abc123",
"userId": "user_xyz789",
"chartType": "bar",
"groupBy": "day"
}
Response (200): Custom report data
POST /api/customer/reports/custom/export
Export custom report.
Authentication: Clerk session
Query Parameters:
format(optional):csvorjson(default:csv)
Request Body: Same as custom report
Response (200): CSV or JSON export
GET /api/customer/packages
List packages (customer view).
Authentication: Clerk session
Response (200): List of packages
POST /api/customer/packages
Not implemented in the current app (customer packages route is GET only). Upload packages via the Connect UI or the presigned API flow (POST /api/v1/packages/upload-url → PUT → POST /api/v1/packages/process) documented above and in docs/API/package-upload-integration.md.
GET /api/customer/dispatches
List dispatch packages.
Authentication: Clerk session
Response (200): List of dispatches
POST /api/customer/dispatches
Create dispatch package.
Authentication: Clerk session
Request: Same as POST /api/v1/dispatches
Response (201): Dispatch object
GET /api/customer/dispatches/{dispatchId}
Get dispatch details.
Authentication: Clerk session
Response (200): Dispatch object
DELETE /api/customer/dispatches/{dispatchId}
Delete dispatch package.
Authentication: Clerk session
Response (204): No content
GET /api/customer/dispatches/{dispatchId}/zip
Download dispatch ZIP.
Authentication: Clerk session
Response (200): ZIP file
GET /api/customer/dispatches/{dispatchId}/launches
Get dispatch launch statistics.
Authentication: Clerk session
Response (200): Launch statistics
GET /api/customer/dispatches/{dispatchId}/report
Get dispatch report.
Authentication: Clerk session
Response (200): Dispatch report data
GET /api/customer/webhooks
List webhooks.
Authentication: Clerk session
Response (200): List of webhooks
POST /api/customer/webhooks
Create webhook.
Authentication: Clerk session
Request: Same as POST /api/v1/webhooks
Response (201): Webhook object
GET /api/customer/webhooks/{webhookId}
Get webhook details.
Authentication: Clerk session
Response (200): Webhook object
PATCH /api/customer/webhooks/{webhookId}
Update webhook.
Authentication: Clerk session
Request Body:
{
"url": "https://new-url.example.com/webhook",
"secret": "newsecret",
"active": true
}
Response (200): Updated webhook object
DELETE /api/customer/webhooks/{webhookId}
Delete webhook.
Authentication: Clerk session
Response (204): No content
GET /api/customer/webhooks/{webhookId}/deliveries
Get webhook delivery history.
Authentication: Clerk session
Query Parameters:
limit(optional): Results per page (default: 20)offset(optional): Pagination offset
Response (200):
{
"deliveries": [
{
"id": "delivery-123",
"status": "success",
"response_code": 200,
"delivered_at": "2025-01-15T10:30:00.000Z"
}
],
"total": 1
}
POST /api/customer/webhooks/{webhookId}/test
Test webhook delivery.
Authentication: Clerk session
Response (200):
{
"status": "sent",
"delivery_id": "delivery-123"
}
GET /api/customer/api-keys
List API keys.
Authentication: Clerk session
Response (200):
{
"api_keys": [
{
"id": "key-123",
"name": "Production Key",
"scopes": ["read", "write"],
"created_at": "2025-01-15T10:30:00.000Z",
"last_used_at": "2025-01-15T12:00:00.000Z"
}
]
}
POST /api/customer/api-keys
Create API key.
Authentication: Clerk session
Request Body:
{
"name": "Production Key",
"scopes": ["read", "write"]
}
Response (201):
{
"id": "key-123",
"api_key": "scorm_live_abc123def456...",
"name": "Production Key",
"scopes": ["read", "write"],
"created_at": "2025-01-15T10:30:00.000Z"
}
Important: The api_key is only shown once. Save it securely.
DELETE /api/customer/api-keys/{keyId}
Delete API key.
Authentication: Clerk session
Response (204): No content
GET /api/customer/usage
Get usage statistics.
Authentication: Clerk session
Response (200):
{
"packages": {
"total": 45,
"quota": 100
},
"storage": {
"used_bytes": 21474836480,
"quota_bytes": 53687091200
},
"sessions": {
"total": 1250,
"this_month": 350
}
}
GET /api/customer/tenant
Get tenant information.
Authentication: Clerk session
Response (200):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"created_at": "2025-01-01T00:00:00.000Z"
}
GET /api/customer/credentials
Get credentials information.
Authentication: Clerk session
Response (200):
{
"api_endpoint": "https://app.allureconnect.com",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000"
}
GET /api/customer/xapi/statements
Get xAPI statements (customer view).
Authentication: Clerk session
Query Parameters: Same as GET /api/v1/xapi/statements
Response (200): xAPI statements
GET /api/customer/xapi/stats
Get xAPI statistics.
Authentication: Clerk session
Response (200):
{
"total_statements": 1250,
"unique_actors": 150,
"activities_tracked": 25
}
GET /api/customer/billing
Get billing information.
Authentication: Clerk session
Response (200):
{
"plan": "professional",
"status": "active",
"next_billing_date": "2025-02-01T00:00:00.000Z"
}
POST /api/customer/billing/portal
Get billing portal URL.
Authentication: Clerk session
Response (200):
{
"url": "https://billing.example.com/portal?session=..."
}
Admin Routes
Admin routes require system administrator access and can view all tenants.
GET /api/admin/system/health
Get system health status.
Authentication: System admin
Response (200):
{
"status": "healthy",
"database": "connected",
"storage": "available",
"uptime_seconds": 86400
}
GET /api/admin/tenants
List all tenants.
Authentication: System admin
Query Parameters:
limit(optional): Results per pageoffset(optional): Pagination offset
Response (200): List of tenants
GET /api/admin/tenants/activity
Get tenant activity statistics.
Authentication: System admin
Response (200): Activity statistics
GET /api/admin/packages
List all packages (across all tenants).
Authentication: System admin
Query Parameters:
tenant_id(optional): Filter by tenantlimit(optional): Results per pageoffset(optional): Pagination offset
Response (200): List of packages
DELETE /api/admin/packages/bulk
Bulk delete packages.
Authentication: System admin
Request Body:
{
"package_ids": ["pkg_123", "pkg_456"],
"tenant_id": "550e8400-e29b-41d4-a716-446655440000"
}
Response (200):
{
"deleted": 2,
"failed": 0
}
PATCH /api/admin/packages/bulk
Bulk update packages.
Authentication: System admin
Request Body:
{
"package_ids": ["pkg_123", "pkg_456"],
"updates": {
"tags": ["updated"]
}
}
Response (200): Update results
GET /api/admin/packages/{packageId}/versions
Get package version history (admin view).
Authentication: System admin
Response (200): Version history
POST /api/admin/packages/{packageId}/versions
Create new package version (admin).
Authentication: System admin
Response (201): Version object
PUT /api/admin/packages/{packageId}/distribution
Update package distribution settings.
Authentication: System admin
Request Body:
{
"enabled": true,
"allowed_domains": ["example.com"]
}
Response (200): Updated distribution settings
GET /api/admin/reports/learner-progress
Get learner progress report (all tenants).
Authentication: System admin
Query Parameters: Same as customer route
Response (200): Learner progress data
GET /api/admin/reports/learner-progress/export
Export learner progress (all tenants).
Authentication: System admin
Response (200): CSV file
POST /api/admin/reports/custom
Generate custom report (all tenants).
Authentication: System admin
Request: Same as customer route
Response (200): Custom report data
POST /api/admin/reports/custom/export
Export custom report (all tenants).
Authentication: System admin
Response (200): CSV or JSON export
GET /api/admin/metrics/usage
Get usage metrics.
Authentication: System admin
Response (200):
{
"total_packages": 1250,
"total_sessions": 50000,
"total_storage_bytes": 1073741824000,
"active_tenants": 45
}
GET /api/admin/metrics/saas
Get SaaS metrics.
Authentication: System admin
Response (200):
{
"total_revenue": 125000.00,
"active_subscriptions": 45,
"churn_rate": 0.05,
"mrr": 10000.00
}
GET /api/admin/usage-alerts
Get usage alerts.
Authentication: System admin
Response (200):
{
"alerts": [
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"type": "quota_warning",
"message": "Storage quota 80% used"
}
]
}
GET /api/admin/webhooks
List all webhooks (all tenants).
Authentication: System admin
Response (200): List of webhooks
GET /api/admin/webhooks/deliveries
Get webhook delivery history (all tenants).
Authentication: System admin
Query Parameters:
webhook_id(optional): Filter by webhookstatus(optional): Filter by statuslimit(optional): Results per pageoffset(optional): Pagination offset
Response (200): Delivery history
POST /api/admin/webhooks/{webhookId}/test
Test webhook delivery (admin).
Authentication: System admin
Response (200): Test result
GET /api/admin/api-keys
List all API keys (all tenants).
Authentication: System admin
Response (200): List of API keys
POST /api/admin/api-keys
Create API key (admin).
Authentication: System admin
Request Body:
{
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Admin Key",
"scopes": ["read", "write", "admin"]
}
Response (201): API key object
DELETE /api/admin/api-keys/{keyId}
Delete API key (admin).
Authentication: System admin
Response (204): No content
GET /api/admin/audit-logs
Get audit logs.
Authentication: System admin
Query Parameters:
tenant_id(optional): Filter by tenantaction(optional): Filter by actiondate_from(optional): ISO 8601 datetimedate_to(optional): ISO 8601 datetimelimit(optional): Results per pageoffset(optional): Pagination offset
Response (200):
{
"logs": [
{
"id": "log-123",
"tenant_id": "550e8400-e29b-41d4-a716-446655440000",
"action": "package.uploaded",
"user_id": "user-123",
"timestamp": "2025-01-15T10:30:00.000Z"
}
],
"total": 1
}
GET /api/admin/check-status
Check system status.
Authentication: System admin
Response (200): Status information
GET /api/admin/debug-status
Get debug status information.
Authentication: System admin
Response (200): Debug information
GET /api/admin/r2-status
Get Cloudflare R2 storage status.
Authentication: System admin
Response (200):
{
"enabled": true,
"bucket": "scorm-packages",
"region": "us-east-1"
}
Request/Response Examples
Complete Integration Example
See the Integration Guides for complete examples in various languages and frameworks:
Handling Version Conflicts
When updating sessions, always include the version field for optimistic locking:
async function updateSessionWithRetry(sessionId: string, updates: any, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
// Step 1: Get current session
const session = await fetch(`/api/v1/sessions/${sessionId}`, {
headers: { 'X-API-Key': apiKey }
}).then(r => r.json());
// Step 2: Merge your changes
const mergedData = {
...session.cmi_data,
...updates.cmi_data
};
// Step 3: Attempt update with current version
const response = await fetch(`/api/v1/sessions/${sessionId}`, {
method: 'PUT',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
version: session.version, // Use current version
cmi_data: mergedData,
...updates
})
});
if (response.ok) {
return await response.json(); // Success!
}
if (response.status === 409) {
console.log(`Version conflict, retrying... (${attempt + 1}/${maxRetries})`);
continue; // Retry with fresh data
}
throw new Error(`Update failed: ${response.status}`);
}
throw new Error('Max retries exceeded for version conflict');
}
Error Handling with Retry
async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Don't retry client errors (4xx)
if (response.status >= 400 && response.status < 500) {
return response;
}
// Success
if (response.ok) {
return response;
}
// Retry server errors (5xx) with exponential backoff
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
console.log(`Server error, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
} catch (error) {
// Network error - retry with backoff
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`Network error, retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}
Additional Resources
- Error Codes Reference - Complete error code documentation
- Rate Limiting Guide - Rate limiting details
- Quota Management Guide - Quota information
- Webhook Setup Guide - Webhook configuration
- CMI Data Guide - Understanding SCORM CMI data
- Package Validation Guide - SCORM package requirements
Last Updated: 2025-01-15
API Version: 1.0.0