Skip to main content
Back to Tutorials

Creating Courses via API

End-to-end guide to programmatic course creation

Creating courses via the REST API is one of the most common integration patterns. Whether you're syncing content from an external CMS, automating course provisioning for new clients, or building a self-service content pipeline, this tutorial walks you through everything from the minimal request to production-grade patterns.

Prerequisites: You'll need a valid API key with write permissions. Find your key in Settings → API Keys in the admin panel. All requests use the incoming/v2 endpoint prefix.

Related training5 min
API Fundamentals -- Thought Industries Academy

A 5-minute course on Thought Industries Academy covering the basics of API fundamentals.

1

Minimal Course Creation

At minimum, you need a title and courseType. This creates a draft course with an auto-generated slug.

minimal-create.sh
curl -X "POST" "https://{{DOMAIN}}.thoughtindustries.com/incoming/v2/courses" \
  -H 'Authorization: Bearer {{APIKEY}}' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Introduction to Platform APIs",
    "courseType": "course"
  }'

The response returns the full course object with a generated id and slug. Store the id — you'll need it for updates, enrollments, and content management.

2

Field Reference

The course creation endpoint accepts the following fields. Required fields are highlighted — all others are optional and can be set later via update.

FieldTypeDescription
titlestring

Display name of the course shown to learners.

Max 255 characters. Must be unique within your instance.

courseTypestring

The type of learning content.

Accepted values: "course", "learning_path", "webinar", "video", "article", "scorm", "xapi".

slugstring

URL-friendly identifier. Auto-generated from title if omitted.

Must be unique. Lowercase, hyphens only. e.g. intro-platform-apis

descriptionstring

Rich-text description displayed on the course detail page.

Supports basic HTML tags.

publishedStatusstring

Controls visibility of the course.

Accepted values: "draft" (default), "published", "archived".

authorsstring[]

Array of author display names.

Purely cosmetic — does not create or link user accounts.

tagsstring[]

Tags for categorization and filtering.

New tags are created automatically if they don't exist.

externalIdstring

Your system's unique identifier for this course.

Useful for syncing with external systems. Must be unique if provided.

customFieldsobject

Key-value pairs for custom metadata.

Keys must match custom fields configured in your admin panel.

isFreeboolean

Whether the course is free to access.

Defaults to false. If true, learners can self-enroll without payment.

sourcestring

Attribution label for where the content originated.

e.g. 'partner-content', 'internal-training'

3

Full Example with All Fields

Here's a production-ready request using most available fields. This creates a draft course with metadata, tags, authors, and custom fields.

full-create.sh
curl -X "POST" "https://{{DOMAIN}}.thoughtindustries.com/incoming/v2/courses" \
  -H 'Authorization: Bearer {{APIKEY}}' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Advanced Integration Patterns",
    "courseType": "course",
    "slug": "advanced-integration-patterns",
    "description": "<p>Learn how to build robust, production-grade integrations with the Thought Industries platform.</p>",
    "publishedStatus": "draft",
    "authors": ["Jane Doe", "John Smith"],
    "tags": ["integration", "advanced", "api"],
    "externalId": "EXT-COURSE-042",
    "isFree": false,
    "customFields": {
      "difficulty": "advanced",
      "estimatedHours": 4
    }
  }'
4

Success Response

A successful creation returns 201 Created with the full course object, including the generated id and timestamps.

201 Created
{
  "id": "crs_a1b2c3d4e5",
  "title": "Advanced Integration Patterns",
  "slug": "advanced-integration-patterns",
  "courseType": "course",
  "publishedStatus": "draft",
  "description": "<p>Learn how to build robust, production-grade integrations with the Thought Industries platform.</p>",
  "authors": ["Jane Doe", "John Smith"],
  "tags": ["integration", "advanced", "api"],
  "externalId": "EXT-COURSE-042",
  "isFree": false,
  "customFields": {
    "difficulty": "advanced",
    "estimatedHours": 4
  },
  "createdAt": "2026-03-13T14:22:00.000Z",
  "updatedAt": "2026-03-13T14:22:00.000Z"
}
5

Validation Rules

The API enforces several validation rules. Understanding these will help you build resilient integrations that handle edge cases gracefully.

title is required

Every course must have a non-empty title. Max 255 characters.

courseType is required

Must be one of: "course", "learning_path", "webinar", "video", "article", "scorm", "xapi".

slug must be unique

If provided, the slug must not already exist. If omitted, one is generated from the title.

externalId must be unique

If provided, no other course can share the same externalId.

customFields keys must exist

Custom field keys must match fields configured in the admin panel. Unknown keys are silently ignored.

publishedStatus defaults to draft

If omitted, the course is created as a draft and will not be visible to learners.

HTML in description

The description field accepts basic HTML (<p>, <strong>, <em>, <ul>, <li>, <a>). Script tags are stripped.

tags are auto-created

If a tag doesn't exist, it's created automatically. Tags are case-insensitive and trimmed.

6

Error Responses

When validation fails, the API returns a structured error with a descriptive message and error code. Here are the most common errors:

Missing required field400

400 Bad Request
{
  "error": "Validation Error",
  "message": "Field 'title' is required.",
  "code": "MISSING_REQUIRED_FIELD",
  "statusCode": 400
}

Duplicate slug422

422 Unprocessable Entity
{
  "error": "Validation Error",
  "message": "A course with slug 'advanced-integration-patterns' already exists.",
  "code": "DUPLICATE_SLUG",
  "statusCode": 422
}

Invalid courseType value400

400 Bad Request
{
  "error": "Validation Error",
  "message": "Invalid courseType 'quiz'. Accepted values: course, learning_path, webinar, video, article, scorm, xapi.",
  "code": "INVALID_FIELD_VALUE",
  "statusCode": 400
}
7

Updating After Creation

After creating a course, you'll often want to update it — publish it, add tags, or modify metadata. Use a PUT request with the course ID. Only include the fields you want to change.

update-course.sh
curl -X "PUT" "https://{{DOMAIN}}.thoughtindustries.com/incoming/v2/courses/crs_a1b2c3d4e5" \
  -H 'Authorization: Bearer {{APIKEY}}' \
  -H 'Content-Type: application/json' \
  -d '{
    "publishedStatus": "published",
    "tags": ["integration", "advanced", "api", "featured"]
  }'

Common Patterns

CMS Sync

Use externalId to map courses to your CMS. On sync, check if the externalId exists — create if new, update if changed.

Client Onboarding

Create a template set of courses for each new client. Use tags to group them and customFields for client-specific metadata.

Content Pipeline

Create courses as drafts, add content via subsequent API calls, then publish programmatically when ready.

Bulk Import

Loop through your content records, creating courses sequentially. Respect rate limits (100 req/min) and handle 429 responses with exponential backoff.