Allure Connect API (overview)
High-level guide for calling the Allure Connect REST API. For the machine-readable contract and try-it console, use GET /api/docs/openapi (OpenAPI JSON) and /api/docs (interactive Scalar UI) on your deployment.
Partner upload runbook (presigned R2, CORS, process): Package upload API — partner integration
Legacy note: Some older docs referred to
POST /api/v1/packagesas a multipart upload. The current app exposesGET /api/v1/packagesfor listing; large package ingestion usesPOST /api/v1/packages/upload-url→PUTto storage →POST /api/v1/packages/process. Small direct uploads may usePOST /api/v1/packages/upload(see OpenAPI and api-reference).
Table of Contents
- Quick Start
- Authentication
- Rate Limiting
- Endpoints
- Error Handling
- Best Practices
- SDKs & Tools
- Additional Resources
Quick Start
Base URL
Production: https://app.allureconnect.com
Development: http://localhost:3000
API Version
Current: v1 — routes are prefixed with /api/v1/ except health checks.
Quick example (recommended large upload)
Mint a presigned URL from your backend, upload the ZIP (browser or server), then process:
const base = 'https://app.allureconnect.com';
const mint = await fetch(`${base}/api/v1/packages/upload-url`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
tenant_id: tenantId,
uploaded_by: 'user-123',
filename: 'course.zip',
file_size: fileSizeBytes,
}),
});
if (!mint.ok) {
throw new Error(`Mint failed: ${mint.status} ${await mint.text()}`);
}
const ticket = await mint.json();
const put = await fetch(ticket.presigned_url, {
method: 'PUT',
headers: ticket.required_put_headers,
body: zipBlob,
});
if (!put.ok) {
throw new Error(`Storage PUT failed: ${put.status} ${await put.text()}`);
}
const proc = await fetch(`${base}/api/v1/packages/process`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
tenant_id: tenantId,
uploaded_by: 'user-123',
storage_path: ticket.storage_path,
original_filename: 'course.zip',
}),
});
if (!proc.ok) {
throw new Error(`Process failed: ${proc.status} ${await proc.text()}`);
}
await proc.json();
Authentication
Use an API key per tenant. Prefer Authorization: Bearer <api_key>; X-API-Key: <api_key> is also supported. If both are sent, Bearer wins.
Authorization: Bearer ac_live_...
X-API-Key: ac_live_...
Example
curl -H "Authorization: Bearer ac_live_..." \
https://app.allureconnect.com/api/v1/packages
API Key Scopes
API keys are scoped (e.g. read vs write vs admin). See the OpenAPI spec and dashboard.
Obtaining API Keys
Create keys in the Connect dashboard (Dashboard → API Keys).
See also: API key security
Rate Limiting
Rate limits prevent abuse and ensure fair usage across all tenants.
Limits
- Read operations: 1000 requests per minute
- Write operations: 100 requests per minute
- Upload operations: 10 requests per minute
Rate Limit Headers
Responses include rate limit information:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 995
X-RateLimit-Reset: 1640995200
Exceeded Rate Limit
HTTP/1.1 429 Too Many Requests
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded"
}
}
Solution: Wait until the reset time or implement exponential backoff.
Endpoints
Health Check
GET /api/health
Check service health status.
Authentication: None required
Response:
{
"status": "healthy",
"service": "scorm-api",
"version": "1.5.0",
"timestamp": "2025-01-01T12:00:00Z",
"checks": {
"database": true
}
}
Packages
List Packages
GET /api/v1/packages
List all SCORM packages for the authenticated tenant.
Query Parameters:
page(integer, default: 1) - Page numberlimit(integer, default: 20, max: 100) - Results per page
Example:
curl -H "Authorization: Bearer ac_live_..." \
"https://app.allureconnect.com/api/v1/packages"
Response:
{
"packages": [
{
"id": "pkg-123",
"tenant_id": "tenant-456",
"title": "Workplace Safety Essentials",
"version": "1.2",
"launch_url": "https://app.allureconnect.com/content/...",
"manifest_url": "https://app.allureconnect.com/content/.../imsmanifest.xml",
"file_size_bytes": 5242880,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
],
"count": 42
}
Upload package (large files)
Use POST /api/v1/packages/upload-url (alias POST /api/v1/packages/upload-sessions) to mint a short-lived presigned URL, PUT the ZIP to that URL, then POST /api/v1/packages/process. See Package upload API — partner integration and R2 browser upload CORS.
For small files in trusted contexts, POST /api/v1/packages/upload accepts multipart/form-data (subject to platform body limits). Details: api-reference.
Get Package
GET /api/v1/packages/{id}
Get details of a specific package.
Response:
{
"id": "pkg-123",
"tenant_id": "tenant-456",
"title": "Course Title",
"version": "1.2",
"launch_url": "https://app.allureconnect.com/content/...",
"manifest_url": "https://app.allureconnect.com/content/.../imsmanifest.xml",
"file_size_bytes": 5242880,
"metadata": {
"description": "Course description",
"sco_count": 5
},
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
Delete Package
DELETE /api/v1/packages/{id}
Delete a SCORM package and all associated sessions.
Response:
{
"success": true,
"package_id": "pkg-123"
}
Sessions
List Sessions
GET /api/v1/sessions
List learner sessions with optional filtering.
Query Parameters:
package_id(UUID) - Filter by packageuser_id(UUID) - Filter by usercompletion_status(string) - Filter by completion (not_attempted, incomplete, completed)success_status(string) - Filter by success (unknown, passed, failed)page(integer, default: 1)limit(integer, default: 20, max: 100)
Example:
curl -H "X-API-Key: scorm_abc123..." \
"https://app.allureconnect.com/api/v1/sessions?package_id=pkg-123&completion_status=completed"
Response:
{
"sessions": [
{
"id": "session-789",
"package_id": "pkg-123",
"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-01T00:00:00Z",
"updated_at": "2025-01-01T01:00:00Z",
"package": {
"title": "Course Title",
"version": "1.2"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"total_pages": 8
}
}
Get Session
GET /api/v1/sessions/{id}
Get detailed session information including full CMI data.
Response:
{
"session": {
"id": "session-789",
"package_id": "pkg-123",
"user_id": "user-abc",
"completion_status": "completed",
"success_status": "passed",
"score": {
"scaled": 0.95,
"raw": 95,
"min": 0,
"max": 100
},
"cmi_data": {
"cmi.core.lesson_status": "completed",
"cmi.core.score.raw": "95",
"cmi.suspend_data": "..."
},
"package": {
"title": "Course Title"
}
}
}
Update Session
PUT /api/v1/sessions/{id}
Update session CMI data (learner progress).
Request Body:
{
"cmi_data": {
"cmi.core.lesson_status": "completed",
"cmi.core.score.raw": "95"
},
"completion_status": "completed",
"success_status": "passed",
"score": {
"scaled": 0.95,
"raw": 95,
"min": 0,
"max": 100
},
"session_time": "PT1H",
"suspend_data": "..."
}
Response:
{
"success": true,
"session_id": "session-789",
"updated_at": "2025-01-01T01:00:00Z"
}
Dispatches
Create Dispatch
POST /api/v1/dispatches
Create a dispatch for external SCORM package distribution.
Request Body:
{
"package_id": "pkg-123",
"dispatch_name": "Partner Training",
"registration_limit": 100,
"expires_in_hours": 720,
"allowed_domains": ["partner.com", "*.partner.com"]
}
Response:
{
"dispatch": {
"id": "dispatch-456",
"tenant_id": "tenant-789",
"package_id": "pkg-123",
"dispatch_name": "Partner Training",
"registration_limit": 100,
"registration_count": 0,
"expires_at": "2025-02-01T00:00:00Z",
"status": "active",
"token": "eyJhbGciOiJIUzI1NiIs...",
"launch_url": "https://app.allureconnect.com/player/dispatch/eyJhbGciOiJIUzI1NiIs...",
"created_at": "2025-01-01T00:00:00Z"
}
}
List Dispatches
GET /api/v1/dispatches
List all dispatches for the authenticated tenant.
Query Parameters:
page(integer, default: 1)limit(integer, default: 20, max: 100)
Response:
{
"dispatches": [
{
"id": "dispatch-456",
"package_id": "pkg-123",
"dispatch_name": "Partner Training",
"registration_limit": 100,
"registration_count": 25,
"expires_at": "2025-02-01T00:00:00Z",
"status": "active",
"created_at": "2025-01-01T00:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 5,
"total_pages": 1
}
}
Revoke Dispatch
DELETE /api/v1/dispatches/{id}
Revoke a dispatch and prevent further access.
Response:
{
"status": "revoked",
"dispatch_id": "dispatch-456"
}
Generate Dispatch ZIP
POST /api/v1/dispatches/{id}/generate
Trigger background generation of the offline dispatch ZIP file.
Response:
{
"success": true,
"message": "Dispatch generation queued.",
"dispatch_id": "dispatch-456"
}
xAPI (Experience API)
Store xAPI Statements
POST /api/v1/xapi/statements
Store one or more xAPI statements.
Request Body (Single):
{
"actor": {
"name": "John Doe",
"mbox": "mailto:john@example.com"
},
"verb": {
"id": "http://adlnet.gov/expapi/verbs/completed",
"display": { "en-US": "completed" }
},
"object": {
"id": "https://app.allureconnect.com/packages/pkg-123",
"definition": {
"name": { "en-US": "Course Title" },
"type": "http://adlnet.gov/expapi/activities/course"
}
},
"result": {
"score": { "scaled": 0.95, "raw": 95, "min": 0, "max": 100 },
"success": true,
"completion": true,
"duration": "PT1H30M"
},
"timestamp": "2025-01-01T12:00:00Z"
}
Request Body (Multiple):
[
{ /* statement 1 */ },
{ /* statement 2 */ }
]
Response (Single):
"550e8400-e29b-41d4-a716-446655440000"
Response (Multiple):
[
"550e8400-e29b-41d4-a716-446655440000",
"660f9511-f3ac-52e5-b827-557766551111"
]
Query xAPI Statements
GET /api/v1/xapi/statements
Query xAPI statements with filters.
Query Parameters:
statementId(UUID) - Get specific statementverb(URL) - Filter by verb IRIactivity(URL) - Filter by activity IRIregistration(UUID) - Filter by registrationsince(ISO 8601) - Statements stored sinceuntil(ISO 8601) - Statements stored untilpage(integer, default: 1)limit(integer, default: 20, max: 100)
Example:
curl -H "X-API-Key: scorm_abc123..." \
"https://app.allureconnect.com/api/v1/xapi/statements?verb=http://adlnet.gov/expapi/verbs/completed"
Response:
{
"statements": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"actor": { /* ... */ },
"verb": { /* ... */ },
"object": { /* ... */ },
"result": { /* ... */ },
"timestamp": "2025-01-01T12:00:00Z",
"stored": "2025-01-01T12:00:00.123Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 250,
"total_pages": 13
}
}
Get xAPI Statement
GET /api/v1/xapi/statements/{id}
Retrieve a specific xAPI statement.
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"actor": { /* ... */ },
"verb": { /* ... */ },
"object": { /* ... */ },
"result": { /* ... */ },
"timestamp": "2025-01-01T12:00:00Z"
}
Actor Performance Analytics
GET /api/v1/xapi/analytics/actors
Get aggregated performance metrics by learner.
Query Parameters:
actor_mbox(email) - Filter by actor emailmin_activities(integer) - Minimum unique activitiessort(string) - Sort field (total_statements, avg_score_scaled, etc.)order(asc/desc, default: desc)page(integer, default: 1)limit(integer, default: 20, max: 100)
Response:
{
"actors": [
{
"actor_mbox": "john@example.com",
"actor_name": "John Doe",
"total_statements": 45,
"unique_activities": 12,
"completed_count": 10,
"passed_count": 9,
"failed_count": 1,
"avg_score_scaled": 0.87,
"first_activity": "2025-01-01T00:00:00Z",
"last_activity": "2025-01-15T14:30:00Z"
}
],
"pagination": { /* ... */ }
}
Activity Performance Analytics
GET /api/v1/xapi/analytics/activities
Get aggregated performance metrics by activity.
Query Parameters:
activity_id(URL) - Filter by activity IRIactivity_type(URL) - Filter by activity typepackage_id(UUID) - Filter by SCORM packagemin_learners(integer) - Minimum unique learnerssort(string) - Sort fieldorder(asc/desc, default: desc)page(integer, default: 1)limit(integer, default: 20, max: 100)
Response:
{
"activities": [
{
"activity_id": "https://app.allureconnect.com/packages/pkg-123",
"activity_type": "http://adlnet.gov/expapi/activities/course",
"package_id": "pkg-123",
"total_statements": 250,
"unique_learners": 50,
"completed_count": 45,
"passed_count": 42,
"failed_count": 3,
"avg_score_scaled": 0.82,
"first_activity": "2025-01-01T00:00:00Z",
"last_activity": "2025-01-15T16:00:00Z"
}
],
"pagination": { /* ... */ }
}
Error Handling
All errors follow a consistent format:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {}
},
"request_id": "correlation-id"
}
Common Error Codes
| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Invalid request format |
| 400 | VALIDATION_ERROR | Failed validation |
| 401 | UNAUTHORIZED | Missing/invalid API key |
| 403 | FORBIDDEN | Insufficient permissions |
| 403 | QUOTA_EXCEEDED | Quota limit exceeded |
| 404 | NOT_FOUND | Resource not found |
| 404 | PACKAGE_NOT_FOUND | SCORM package not found |
| 404 | SESSION_NOT_FOUND | Session not found |
| 409 | CONFLICT | State conflict |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests |
| 500 | INTERNAL_ERROR | Server error |
| 500 | DATABASE_ERROR | Database operation failed |
Error Response Examples
Validation Error:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": {
"fieldErrors": {
"email": ["Invalid email format"],
"age": ["Must be greater than 0"]
}
}
}
}
Quota Exceeded:
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Package limit exceeded",
"details": {
"limit": 100,
"current": 100
}
}
}
Best Practices
1. Always Check Response Status
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
console.error(`API Error [${error.error.code}]: ${error.error.message}`);
throw new Error(error.error.message);
}
const data = await response.json();
2. Implement Retry Logic
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
// Don't retry on client errors (4xx)
if (response.status >= 400 && response.status < 500) {
return response;
}
if (response.ok) {
return response;
}
// Retry on server errors (5xx)
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
} catch (error) {
if (i === maxRetries - 1) throw error;
}
}
}
3. Handle Rate Limiting
async function fetchWithRateLimit(url, options) {
const response = await fetch(url, options);
if (response.status === 429) {
const resetTime = response.headers.get('X-RateLimit-Reset');
const waitTime = (parseInt(resetTime!) - Date.now() / 1000) * 1000;
await new Promise(resolve => setTimeout(resolve, waitTime));
return fetchWithRateLimit(url, options); // Retry
}
return response;
}
4. Use Pagination
async function fetchAllPackages(apiKey) {
const allPackages = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(
`https://app.allureconnect.com/api/v1/packages?page=${page}&limit=100`,
{ headers: { 'X-API-Key': apiKey } }
);
const { packages, count } = await response.json();
allPackages.push(...packages);
hasMore = packages.length === 100;
page++;
}
return allPackages;
}
5. Monitor API Usage
Track your API usage to avoid hitting limits:
const response = await fetch(url, options);
const rateLimitRemaining = response.headers.get('X-RateLimit-Remaining');
const rateLimitReset = response.headers.get('X-RateLimit-Reset');
console.log(`Remaining requests: ${rateLimitRemaining}`);
console.log(`Reset time: ${new Date(parseInt(rateLimitReset!) * 1000)}`);
SDKs & Tools
Official SDKs
Coming Soon:
- Node.js SDK
- Python SDK
- PHP SDK
- .NET SDK
OpenAPI specification
- JSON (served by the app):
GET /api/docs/openapi— e.g.https://app.allureconnect.com/api/docs/openapi - Interactive docs:
/api/docs(Scalar UI)
Use with Postman, Insomnia, OpenAPI Generator, etc.
There is no separate /api/docs/openapi.yaml route; import the JSON above if your tool requires a file download.
Additional Resources
Documentation
- Package upload integration (presigned R2) — Partner/backend flow, env vars, Convex internal token, rate limits
- API versioning strategy — Versioning notes (see also
lib/openapi.tsinfo.version) - API key security
- SCORM API reference
- Changelog — Version history and upgrade guides
Specifications
Support
- Documentation: https://docs.allurelms.com/api
- Email: engineering@allurelms.com
- Issues: https://github.com/AllureLMS/scorm-api/issues
- Status Page: https://status.allurelms.com
Community
- Discord: https://discord.gg/allurelms
- Twitter: @AllureLMS
- Blog: https://blog.allurelms.com
Last Updated: 2026-02-11
API path version: v1 (/api/v1/...)
OpenAPI document: info.version in lib/openapi.ts (served at /api/docs/openapi)