Course endpoints sit under /api/brands/{brand}/courses/ with brandAccess middleware. Authenticate with EF-Access-Key.
List Courses
GET /api/brands/{brand}/courses
Results per page (1–100, default: 25)
Search by title, description, slug, or instructor name
newest, oldest, created_at, updated_at
curl https://app.elasticfunnels.io/api/brands/{brand_id}/courses \
-H "EF-Access-Key: your_api_key_here"
{
"current_page": 1,
"data": [
{
"id": 1,
"title": "Gut Health Masterclass",
"slug": "gut-health",
"status": "published",
"delivery_type": "module",
"language_code": "en",
"instructor_name": "Dr. Jane Smith",
"domain_id": 7,
"modules_count": 6,
"enrollments_count": 124
}
],
"per_page": 25,
"total": 3,
"last_page": 1
}
List Courses (Unpaginated)
GET /api/brands/{brand}/courses/all
Returns { id, title } for all courses — useful for dropdowns.
Get Course
GET /api/brands/{brand}/courses/{course}
{
"id": 1,
"title": "Gut Health Masterclass",
"slug": "gut-health",
"status": "published",
"delivery_type": "module",
"language_code": "en",
"description": "Learn how to improve your gut health...",
"instructor_name": "Dr. Jane Smith",
"instructor_photo": "https://cdn.elasticfunnels.io/42/assets/media/instructor.jpg",
"logo": null,
"cover": "https://cdn.elasticfunnels.io/42/assets/media/cover.jpg",
"domain_id": 7,
"domain": { "id": 7, "domain": "courses.mybrand.com" },
"show_module_titles": true,
"allow_comments": false,
"details_page_id": null,
"module_page_id": 101,
"content_page_id": 102,
"enrollment_email_template_id": null,
"enrollments_count": 124
}
Create Course
POST /api/brands/{brand}/courses
Language code (e.g. en, pt)
embedded (content lives inline on a page) or module (dedicated course pages on a domain)
Required when delivery_type is module. Must be an existing brand domain.
Required when delivery_type is module. Must be unique per brand_id + domain_id.
Instructor display name (max 255)
Instructor photo — upload as file (multipart) or provide as URL string
Course logo — upload as file or URL string
Cover image — upload as file or URL string
Show module titles in the course outline
Enable commenting on content
Course category name (max 255)
Page ID for the course details/landing page
Page ID for module listing
Page ID for content viewing
enrollment_email_template_id
Email template ID to send upon enrollment
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/courses \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Gut Health Masterclass",
"language_code": "en",
"description": "Learn how to improve your gut health naturally.",
"status": "draft",
"delivery_type": "module",
"domain_id": 7,
"slug": "gut-health",
"instructor_name": "Dr. Jane Smith"
}'
{
"course": {
"id": 1,
"title": "Gut Health Masterclass",
"slug": "gut-health",
"status": "draft",
"brand_id": 42,
"created_at": "2024-12-11T10:00:00.000000Z"
}
}
Update Course
PUT /api/brands/{brand}/courses/{course}
Same body as Create. title, language_code, description, status, and delivery_type are required.
Delete Course
DELETE /api/brands/{brand}/courses/{course}
Copy Course to Another Brand
POST /api/brands/{brand}/courses/copy
Title for the copied course in the target brand
Enrollments
List Enrollments
GET /api/brands/{brand}/courses/{course}/enrollments
{
"data": [
{
"id": "es-enroll-abc123",
"email": "student@example.com",
"name": "Jane Student",
"user_id": 99,
"notes": null,
"created_at": "2024-12-01T00:00:00Z",
"completed_content_ids": [10, 11],
"progress_percent": 33,
"last_content_id": 11,
"last_module_id": 2
}
]
}
Get Enrollment
GET /api/brands/{brand}/courses/{course}/enrollments/{enrollment}
Elasticsearch document ID for the enrollment
{
"data": { /* same enrollment object */ }
}
Enroll Student
POST /api/brands/{brand}/courses/{course}/enrollments
Student email address. Returns 422 if already enrolled.
Student display name (max 255)
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/courses/1/enrollments \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"email": "student@example.com", "name": "Jane Student"}'
{
"data": {
"id": "es-enroll-abc123",
"email": "student@example.com",
"name": "Jane Student",
"created_at": "2024-12-11T10:00:00Z"
}
}
Update Enrollment Notes
PUT /api/brands/{brand}/courses/{course}/enrollments/{enrollment}
Internal notes about the enrollment (max 10,000 chars)
Complete Content Item
Mark a specific content item as completed for an enrolled student.
POST /api/brands/{brand}/courses/{course}/enrollments/{enrollment}/complete
The course content item ID to mark as complete
{
"data": {
"completed_content_ids": [10, 11, 12],
"progress_percent": 50,
"last_content_id": 12
}
}
This endpoint is idempotent — marking an already-completed item again does not create duplicates.
Remove Enrollment
DELETE /api/brands/{brand}/courses/{course}/enrollments/{enrollment}
Modules
List Modules
GET /api/brands/{brand}/courses/{course}/modules
{
"data": [
{
"id": 3,
"title": "Module 1: Introduction",
"description": "Overview of gut health fundamentals.",
"order": 1,
"contents": [
{ "id": 10, "title": "What is Gut Flora?", "type": "video", "order": 1 },
{ "id": 11, "title": "The Gut-Brain Axis", "type": "text", "order": 2 }
]
}
]
}
List Modules (Unpaginated)
GET /api/brands/{brand}/courses/{course}/modules/all
Returns { id, title } array — cached.
Create Module
POST /api/brands/{brand}/courses/{course}/modules
Update Module
PUT /api/brands/{brand}/courses/{course}/modules/{module}
Delete Module
DELETE /api/brands/{brand}/courses/{course}/modules/{module}
Reorder Modules
POST /api/brands/{brand}/courses/{course}/modules/order
Map of { "<module_id>": <integer order>, ... }
Notes
- Enrollment IDs are Elasticsearch document IDs (strings), not numeric database IDs
- File uploads (
logo, cover, instructor_photo) are stored on BunnyCDN — you can provide a URL string instead of uploading a file
- The
copy endpoint replicates the course’s modules and all content to the target brand
progress_percent is recalculated automatically when content is marked complete