SCORM API Reference

Complete reference documentation for all SCORM API endpoints, request/response schemas, authentication requirements, and error handling.

Table of Contents

  1. Overview
  2. Authentication
  3. Base URLs
  4. API Versioning
  5. Rate Limiting
  6. Error Handling
  7. API Endpoints
  8. 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:

  1. POST /api/v1/packages/upload-url (or alias POST /api/v1/packages/upload-sessions) to mint a presigned URL
  2. PUT the ZIP to presigned_url with required_put_headers
  3. POST /api/v1/packages/process to 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 UUID
  • limit (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_url already 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 UUID
  • user_id (optional): Filter by user ID
  • package_id (optional): Filter by package ID
  • completion_status (optional): not_attempted, incomplete, completed
  • success_status (optional): unknown, passed, failed
  • date_from (optional): ISO 8601 datetime
  • date_to (optional): ISO 8601 datetime
  • page (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 UUID
  • limit (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 UUID
  • actor (optional): Filter by actor (JSON)
  • verb (optional): Filter by verb ID
  • activity (optional): Filter by activity ID
  • since (optional): ISO 8601 datetime
  • until (optional): ISO 8601 datetime
  • limit (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 UUID
  • activity_id (optional): Filter by activity
  • date_from (optional): ISO 8601 datetime
  • date_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 UUID
  • actor (optional): Filter by actor (JSON)
  • date_from (optional): ISO 8601 datetime
  • date_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): csv or json (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-urlPUTPOST /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 page
  • offset (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 tenant
  • limit (optional): Results per page
  • offset (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 webhook
  • status (optional): Filter by status
  • limit (optional): Results per page
  • offset (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 tenant
  • action (optional): Filter by action
  • date_from (optional): ISO 8601 datetime
  • date_to (optional): ISO 8601 datetime
  • limit (optional): Results per page
  • offset (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


Last Updated: 2025-01-15
API Version: 1.0.0