REST API v1

Developer Documentation

Manage CE courses, enrollments, learner progress, certificates, and transactions through a unified REST API

Quick Start

1Generate an API Key

Go to Developer API in your dashboard. Keys are prefixed with cbapi_.

2Make Your First Request

Use the Authorization: Bearer header with any of the 9 services below.

3Check Your Response

All responses return { "success": true, "data": ... } with pagination.

Authentication

All API requests require a Bearer token. API keys must start with cbapi_.

Header
Authorization: Bearer cbapi_YOUR_API_KEY

Available Scopes

courses:readView CE courses and activities
courses:writeCreate, update, and delete CE courses
content:readFetch course phases, HTML content, and quiz questions for native rendering
enrollments:readView course enrollments and sales data
enrollments:writeCreate, update, and cancel enrollments
progress:readView learner progress and phase completions
progress:writeSubmit phase completions and quiz answers for server-side validation
completions:writeMark an entire activity complete in a single call (external LMS / portal ingest)
certificates:readView and verify completion certificates
transactions:readView payment and transaction history
events:readView virtual and in-person live events, schedules, venues, and capacity
registrations:readView event registrations, attendance, and verified duration
registrations:writeRegister learners for live and virtual events
outcomes:readReceive follow-up recheck answer keys (is_correct) in course content for server-side grading
payouts:readView marketplace payouts and revenue-share records
webhooks:readList and view registered webhook endpoints
webhooks:writeCreate, update, and delete webhook endpoints
adminFull access to all resources and operations

API Endpoints

Nine REST services for course management, content delivery, enrollments, progress tracking, certificates, transactions, webhooks, and utility endpoints

Courses API

Create, manage, and query CE courses and continuing education activities

/api-courses• 60 req/min
GETPOSTPUTDELETE

Course Content API

Fetch course phases with HTML content and quiz questions for native rendering in your app. Answer keys are stripped server-side.

/api-course-content• 60 req/min
GET

Enrollments / Sales API

Manage learner enrollments, track sales, and handle payment status for courses

/api-enrollments• 60 req/min
GETPOSTPUTDELETE

Submit Progress API

Submit phase completions and quiz answers. Server validates answers, calculates scores, and triggers certificate generation.

/api-submit-progress• 60 req/min
POST

Progress API

Track learner progress through courses, view phase completions and time spent

/api-progress• 60 req/min
GET

Certificates API

Retrieve and verify completion certificates for learners who finished courses

/api-certificates• 60 req/min
GET

Transactions API

Query payment history, revenue data, and transaction status for course sales

/api-transactions• 60 req/min
GET

Live Events API

List virtual (Zoom) and in-person live events with schedule, venue, capacity, pricing, and the CE activity they award credit for

/api-events• 60 req/min
GET

Registrations & Attendance API

Register learners for live events and read back verified attendance, duration, and check-in times used for live-activity credit

/api-event-registrations• 60 req/min
GETPOST

Payouts API

Read marketplace payouts and Stripe Connect revenue-share transfers for your provider organization

/api-payouts• 60 req/min
GET

Webhooks API

Register, list, update, and delete webhook endpoints for event-driven integrations

/api-webhooks• 60 req/min
GETPOSTPUTDELETE

Subscription Plans

Retrieve active subscription plans with pricing and feature limits — no API key required

/get-subscription-plans• Unlimited
GET

Code Examples

curl
# List your courses
curl -X GET "https://aeljuvrsbvkajosclvxs.supabase.co/functions/v1/api-courses" \
  -H "Authorization: Bearer cbapi_YOUR_API_KEY"

# Create an enrollment
curl -X POST "https://aeljuvrsbvkajosclvxs.supabase.co/functions/v1/api-enrollments" \
  -H "Authorization: Bearer cbapi_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "activity_id": "COURSE_ID",
    "user_id": "USER_ID"
  }'

# Check learner progress
curl -X GET "https://aeljuvrsbvkajosclvxs.supabase.co/functions/v1/api-progress?activity_id=COURSE_ID" \
  -H "Authorization: Bearer cbapi_YOUR_API_KEY"

Component Types Reference

The api-course-content endpoint returns phases containing these component types. Answer keys and correct mappings are stripped server-side for all graded types.

TypeDescriptionStripped?Submit Format
htmlStatic HTML content blockNoNo submission needed — mark phase complete when viewed
videoEmbedded video playerNoNo submission needed — mark phase complete when viewed
mcqMultiple-choice questions (assessment)✓ Yesanswers: [{ question_id, selected }]
drag-drop-matchMatch source items to target items by dragging✓ Yesmatches: [{ source_id, target_id }]
groupingDrag items into the correct category group✓ Yesplacements: [{ item_id, group_id }]
timed-quizSpeed-round quiz with per-question timers✓ Yesanswers: [{ question_id, selected_index }]
card-flipMemory-match card flipping game✓ Yesmatched_pairs: [{ card1_id, card2_id }]
narrative-escapeInteractive escape-room with scenes and hotspot puzzles✓ YesNo standardized submission — mark phase complete when all puzzles solved
choiceSingle-choice selector (optionally graded)✓ Yesanswers: [{ question_id, selected }]

