Create Courses
Creates one or more courses (up to 100 courses per request, with a maximum of 25 children per course). Children include sections, lessons, and topics. The endpoint automatically creates the full course hierarchy including the course group, course (session), sections, lessons, and topics based on the `kind` specified.
Creates one or more courses (up to 100 courses per request, with a maximum of 25 children per course). Children include sections, lessons, and topics. The endpoint automatically creates the full course hierarchy including the course group, course (session), sections, lessons, and topics based on the kind specified.
Authentication & Permissions
Authenticate with Authorization: Bearer YOUR_API_KEY. This endpoint requires the courses.new permission. This route is not rate limited.
Course Attributes
| Property | Type | Required? | Description |
|---|---|---|---|
title | string | yes | Title of the course |
kind | string | yes | Course type: courseGroup, microCourse, article, video, shareableContentObject (5 kinds). xApiObject is not a valid course kind — it is only a topic type |
sku | string | no | Unique SKU identifier |
isbn | string | no | ISBN number |
discussionsEnabled | boolean | no | Enable course discussions |
availableToPublic | boolean | no | Make course available without login |
enrollmentLimit | integer | no | Maximum number of enrollments (enables seat limits) |
enrollmentStartDate | date | no | When enrollment opens (ISO 8601) |
enrollmentEndDate | date | no | When enrollment closes (ISO 8601) |
courseStartDate | date | no | When the course starts (ISO 8601) |
courseEndDate | date | no | When the course ends (ISO 8601) |
gracePeriodEndDate | date | no | Grace period end date (ISO 8601) |
sections | array | no | Array of section objects (for courseGroup kind) |
topics | array | no | Array of topic objects (for microCourse kind) |
articleVariant | object | no | Article content (for article kind) |
width | integer | no | Display width for SCORM/xAPI (pixels) |
height | integer | no | Display height for SCORM/xAPI (pixels) |
embeddedEnabled | boolean | no | Enable embedded display for SCORM/xAPI |
resetSessionAfterComplete | boolean | no | Reset SCORM session after completion |
scormUrl | URL | no | URL to SCORM/xAPI package (for standalone SCORM courses) |
videoUrl | URL | no | External video URL for video kind — downloaded and uploaded to Wistia by a background job. Set at the course (top) level, not in topics |
videoAsset | ID | no | Existing Wistia media ID or Synthesia UUID for video kind. Mutually exclusive with videoUrl. Course-level |
assetType | string | no | Video provider for video kind: wistia (default) or synthesia. Required for Synthesia. Course-level |
preAsset | ID | no | Wistia media ID for pre-roll video (video kind). Course-level |
postAsset | ID | no | Wistia media ID for post-roll video (video kind). Course-level |
posterImageAsset | URL | no | Poster image URL shown before the video plays (video kind). Course-level |
Catalog & Session Attributes (live in production):
CourseGroup-Level (Catalog) Attributes:
| Property | Type | Description |
|---|---|---|
description | string | Course description for catalog and detail pages (max 5,000 characters) |
tagIds | array | Array of tag UUIDs for categorization (max 10). Tags must already exist on the company. Use GET /tags to list available IDs |
customFields | JSON | Custom field values as {"slug": "value" or ["v1", "v2"]}. Replaces the entire object on update |
asset | URL | Catalog thumbnail image URL (recommended: 800x385px, PNG/JPEG/GIF). Downloaded and uploaded to S3 |
assetAltText | string | Alt text for catalog image. Defaults to empty string when asset is provided |
detailAsset | URL | Detail page image URL (recommended: 800x450px, PNG/JPEG/GIF). Downloaded and uploaded to S3 |
detailAssetAltText | string | Alt text for detail image. Defaults to empty string when detailAsset is provided |
ribbon | string | Ribbon badge slug (e.g., new, popular). Must exist in the company's ribbon configuration |
metaTitle | string | SEO meta title (max 200 characters, HTML stripped) |
metaDescription | string | SEO meta description (max 500 characters, HTML stripped) |
contentType | string | Content type label (e.g., "Training Module"). Auto-derived from kind if omitted; must exist in the company's configuration |
relatedCourseGroupIds | array | Related catalog course group UUIDs. Must exist on the company |
Course-Level (Session) Attributes:
| Property | Type | Description |
|---|---|---|
priceInCents | integer | Price in cents (e.g., 9900 = $99.00). Max: 99,999,999. Cannot be negative |
freeWithRegistration | boolean | Free access for all registered users (default: false) |
purchasable | boolean | Enable purchase functionality (default: false) |
status | string | draft or published; omit to default to draft. With futurePublishDate, status becomes pending for scheduled release |
futurePublishDate | date | Auto-publish the session to the catalog at this date. Implies pending unless status is published |
publishDate | date | Course group catalog date used for search/display ordering |
sessionCustomFields | JSON | Session-level custom field values. Replaces the entire object on update |
forceLinearProgress | boolean | Force learners to complete pages in order (default: false) |
showProgress | boolean | Show progress indicator to learners (default: true) |
prerequisiteCourseIds | array | Prerequisite course UUIDs. Must exist on the company |
prerequisiteLearningPathIds | array | Prerequisite learning path UUIDs. Must exist on the company |
Section Object
| Property | Type | Required? | Description |
|---|---|---|---|
title | string | yes | Section title |
releaseDate | date | no | When section is released |
lessons | array | yes | Array of lesson objects |
Lesson Object
| Property | Type | Required? | Description |
|---|---|---|---|
title | string | yes | Lesson title |
openType | string | yes | Access level: studentsOnly, open, emailCaptureOpen |
topics | array | yes | Array of topic objects |
Topic Object
| Property | Type | Required? | Description |
|---|---|---|---|
title | string | yes | Topic title |
type | string | yes | Topic type (see Supported Topic Types below) |
body | string | no | HTML content (for text/article topics) |
languages | array | no | Multi-language content variants |
preTextBlock | string | no | Text displayed before topic content |
postTextBlock | string | no | Text displayed after topic content |
width | integer | no | Display width (pixels) |
height | integer | no | Display height (pixels) |
embeddedEnabled | boolean | no | Enable embedded display |
fullscreenEmbed | boolean | no | Enable fullscreen embed |
preventProgression | boolean | no | Prevent learner from advancing until complete |
resetSessionAfterComplete | boolean | no | Reset session after completion (SCORM) |
caption | string | no | Caption text |
searchDisabled | boolean | no | Exclude from search |
printDisabled | boolean | no | Disable printing |
fileDownloadDisabled | boolean | no | Disable file download |
scormUrl | URL | no | URL to SCORM/xAPI package (for shareableContentObject/xApiObject) |
scoTitle | string | no | SCORM title |
objectType | string | no | SCORM standard type |
courseCompletionCriteria | object | no | Completion criteria for the topic |
pdfUrl | URL | no | URL to PDF file (for pdfViewer type) |
videoUrl | URL | no | External URL to a video file (for video type) — uploaded to Wistia by a background job |
videoAsset | ID | no | Existing Wistia media ID or Synthesia UUID (for video type). Mutually exclusive with videoUrl |
assetType | string | no | Video provider: wistia (default) or synthesia. Required when videoAsset is a Synthesia ID |
preAsset | ID | no | Wistia media ID for pre-roll video |
postAsset | ID | no | Wistia media ID for post-roll video |
posterImageAsset | URL | no | Poster image URL displayed before the video plays |
audioUrl | URL | no | Coming soon - URL to audio file (for audio type) |
Supported Topic Types
Fully Supported (content can be created via API):
| Type | Description |
|---|---|
text | Rich text content (use body field) |
article | Article page (use body or languages field) |
video | Video page (use videoUrl to upload, or videoAsset to reference existing) |
shareableContentObject | SCORM content (use scormUrl field) |
xApiObject | xAPI content (use scormUrl field) |
pdfViewer | PDF document (use pdfUrl field) |
embed | Embedded content (use body with iframe) |
discussionBoard | Discussion forum |
Shell Only (creates placeholder, content must be added via UI):
| Type | Description |
|---|---|
quiz | Quiz - questions must be added via UI |
test | Assessment - questions must be added via UI |
survey | Survey - questions must be added via UI |
assignment | Assignment - configuration required via UI |
audio | Audio page - coming soon via API |
image | Image gallery - configuration via UI |
slideshow | Slideshow - configuration via UI |
presentation | Presentation - configuration via UI |
meetingInfo | Meeting information - linked to calendar event |
POST https://example.thoughtindustries.com/incoming/v2/content/course/createExample request - Standard Course
curl -X "POST" "https://example.thoughtindustries.com/incoming/v2/content/course/create" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"courseAttributes": [
{
"title": "Introduction to Marketing",
"kind": "courseGroup",
"sku": "MKT-101",
"discussionsEnabled": true,
"sections": [
{
"title": "Getting Started",
"lessons": [
{
"title": "Welcome",
"openType": "studentsOnly",
"topics": [
{
"title": "Course Overview",
"type": "text",
"body": "<p>Welcome to the course!</p>"
},
{
"title": "Embedded Media",
"type": "embed",
"body": "<iframe src=\"https://player.vimeo.com/video/123456\" width=\"640\" height=\"360\"></iframe>"
}
]
}
]
},
{
"title": "Core Concepts",
"lessons": [
{
"title": "Marketing Fundamentals",
"openType": "studentsOnly",
"topics": [
{
"title": "The 4 Ps of Marketing",
"type": "article",
"body": "<h2>Product, Price, Place, Promotion</h2><p>...</p>"
},
{
"title": "Knowledge Check",
"type": "quiz"
}
]
}
]
}
]
}
]
}'Example response - Standard Course
The response is always an object containing the created course and course group IDs:
{
"courseIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"],
"courseGroupIds": ["b2c3d4e5-f6a7-8901-bcde-f23456789012"]
}Example request - Micro Course
curl -X "POST" "https://example.thoughtindustries.com/incoming/v2/content/course/create" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"courseAttributes": [
{
"title": "Quick Tips: Email Etiquette",
"kind": "microCourse",
"topics": [
{
"title": "Subject Lines",
"type": "text",
"body": "<p>Keep subject lines clear and concise.</p>"
},
{
"title": "Professional Tone",
"type": "text",
"body": "<p>Maintain a professional tone in all communications.</p>"
},
{
"title": "Quiz",
"type": "quiz"
}
]
}
]
}'Example request - Standalone SCORM Course
curl -X "POST" "https://example.thoughtindustries.com/incoming/v2/content/course/create" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"courseAttributes": [
{
"title": "Compliance Training Module",
"kind": "shareableContentObject",
"sku": "COMP-2024",
"width": 1024,
"height": 768,
"embeddedEnabled": true,
"scormUrl": "https://example.com/scorm-packages/compliance-2024.zip"
}
]
}'Example response - SCORM Course with Background Job
When uploading SCORM content, the response includes background job information:
{
"courseIds": ["b2c3d4e5-f6a7-8901-bcde-f23456789012"],
"courseGroupIds": ["c3d4e5f6-a7b8-9012-cdef-345678901234"],
"backgroundJob": {
"id": "job-uuid-here",
"type": "contentBulkUpload",
"status": "queued",
"contentUploads": [
{
"topicId": "topic-uuid-here",
"courseId": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"contentType": "scorm"
}
]
}
}Example request - Article
curl -X "POST" "https://example.thoughtindustries.com/incoming/v2/content/course/create" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"courseAttributes": [
{
"title": "Best Practices for Remote Work",
"kind": "article",
"articleVariant": {
"body": "<h1>Working from Home</h1><p>Here are the best practices...</p>"
}
}
]
}'Example request - Standalone Video Course (upload from URL)
For kind: "video", the video fields (videoUrl, videoAsset, assetType, preAsset, postAsset, posterImageAsset) are set at the course (top) level — not inside a topics array. videoUrl and videoAsset are mutually exclusive, and assetType cannot be combined with videoUrl; violations return a 400 error. The platform downloads the file at videoUrl and uploads it to Wistia in a background job. Poll /v2/jobs/{id} to track the upload.
curl -X "POST" "https://example.thoughtindustries.com/incoming/v2/content/course/create" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"courseAttributes": [
{
"title": "Welcome Video",
"kind": "video",
"sku": "VID-001",
"videoUrl": "https://example.com/videos/welcome.mp4",
"posterImageAsset": "https://example.com/images/welcome-poster.jpg"
}
]
}'Example response - Video Course with Background Job
{
"courseIds": ["c3d4e5f6-a7b8-9012-cdef-345678901234"],
"courseGroupIds": ["d4e5f6a7-b8c9-0123-defa-456789012345"],
"backgroundJob": {
"id": "job-uuid-here",
"type": "contentBulkUpload",
"status": "queued",
"contentUploads": [
{
"topicId": "topic-uuid-here",
"courseId": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"contentType": "video"
}
]
}
}Example request - Standalone Video Course (existing Wistia media)
To reuse a video already uploaded to Wistia, pass videoAsset (the Wistia media ID) instead of videoUrl. No background job is queued.
curl -X "POST" "https://example.thoughtindustries.com/incoming/v2/content/course/create" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"courseAttributes": [
{
"title": "Product Demo",
"kind": "video",
"videoAsset": "abc123wistia",
"preAsset": "intro456wistia",
"postAsset": "outro789wistia"
}
]
}'Errors
Validation failures return HTTP 400 with an errors array. For a single course the array has one message; for multiple courses each message is prefixed with Course at index N:.
{
"errors": [
"Invalid course kinds: xApiObject. Supported kinds are: article, video, courseGroup, microCourse, shareableContentObject"
]
}