Integration Guide: Native Content Delivery

Embed courses natively in your application. Learners never leave your app — you fetch content via API, render it in your own UI, and submit completions back for server-side scoring and certification.

1
Sync the Course Catalog
courses:read

Pull published courses to display in your UI. The response includes name, description, pricing, therapeutic_area, and disease_state.

GET /api-courses?status=published&limit=50
Authorization: Bearer cbapi_YOUR_API_KEY

💡Use include_unlisted=true to also retrieve non-marketplace courses you own.

2
Enroll a Learner
enrollments:write

When a user clicks "Enroll" in your app, create an enrollment record. Pass the course ID as activity_id and the learner's user ID.

POST /api-enrollments
Content-Type: application/json
Authorization: Bearer cbapi_YOUR_API_KEY

{
  "activity_id": "COURSE_UUID",
  "user_id": "LEARNER_UUID",
  "enrollment_type": "api"
}

💡Ensure your API key has the enrollments:write scope.

3
Fetch Course Content for Native Rendering
content:read

Retrieve the full phase structure with HTML content, video URLs, quiz questions, and interactive games (drag-drop, grouping, card-flip, timed-quiz, escape rooms). All answer keys are stripped server-side.

GET /api-course-content?course_id=COURSE_UUID
Authorization: Bearer cbapi_YOUR_API_KEY

// Response includes phases[] with components:
// - type: "html" → render as HTML content
// - type: "video" → embed video player
// - type: "mcq" → render quiz (no answer keys)
// - type: "drag-drop-match" → sourceItems[] + targetItems[] (shuffled)
// - type: "grouping" → groups[] + items[] (correctGroup removed)
// - type: "timed-quiz" → questions[] with timeLimit per question
// - type: "card-flip" → cards[] (pairId removed, shuffled)
// - type: "narrative-escape" → scenes[] with hotspot puzzles

💡See the Component Types Reference above for the full schema of each type and the correct submission format.

4
Submit Phase Completions & Quiz Answers
progress:write

When a learner finishes a phase or submits quiz answers, post the completion back. The server validates answers, calculates scores, and auto-generates certificates when all required phases are done.

POST /api-submit-progress
Content-Type: application/json
Authorization: Bearer cbapi_YOUR_API_KEY

{
  "activity_id": "COURSE_UUID",
  "user_id": "LEARNER_UUID",
  "phase_type": "post-assessment",
  "phase_id": "phase-1",
  "answers": [
    { "question_id": "q1", "selected": "B" },
    { "question_id": "q2", "selected": "A" }
  ],
  "time_spent_minutes": 12
}

💡For content phases (no quiz), omit the answers array. The server returns score, passed status, and whether all phases are complete.

5
Track Progress
progress:read

Poll learner progress to update your UI. The response includes current_phase, completed_phases, time_spent_minutes, and certificate status.

GET /api-progress?activity_id=COURSE_UUID&user_id=LEARNER_UUID
Authorization: Bearer cbapi_YOUR_API_KEY

💡For detailed phase-level data (scores, pass/fail), use GET /api-progress/{id} with the activity record ID.

6
Retrieve Certificates
certificates:read

Once all required phases are complete, the certificate is auto-generated. Query certificates to get the certificate_id and completion timestamp.

GET /api-certificates?activity_id=COURSE_UUID&user_id=LEARNER_UUID
Authorization: Bearer cbapi_YOUR_API_KEY

💡Certificates include a unique certificate_id that can be used for verification.

Webhooks

Subscribe to real-time event notifications. Configure webhooks in your Developer Portal to receive HTTP POST callbacks when key CE lifecycle events occur.

Event Types

enrollment.createdSupported event type. When delivered, it is triggered when a learner enrolls in a CE activity
Payload
{
  "event": "enrollment.created",
  "timestamp": "2025-04-13T14:30:00Z",
  "data": {
    "enrollment_id": "enr_abc123",
    "activity_id": "act_xyz789",
    "user_id": "usr_456",
    "enrollment_type": "marketplace",
    "payment_completed": true,
    "enrolled_at": "2025-04-13T14:30:00Z"
  }
}
progress.updatedSupported event type. When delivered, it is triggered when a learner completes a phase or submits quiz answers
Payload
{
  "event": "progress.updated",
  "timestamp": "2025-04-13T15:10:00Z",
  "data": {
    "enrollment_id": "enr_abc123",
    "activity_id": "act_xyz789",
    "user_id": "usr_456",
    "phase_type": "mcq",
    "phase_title": "Post-Test Assessment",
    "score": 85,
    "passed": true,
    "time_spent_minutes": 12,
    "completed_phases": 3,
    "total_phases": 5
  }
}
certificate.issuedSupported event type. When delivered, it is triggered when a completion certificate is generated after all phases pass
Payload
{
  "event": "certificate.issued",
  "timestamp": "2025-04-13T16:00:00Z",
  "data": {
    "certificate_id": "cert_xyz789",
    "enrollment_id": "enr_abc123",
    "activity_id": "act_xyz789",
    "user_id": "usr_456",
    "certificate_number": "CE-2025-00042",
    "activity_title": "Advanced Pharmacology Update 2025",
    "credit_hours": 2.0,
    "verification_url": "https://iqapparatus.com/verify/cert_xyz789",
    "issued_at": "2025-04-13T16:00:00Z"
  }
}
event.registration.createdSupported event type. Triggered when a learner is registered for a live or virtual event, whether through the API or your branded portal
Payload
{
  "event": "event.registration.created",
  "timestamp": "2025-08-20T14:00:00Z",
  "data": {
    "registration_id": "reg_uuid",
    "event_id": "evt_uuid",
    "event_title": "ICI Myocarditis: Live Case Debrief",
    "user_id": "usr_456",
    "status": "registered",
    "created_at": "2025-08-20T14:00:00Z"
  }
}
event.attendance.recordedSupported event type. Triggered when verified attendance is written back for a live event participant — the signal that decides live-activity credit
Payload
{
  "event": "event.attendance.recorded",
  "timestamp": "2025-09-12T18:05:00Z",
  "data": {
    "registration_id": "reg_uuid",
    "event_id": "evt_uuid",
    "user_id": "usr_456",
    "attended": true,
    "attendance_percentage": 92,
    "duration_minutes": 55,
    "meets_minimum": true
  }
}
payout.paidSupported event type. Triggered when a marketplace payout is transferred to a provider organization through Stripe Connect
Payload
{
  "event": "payout.paid",
  "timestamp": "2025-08-01T09:00:00Z",
  "data": {
    "payout_id": "po_uuid",
    "organization_id": "org-uuid",
    "amount": 268.95,
    "currency": "usd",
    "status": "paid",
    "purchase_id": "pur_uuid",
    "stripe_transfer_id": "tr_1P..."
  }
}

Signature Verification

Every webhook delivery includes an X-Webhook-Signature header and an X-Webhook-Timestamp header. Verify the signature using HMAC-SHA256 with your webhook secret:

Node.js
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, timestamp, secret) {
  const signedPayload = `${timestamp}.${payload}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Retry Policy

Timeout
30s

Your endpoint must respond within 30 seconds with a 2xx status code.

Max Retries
5

Webhook is automatically disabled after 5 consecutive failures. Re-enable from the Developer Portal.

Backoff Schedule
1Immediate
21 minute
35 minutes
430 minutes
52 hours

Rate Limiting

60
per minute
1000
per hour

Response Headers

X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset

Error Codes

400Bad Request - Invalid request format or missing required fields
401Unauthorized - Invalid or missing API key
403Forbidden - Insufficient scope permissions
404Not Found - Resource does not exist
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Server error occurred

Sample microlearning-series payload

A complete GET /api-course-content response for a published microlearning-series module — avatar video, debrief card, structured references, and series roadmap included. Use it as a test fixture while building your renderer.

All identifiers are dummy UUIDs and the video URLs are fake placeholders. In real responses,video_url is a temporary signed URL that expires atmedia_expires_at (about six hours) — re-fetch the course rather than caching it. The talking-avatar and ai-videoblocks illustrate the ready and pending states.
View sample JSON response24,106 characters
{
  "success": true,
  "data": {
    "course_id": "00000000-0000-4000-8000-000000000001",
    "name": "The New PE Classification: A to E",
    "description": "Bramwell Method microlearning activity on classifying acute pulmonary embolism using the 2026 AHA/ACC five-category system (Categories A–E).",
    "activity_type": null,
    "delivery_format": "enduring",
    "format": "bramwell",
    "delivery_style": "Case-based microlearning",
    "learning_objectives": [
      "Classify acute pulmonary embolism using the 2026 AHA/ACC five-category system.",
      "Match each category to its initial management pathway.",
      "Recognize when escalation to advanced therapy is indicated."
    ],
    "accreditation": {
      "credits": [
        {
          "id": "cme-1",
          "name": "Physician CME",
          "profession": "physician",
          "credit_type": "AMA PRA Category 1 Credit(s)™",
          "credit_type_slug": "ama-pra-cat1",
          "credit_hours": 0.25,
          "accredited_provider": "Example Joint Providership",
          "provider_statement": "This activity has been planned and implemented in accordance with the accreditation requirements and policies of the ACCME."
        }
      ],
      "total_credit_hours": 0.25,
      "accreditation_statement": "Accredited for 0.25 AMA PRA Category 1 Credit™.",
      "commercial_support": "None",
      "disclosure_summary": "All planners and faculty have disclosed no relevant financial relationships.",
      "release_date": "2026-01-01",
      "expiration_date": "2027-01-01"
    },
    "landing_page": {
      "statementOfNeed": "Acute PE classification drives management, yet the five-category system is unevenly applied.",
      "estimatedTime": "15 minutes",
      "targetAudience": [
        "Physicians",
        "Nurse Practitioners",
        "Physician Assistants",
        "Pharmacists"
      ],
      "howToEarnCredit": "Complete the microlearning, submit the evaluation, and download your certificate.",
      "hardwareSoftwareRequirements": "Modern browser with a stable internet connection."
    },
    "confidence_baseline": {
      "id": "confidence-baseline",
      "prompt": "Before you start, how confident are you in classifying acute PE severity?",
      "min": 1,
      "max": 5,
      "minLabel": "Not at all confident",
      "maxLabel": "Very confident"
    },
    "evaluation_questions": [
      {
        "id": "eval-overall-quality",
        "question": "The activity was:",
        "type": "single-select",
        "options": [
          "Excellent",
          "Very Good",
          "Good",
          "Fair",
          "Poor"
        ],
        "category": "satisfaction",
        "mooreLevel": 1,
        "required": true
      }
    ],
    "total_phases": 5,
    "phases": [
      {
        "id": "phase-hook",
        "title": "Meet the Patient",
        "type": "introduction",
        "required": true,
        "order": 0,
        "components": [
          {
            "id": "phase-hook-html",
            "type": "html",
            "content": "<h2>15 minutes. Four decisions.</h2><p>Meet Carlos — 55, presents with acute dyspnea, O₂ sat 93% on room air, BP 118/74. CT angiography confirms bilateral pulmonary emboli. Troponin I is elevated. Bedside echo shows RV dilation with an RV:LV ratio >0.9, but he is hemodynamically stable. Walk his workup in four decisions.</p>"
          }
        ]
      },
      {
        "id": "phase-decisions",
        "title": "Four Clinical Decisions",
        "type": "pre-assessment",
        "required": true,
        "order": 1,
        "components": [
          {
            "id": "phase-decisions-mcq",
            "type": "mcq",
            "questions": [
              {
                "id": "m1",
                "type": "multiple-choice",
                "matchId": "m1",
                "options": [
                  "Category C — elevated biomarkers/RV dysfunction, no hemodynamic failure",
                  "Category B — low risk, symptomatic",
                  "Submassive PE (legacy terminology)",
                  "Category D — incipient cardiopulmonary failure"
                ],
                "question": "Carlos, 55, acute PE, troponin elevated, RV:LV >0.9, BP 118/74. How do you classify his PE under the 2026 guideline?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              },
              {
                "id": "d2",
                "type": "multiple-choice",
                "matchId": "d2",
                "options": [
                  "Yes — Category C requires inpatient monitoring",
                  "No — he is hemodynamically stable and can go home",
                  "Only if he fails a 6-hour observation period",
                  "Only if D-dimer exceeds 4,000 ng/mL"
                ],
                "question": "Does Carlos require hospitalization?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              },
              {
                "id": "d3",
                "type": "multiple-choice",
                "matchId": "d3",
                "options": [
                  "DOAC (e.g., rivaroxaban or apixaban)",
                  "Warfarin with heparin bridge",
                  "Unfractionated heparin drip alone",
                  "Aspirin plus clopidogrel"
                ],
                "question": "What is the preferred anticoagulant class for Carlos?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              },
              {
                "id": "d4",
                "type": "multiple-choice",
                "matchId": "d4",
                "options": [
                  "Yes — Class 1 recommendation for complex PE with RV dysfunction",
                  "No — PERT is only for Category E with cardiac arrest",
                  "Only after 48 hours of failed anticoagulation",
                  "Only if systemic thrombolysis is the plan"
                ],
                "question": "Should you activate PERT for Carlos?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              }
            ]
          }
        ]
      },
      {
        "id": "phase-teach",
        "title": "What the Guideline Says",
        "type": "content",
        "required": true,
        "order": 2,
        "components": [
          {
            "id": "phase-teach-avatar",
            "type": "talking-avatar",
            "title": "Faculty walkthrough: classifying Carlos",
            "video_status": "ready",
            "video_url": "https://example-signed-media.invalid/avatar/EXAMPLE.mp4?token=PLACEHOLDER_SIGNED_URL",
            "expires_at": "2026-08-18T04:39:00.000Z",
            "poster_url": "https://example-signed-media.invalid/avatar/EXAMPLE-poster.jpg",
            "duration_seconds": 96
          },
          {
            "id": "phase-teach-html",
            "type": "html",
            "content": "<h3>Decision 1 — Classify PE severity: Category C</h3><p>The 2026 AHA/ACC guideline introduces a <strong>five-category clinical classification</strong> that replaces the legacy massive/submassive/low-risk model [1]:</p><ul><li><strong>Category A</strong> — Asymptomatic (incidental PE): normal hemodynamics, no symptoms, negative biomarkers.</li><li><strong>Category B</strong> — Low risk: symptomatic but normal RV function, negative biomarkers.</li><li><strong>Category C</strong> — Elevated biomarkers and/or RV dysfunction <em>without</em> hemodynamic compromise.</li><li><strong>Category D</strong> — Incipient cardiopulmonary failure: hemodynamic instability not yet refractory.</li><li><strong>Category E</strong> — Cardiopulmonary failure with persistent hypotension or cardiac arrest.</li></ul><p>Carlos has elevated troponin, RV dilation (RV:LV >0.9), and stable blood pressure — he is <strong>Category C</strong> [1].</p><h3>Decision 2 — Does Carlos need hospitalization? Yes</h3><p>Category C patients require inpatient monitoring because they carry an elevated short-term risk of hemodynamic deterioration. The 2026 guideline recommends hospitalization with serial reassessment for all Category C, D, and E patients [1]. Only Category A (and select Category B) patients may be considered for early or home discharge.</p><h3>Decision 3 — Anticoagulate with a DOAC</h3><p>The 2026 guideline recommends <strong>DOACs over vitamin K antagonists</strong> for acute PE in patients without contraindications (active cancer requiring LMWH, antiphospholipid syndrome, severe renal impairment CrCl <15 mL/min) [1]. Carlos has no contraindication — start a DOAC today. Rivaroxaban (15 mg BID × 21 days, then 20 mg daily) and apixaban (10 mg BID × 7 days, then 5 mg BID) do not require heparin bridging [1].</p><h3>Decision 4 — Activate PERT for Category C with RV dysfunction</h3><p>The 2026 guideline elevates the <strong>Pulmonary Embolism Response Team (PERT) to a Class 1 recommendation</strong> for complex PE [1]. Carlos’s RV dilation and elevated troponin place him at risk of deterioration. Activating PERT brings pulmonology, interventional radiology, cardiology, and critical care to the table in real time to determine whether anticoagulation alone is sufficient or whether catheter-directed therapy should be prepared [1,3]. Over 100 medical centers participate in the National PERT Consortium, yet most U.S. hospitals still lack a formal PERT [3].</p>"
          },
          {
            "id": "phase-teach-aivideo",
            "type": "ai-video",
            "title": "Case animation (still rendering)",
            "video_status": "pending"
          }
        ]
      },
      {
        "id": "phase-check",
        "title": "Confirm Your Knowledge",
        "type": "post-assessment",
        "required": true,
        "order": 3,
        "debrief_readout": {
          "title": "Before vs. after — your matched item",
          "confidenceKeys": {
            "baselineId": "confidence-baseline"
          },
          "matches": [
            {
              "matchId": "m1",
              "label": "Which category describes elevated biomarkers with stable hemodynamics?"
            }
          ]
        },
        "components": [
          {
            "id": "phase-check-mcq",
            "type": "mcq",
            "questions": [
              {
                "id": "posttest-m1",
                "type": "multiple-choice",
                "matchId": "m1",
                "options": [
                  "Category C — elevated biomarkers/RV dysfunction, no hemodynamic failure",
                  "Category B — low risk, symptomatic",
                  "Submassive PE (legacy terminology)",
                  "Category D — incipient cardiopulmonary failure"
                ],
                "question": "Carlos, 55, acute PE, troponin elevated, RV:LV >0.9, BP 118/74. How do you classify his PE under the 2026 guideline?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              },
              {
                "id": "posttest-d2",
                "type": "multiple-choice",
                "matchId": "d2",
                "options": [
                  "Yes — Category C requires inpatient monitoring",
                  "No — he is hemodynamically stable and can go home",
                  "Only if he fails a 6-hour observation period",
                  "Only if D-dimer exceeds 4,000 ng/mL"
                ],
                "question": "Does Carlos require hospitalization?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              },
              {
                "id": "posttest-d3",
                "type": "multiple-choice",
                "matchId": "d3",
                "options": [
                  "DOAC (e.g., rivaroxaban or apixaban)",
                  "Warfarin with heparin bridge",
                  "Unfractionated heparin drip alone",
                  "Aspirin plus clopidogrel"
                ],
                "question": "What is the preferred anticoagulant class for Carlos?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              },
              {
                "id": "posttest-d4",
                "type": "multiple-choice",
                "matchId": "d4",
                "options": [
                  "Yes — Class 1 recommendation for complex PE with RV dysfunction",
                  "No — PERT is only for Category E with cardiac arrest",
                  "Only after 48 hours of failed anticoagulation",
                  "Only if systemic thrombolysis is the plan"
                ],
                "question": "Should you activate PERT for Carlos?",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "deliveryStyle": "embedded",
                "outcomeMetric": "knowledge"
              },
              {
                "id": "m1",
                "type": "multiple-choice",
                "matchId": "m1",
                "options": [
                  "Category B — low risk",
                  "Category C — elevated biomarkers/RV dysfunction without hemodynamic failure",
                  "Category D — incipient cardiopulmonary failure",
                  "Category E — cardiopulmonary failure"
                ],
                "question": "A 60-year-old presents with acute PE: troponin elevated, echo shows RV:LV ratio 1.0, BP 122/78. Which 2026 classification applies?",
                "badgeLabel": "MATCHED TO DECISION 1",
                "difficulty": "intermediate",
                "mooreLevel": 3,
                "outcomeMetric": "knowledge"
              }
            ]
          }
        ]
      },
      {
        "id": "phase-commit",
        "title": "Commit to Change",
        "type": "evaluation",
        "required": true,
        "order": 4,
        "components": [
          {
            "id": "phase-commit-evaluation",
            "type": "evaluation",
            "questions": [
              {
                "id": "eval-overall-quality",
                "type": "single-select",
                "options": [
                  "Excellent",
                  "Very Good",
                  "Good",
                  "Fair",
                  "Poor"
                ],
                "category": "satisfaction",
                "question": "The activity was:",
                "required": true,
                "mooreLevel": 1,
                "outcomeMetric": "satisfaction",
                "response_type": "single-select",
                "response_options": [
                  "Excellent",
                  "Very Good",
                  "Good",
                  "Fair",
                  "Poor"
                ],
                "scale": null
              },
              {
                "id": "eval-fair-balanced",
                "type": "yes-no",
                "category": "bias",
                "question": "The activity was fair and balanced:",
                "required": true,
                "mooreLevel": 1,
                "outcomeMetric": "bias",
                "response_type": "yes-no",
                "response_options": null,
                "scale": null
              },
              {
                "id": "eval-evidence-based",
                "type": "yes-no",
                "category": "evidence",
                "question": "The activity content was evidence based:",
                "required": true,
                "mooreLevel": 1,
                "outcomeMetric": "evidence",
                "response_type": "yes-no",
                "response_options": null,
                "scale": null
              },
              {
                "id": "eval-disclosures-shown",
                "type": "yes-no",
                "category": "disclosure",
                "question": "The author’s relevant financial relationships (or lack thereof) were disclosed:",
                "required": true,
                "mooreLevel": 1,
                "outcomeMetric": "disclosure",
                "response_type": "yes-no",
                "response_options": null,
                "scale": null
              },
              {
                "id": "eval-confidence-post",
                "max": 5,
                "min": 1,
                "step": 1,
                "type": "slider",
                "category": "confidence",
                "maxLabel": "Very confident",
                "minLabel": "Not at all confident",
                "question": "After this activity, how confident are you in classifying acute PE using the 2026 five-category system?",
                "required": true,
                "mooreLevel": 2,
                "outcomeMetric": "confidence",
                "response_type": "slider",
                "response_options": null,
                "scale": null
              },
              {
                "id": "commit-to-change",
                "type": "multi-select",
                "options": [
                  "Apply the A–E classification instead of massive/submassive/low-risk",
                  "Hospitalize all Category C patients with serial reassessment",
                  "Start a DOAC as first-line anticoagulation for eligible PE patients",
                  "Activate PERT for Category C patients with RV dysfunction",
                  "Establish or join a PERT at my institution",
                  "Other"
                ],
                "category": "commitment-to-change",
                "question": "Commit to change — select every action you will take for the next patient like Carlos (choose one or more):",
                "required": true,
                "mooreLevel": 3,
                "outcomeMetric": "intent",
                "response_type": "multi-select",
                "response_options": [
                  "Apply the A–E classification instead of massive/submassive/low-risk",
                  "Hospitalize all Category C patients with serial reassessment",
                  "Start a DOAC as first-line anticoagulation for eligible PE patients",
                  "Activate PERT for Category C patients with RV dysfunction",
                  "Establish or join a PERT at my institution",
                  "Other"
                ],
                "scale": null
              }
            ]
          }
        ]
      }
    ],
    "debrief": {
      "caseTakeaway": "<p>Carlos’s PE is not ‘submassive’ anymore — it’s <strong>Category C</strong>. The 2026 AHA/ACC five-category system (A–E) gives every PE a letter that maps directly to disposition, anticoagulation, and escalation. Carlos’s elevated troponin and RV dilation without hemodynamic compromise put him squarely in Category C: admit, start a DOAC, and activate PERT to decide whether anticoagulation alone is enough or catheter-directed therapy should be on standby [1].</p><h3>What to do differently for your next patient like Carlos</h3><ul><li><strong>Classify, don’t label.</strong> Replace ‘massive/submassive/low-risk’ with Categories A–E at the point of diagnosis.</li><li><strong>Hospitalize C, D, and E.</strong> Only Category A (and select Category B) patients may be considered for home or early discharge.</li><li><strong>Start DOAC first line</strong> unless the patient has active cancer, antiphospholipid syndrome, or CrCl <15 mL/min.</li><li><strong>Activate PERT early</strong> for Category C with RV dysfunction, and for all Category D and E patients.</li></ul><h3>When to escalate</h3><p>If Carlos deteriorates — falling BP, rising lactate, worsening RV function — he reclassifies to Category D. PERT should already be at the table to pivot to catheter-directed therapy or systemic thrombolysis.</p>",
      "answerKeyHtml": "",
      "nextStepsHtml": ""
    },
    "references": [
      {
        "doi": "10.1161/CIR.0000000000001415",
        "year": 2026,
        "title": "2026 AHA/ACC/ACCP/ACEP/CHEST/SCAI/SHM/SIR/SVM/SVN Guideline for the Evaluation and Management of Acute Pulmonary Embolism in Adults",
        "number": "1",
        "authors": [
          "Defined Authorship Committee"
        ],
        "journal": "Circulation"
      },
      {
        "doi": "10.1093/eurheartj/ehz405",
        "year": 2020,
        "title": "2019 ESC Guidelines for the Diagnosis and Management of Acute Pulmonary Embolism",
        "number": "2",
        "authors": [
          "Konstantinides SV",
          "Meyer G",
          "Becattini C",
          "et al."
        ],
        "journal": "Eur Heart J"
      },
      {
        "url": "https://pertconsortium.org",
        "year": 2025,
        "title": "Pulmonary Embolism Response Team Database: 12,346 Patients Across 35 Institutions, 2015–2024",
        "number": "3",
        "authors": [
          "National PERT Consortium"
        ],
        "journal": "pertconsortium.org"
      },
      {
        "doi": "10.3389/fmed.2025.1708409",
        "year": 2025,
        "title": "Catheter-Directed Therapy for Acute Pulmonary Embolism: Evidence, Gaps, and Future Directions",
        "number": "4",
        "authors": [
          "Bikdeli B",
          "Mazzeffi MA",
          "Engstrom BI",
          "et al."
        ],
        "journal": "Front Med"
      },
      {
        "doi": "10.1016/S2213-2600(23)00093-2",
        "year": 2023,
        "title": "Chronic Thromboembolic Pulmonary Hypertension and Post-PE Syndrome",
        "number": "5",
        "authors": [
          "Klok FA",
          "Barco S",
          "Konstantinides SV"
        ],
        "journal": "Lancet Respir Med"
      }
    ],
    "series": {
      "series_id": "bramwell-2026-acute-pe-management-series-41953f",
      "name": "2026 Acute PE Management Series",
      "description": "Five 15-minute Bramwell Method microlearnings translating the landmark 2026 AHA/ACC Pulmonary Embolism Guideline — the new five-category clinical classification, safe ED discharge for asymptomatic PE, DOAC selection and dosing, the Class 1 PERT recommendation, and structured post-PE follow-up for chronic thromboembolic disease — into point-of-care decisions. Designed for emergency physicians, hospitalists, pulmonologists, NPs, PAs, and pharmacists who diagnose and manage acute PE.",
      "position": 1,
      "total": 5,
      "modules": [
        {
          "course_id": "00000000-0000-4000-8000-000000000001",
          "title": "The New PE Classification: A to E",
          "status": "available",
          "position": 1
        },
        {
          "course_id": "00000000-0000-4000-8000-000000000002",
          "title": "Category A PE: Home from the ED",
          "status": "available",
          "position": 2
        },
        {
          "course_id": "00000000-0000-4000-8000-000000000003",
          "title": "DOACs: Choosing and Dosing for Acute PE",
          "status": "available",
          "position": 3
        },
        {
          "course_id": "00000000-0000-4000-8000-000000000004",
          "title": "PERT Class 1: Building and Using Your PE Response Team",
          "status": "available",
          "position": 4
        },
        {
          "course_id": "00000000-0000-4000-8000-000000000005",
          "title": "Post-PE Syndrome: The Follow-Up Nobody Is Doing",
          "status": "available",
          "position": 5
        }
      ]
    },
    "media_expires_at": "2026-08-18T04:39:00.000Z"
  },
  "error": null
}

Render contract

revision 2026-09-03

IQ Apparatus owns the render contract. Every response carries contract_version in the envelope, and course payloads repeat it inside data. Changes are additive only: fields are never removed or retyped within a revision. Ignore unknown fields, render unknown component and phase types as a notice rather than dropping them, and treat an unrecognized revision as newer than yours.

Contract module (latest)

Zero-import TypeScript. Vendor it directly — no dependency on our package.

/contract/iq-render-contract.ts
Contract module (pinned 2026-09-03)

Immutable revision. Pin this if you want to opt into upgrades deliberately.

/contract/iq-render-contract-2026-09-03.ts
JSON Schema

For non-TypeScript consumers and CI validation of stored payloads.

/contract/iq-render-contract.schema.json
Golden fixtures

Catalog, full Bramwell series module, pending/failed media, unknown-revision probe.

/contract/fixtures/index.json
Contract changelog

Every revision, additive-only policy, and what changed.

/contract/CHANGELOG.md

Sequential gating is yours

series.sequential_gating_enforced is always false. The API will serve module N even if N−1 is incomplete. Use series.next / series.previous plus your own completion records to gate the roadmap.

Bramwell rendering spec & themev1

When data.format === "bramwell", the payload carries the Bramwell structure — learning_objectives, accreditation, landing_page, evaluation_questions, confidence_baseline, and per-phase debrief_readout. Pair it with the spec and the namespaced theme below to reproduce the IQ Apparatus experience one-for-one.

Phase order

  1. 1Hook / case opening — Title, style chip, opening content, avatar video
  2. 2Pre-assessment — Pre-test questions plus the confidence baseline slider
  3. 3Decision path — Content, decision cards, reflection, image hotspots
  4. 4Post-assessment — Takeaway card renders above per-question review
  5. 5Evaluation — The ACCME items verbatim, incl. commit-to-change

Component contract & fallbacks

TypeRequired renderingAccepted fallback
talking-avatar / ai-videoVideo with poster + captions; pending state when video_status !== "ready"Poster + transcript
choice (decision card)Card list, single select, no rationale before submissionRadio group
confidence-checkSlider min→max with end labelsRadio scale, same values
image-hotspotImage with clickable hotspot regionsStatic image + list of hotspot labels
card-flipFlip cardsTerm / definition list
drag-drop-matchDrag sources onto targetsPer-source dropdown of targets

Every fallback must post the same answer shape to POST /api-submit-progress — outcomes reporting depends on answer shape, not on widget fidelity.

Your app owns
  • Phase progression and step navigation (from required + order)
  • Locking a phase until the previous one is complete
  • Holding in-progress answers between submissions
The server owns
  • Answer keys, rationale and feedback (returned by api-submit-progress only)
  • Score and pass/fail
  • Completion, credit and certificate eligibility
Media freshness. Video URLs are short-lived signed URLs. Each video block carries its own expires_at, and the payload carries media_expires_at. Never cache or static-render a video_url past that time — re-fetch the course right before playback.
CSS safety. The theme declares nothing on :root; every token and recipe is scoped under .iq-bramwell-theme, so it cannot leak into your layout.

Changelog

addedRender contract published (revision 2026-08-19)2026-08-19
  • •Every response now carries contract_version in the envelope, and course payloads repeat it inside data. The canonical zero-import module is published at /contract/iq-render-contract.ts (pinned: /contract/iq-render-contract-2026-08-19.ts) with a JSON Schema alongside it.
  • •GET /api-courses now returns organization_id and accredited_provider_name on every catalog item, so a consumer can verify each item belongs to its key’s organization.
  • •GET /api-course-content now returns organization_id, credit_designations, available_certificate_types, and activity_details. Credit data is authoritative and structured — never parse the prose accreditation statement.
  • •Video blocks now always carry fallback_html for pending, failed, or expired media, and media_expires_at is emitted whenever the payload contains any expiring signed URL.
  • •series now includes next, previous, and sequential_gating_enforced (always false — the API never refuses module N because N−1 is incomplete; enforce ordering from your own completion records).
  • •Golden fixtures at /contract/fixtures/index.json (catalog, full Bramwell series module, pending/failed media, unknown-future-revision probe) and a machine-readable changelog at /contract/CHANGELOG.md.
addedBramwell fidelity fields + published rendering spec2026-08-19
  • •GET /api-course-content now returns format ("bramwell" | null) and delivery_style so clients can branch to a Bramwell renderer.
  • •New additive fields: learning_objectives[], accreditation (credit type/hours, provider statement, release + expiration dates), landing_page, evaluation_questions[] (the required ACCME items), and confidence_baseline.
  • •phases[].debrief_readout returns the matched pre/post items for the knowledge-lift card. Answer indexes are stripped — correctness still comes only from POST /api-submit-progress.
  • •Each resolved video block now carries its own expires_at alongside the payload-level media_expires_at. Never cache or static-render video_url past that timestamp.
  • •Published rendering spec (/bramwell-rendering-spec.md) and namespaced stylesheet (/bramwell-theme.css). All tokens are scoped under .iq-bramwell-theme — nothing is declared on :root.
  • •The spec documents state ownership (host owns phase gating and navigation; the server owns answer keys, scoring, completion and credit) and a documented fallback for every interactive widget.
changedAssessment rationales removed from course content responses2026-08-18
  • •GET /api-course-content no longer returns rationale, rationaleHtml, explanationHtml, feedback, correctFeedback, or incorrectFeedback on assessment questions — these revealed the correct answer before submission.
  • •Rationales are still returned per question by POST /api-submit-progress after the learner answers. Render feedback from that response rather than from the course payload.
addedMicrolearning series support in GET /api-course-content2026-08-17
  • •Avatar and AI video components ("talking-avatar", "ai-video") now return playable media: video_status ("ready" | "pending"), video_url, poster_url, and duration_seconds. Internal render fields (jobId, script, prompt, shots, avatarId, voiceId) are no longer returned.
  • •data.media_expires_at — ISO timestamp, present only when the payload contains signed media URLs. Video URLs expire ~6 hours after the request; re-fetch the course to mint fresh URLs instead of caching video_url past this time.
  • •data.debrief — post-test takeaway card: { caseTakeaway, answerKeyHtml, nextStepsHtml }. Null for activities without a debrief.
  • •data.references — structured citations backing the [N] markers in content: { number, title, authors[], journal, year, doi, pmid, url }.
  • •data.series — series context for microlearning modules: { series_id, name, description, position, total, modules[{ course_id, title, status, position }] }. Null for standalone activities.
  • •Answer keys are now stripped server-side for newer block types (image-hotspot, reflection, confidence-check, choice), so those components can be rendered as delivered.

Developer Resources