update-course-content
Action: Update existing course content (topics, lessons, sections) by UUID via PUT /content/course/update.
Skill by thoughtindustries
Chalk Labs
Need help deploying update-course-content?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/thoughtindustries-update-course-content
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
- Runs as
- Node.js package
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/update-course-content
What's inside
- Skill name
update-course-content- Description
- Action: Update existing course content (topics, lessons, sections) by UUID via PUT /content/course/update. Content stays as draft.
- License
- MIT
- Instructions
- 185 lines in SKILL.md · view on GitHub
Before installing
- Ships executable scripts
Files (2)
- SKILL.md
- update-course-content.js
SKILL.md (first lines)
# Update Course Content
This is an **Action** skill — a single, unambiguous API call. It is deterministic and narrowly scoped.
## Endpoint
`PUT /incoming/v2/content/course/update`
## When to use
- Updating the body of existing topics (e.g., after rewriting a lesson).
- Changing titles, positions, or metadata of sections/lessons/topics.
- Adding new child nodes (omit `id` to create under an existing parent).
- **Retiring test/junk content** — TI has **no delete endpoint**, so set a **course (session)** to `status: "archived"` here (this does not remove the parent course group). See [Removing content](#removing-content).
> **Updating pre-existing content is fully supported** — it is not limited to content you created in the same session. The only prerequisite is the correct topic/section/lesson **UUID**. Do **not** fall back to "edit it manually in the TI admin UI" just because the content already existed. The supported end-to-end flow for existing content is:
>
> 1. **`resolve-content`** — turn a course/lesson/topic title or admin URL into UUIDs.
> 2. **`get-full-content`** — read the current topic `body` (this is the only read endpoint that returns body; `get-topic`/`get-topics-by-lesson` do not).
> 3. **`update-course-content`** — PUT the changed topic(s) by `id`. This is a verified round-trip.
>
> The manual-in-TI fallback is only appropriate for the genuine limits below — never as a substitute for a supported update.
## Agent instructions
This endpoint does double duty:
- **Update** an existing entity → include its `id` (UUID). Only the fields you pass change. Use read actions first to obtain IDs.
- **Create** a new child under an existing course → **omit `id`** and instead supply the **parent link** (and, for topics, a `type`):
| Creating a… | Omit `id`, then supply | Also required |
|-------------|------------------------|---------------|
| course (under a group) | `courseGroupId` | — |
| section | `courseId` | — |
| lesson | `sectionId` | — |
| topic | `lessonId` | `type` (e.g. `text`) |
Do **not** invent field names like `parentId` — the API only recognises the parent keys above. This is the correct way to "attach a section/lesson/topic" to a course that already exists.
**New topic `type` must be lesson-safe.** Use `"text"` for HTML body content. Do **not** use `"article"` (or other standalone-only types) for a topic attached to a lesson — the API accepts it but the learner view 404s and the editor cannot open it, and there is no delete action to recover. The script blocks this locally.
1. Run `update-course-content.js --dry-run --file=update.json` first to validate — the script now fails locally with a precise message if a create is missing its parent link or a new topic is missing `type`, or if a video's source is in the wrong field.
2. Then run without `--dry-run`.
3. Confirm `restartProgress` semantics with the user if replacing SCORM on an existing topic.
4. To set/replace a **video**, target the topic by `id` with `videoAsset`/`videoUrl` — never an embed in `body`. See [Video content](#video-content).
## Limits
- The update payload is an **object** (not an array like create). It contains optional arrays: `courseGroups`, `courses`, `sections`, `lessons`, `topics`.
- Up to **100 items** total across all arrays combined.
### One payload, multiple arrays
You can mix `courseGroups`, `courses`, `sections`, `lessons`, and `topics` in a single payload, and mix different fields per item (e.g. one topic changing `title`, another changing `body`). The script always sends this as **one request**.
Historically, some server versions applied each entity array as **one SQL statement built from the union of all fields in that array**, which failed with an opaque `HTTP 400 — "A processing error occurred. Please refresh the page and try again."` whenever the update items in a single array didn't all carry the **same set of fields**. (This was a server bug, not entity-type mixing — two topics in one array, one with only `title` and one with only `body`, reproduced it on its own. It is fixed server-side.)
To stay compatible with instances that haven't picked up the server fix yet, `update-course-content.js` uses a **fallback**: it sends the combined request first (atomic, and the only request needed on a fixed server); only if the server rejects it with that specific legacy processing error does it retry by splitting the payload into the minimum number of sequential requests (parent→child order), grouping update items so each request's arrays are field-uniform. Use `--dry-run` to preview the combined request and, if relevant, the fallback split.
On a fixed server the update is a single atomic request. On the fallback path, updates are re-sent per group and are idempotent, but are **not atomic**; if a fallback request fails, the error names which entity arrays and ids were involved and how many earlier fallback requests already applied.
### Genuine limits (when manual TI editing IS the right answer)
These are the only cases where "do it in the TI admin UI" is legitimate — plain text/HTML `body` edits are **not** among them:
- **Publishing.** This action never releases content; a human publishes drafts in TI (see hard gate).
- **Quiz/assessment questions & answers.** `body` is HTML page content; it does not edit structured quiz questions, options, or scoring.
- **SCORM / media file uploads.** Binary uploads use the separate content-upload flow, not a JSON `body`. Confirm `restartProgress` semantics with the user before replacing SCORM on an existing topic.
When you hit one of these, name the specific part that must be done in TI and still apply everything the API *does* support (e.g. the text body) rather than deferring the whole task to manual editing.
## Run the script
```bash
node "${CLAUDE_PLUGIN_ROOT:+$CLAUDE_PLUGIN_ROOT/skills/actions/update-course-content/}update-course-content.js" --dry-run --file=update.json
node "${CLAUDE_PLUGIN_ROOT:+$CLAUDE_PLUGIN_ROOT/skills/actions/update-course-content/}update-course-content.js" --file=update.json
```
## Payload shape
```json
{
"topics": [
{ "id": "TOPIC_UUID", "body": "<p>Updated content here</p>" }
]
}
```
Or with explicit wrapper:
```json
{
"courseAttributes": {
"topics": [
{ "id": "TOPIC_UUID", "title": "New Title", "body": "<p>...</p>" }
]
}
}
```
Attaching a new section → lesson → topic to an existing course (each level created bottom-up needs its parent's UUID; create the section first to get its `id`, then the lesson, then the topic — or add each level in separate calls):
```json
{
"sections": [
{ "courseId": "COURSE_UUID", "title": "New Section", "displayOrder": 3 }
]
}
```
```json
{
"lessons": [
{ "sectionId": "SECTION_UUID", "title": "New Lesson", "openType": "studentsOnly" }
]
}
```
```json
{
"topics": [
{ "lessonId": "LESSON_UUID", "title": "New Topic", "type": "text", "body": "<p>...</p>" }
]From thoughtindustries/ti-admin-plugin, MIT.
More from thoughtindustries/ti-admin-plugin
- 0.521score
resolve-content
thoughtindustries
Action: Resolve a human reference (course/lesson/topic title, slug, admin URL, or UUID) into the concrete UUID chain the read/update actions need.
SkillBFreshness: BClaude Code +9 - 0.521score
configure
thoughtindustries
Action: One-time setup that securely stores the TI instance URL and Incoming API key in a user-scoped config file, so no shell variable or in-chat key is…
SkillBFreshness: BClaude Code +9 - 0.521score
create-course-draft
thoughtindustries
Action: Create a new course (courseGroup, microCourse, article, video) as a draft via POST /content/course/create.
SkillBFreshness: BClaude Code +9 - 0.521score
get-full-content
thoughtindustries
Action: Read the full content body of a course or learning path (all sections/lessons/topics with HTML body) via GET /fullContent/{type}/{id}, and of…
SkillBFreshness: BClaude Code +9 - 0.521score
get-topic
thoughtindustries
Action: Fetch a single topic's metadata by ID via GET /topics/{id} (id, title, type, pre/post text).
SkillBFreshness: BClaude Code +9 - 0.521score
get-topics-by-lesson
thoughtindustries
Action: List a lesson's topics (metadata only) via GET /topics/lesson/{lessonId}.
SkillBFreshness: BClaude Code +9 - 0.521score
list-course-groups
thoughtindustries
Action: List/search course groups from the TI catalog via GET /courseGroups.
SkillBFreshness: BClaude Code +9 - 0.521score
read-existing-content
thoughtindustries
Routine: Show the content of an existing course/lesson/topic from a human reference (title or admin URL) — resolves IDs, then reads the actual topic bodies.
SkillBFreshness: BClaude Code +9 - 0.521score
rewrite-lesson
thoughtindustries
Routine: Read an existing lesson's content, rewrite it in a different tone/length/reading level, and stage the rewrite as a draft update.
SkillBFreshness: BClaude Code +9 - 0.518score
get-course-group
thoughtindustries
Action: Fetch a single course group by ID via GET /courseGroups/{id}.
SkillBFreshness: BClaude Code +9 - 0.518score
get-course-structure
thoughtindustries
Action: Fetch the full tree (sections, lessons, topics) of a course via GET /courses/{courseId}/structure.
SkillBFreshness: BClaude Code +9 - 0.518score
get-section
thoughtindustries
Action: Fetch a single section by ID via GET /sections/{id}.
SkillBFreshness: BClaude Code +9
Signals
- Stars
- 0
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- Aug 14, 2026 (2 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from thoughtindustries
- 0.521score
get-topic
thoughtindustries
Action: Fetch a single topic's metadata by ID via GET /topics/{id} (id, title, type, pre/post text).
SkillBFreshness: BClaude Code +9 - 0.521score
create-course-draft
thoughtindustries
Action: Create a new course (courseGroup, microCourse, article, video) as a draft via POST /content/course/create.
SkillBFreshness: BClaude Code +9 - 0.521score
configure
thoughtindustries
Action: One-time setup that securely stores the TI instance URL and Incoming API key in a user-scoped config file, so no shell variable or in-chat key is…
SkillBFreshness: BClaude Code +9 - 0.521score
resolve-content
thoughtindustries
Action: Resolve a human reference (course/lesson/topic title, slug, admin URL, or UUID) into the concrete UUID chain the read/update actions need.
SkillBFreshness: BClaude Code +9 - 0.521score
get-full-content
thoughtindustries
Action: Read the full content body of a course or learning path (all sections/lessons/topics with HTML body) via GET /fullContent/{type}/{id}, and of…
SkillBFreshness: BClaude Code +9 - 0.521score
get-topics-by-lesson
thoughtindustries
Action: List a lesson's topics (metadata only) via GET /topics/lesson/{lessonId}.
SkillBFreshness: BClaude Code +9
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get update-course-content running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
update-course-content skill. Agent Mart. https://agentmart.dev/skills/thoughtindustries-update-course-content (last updated 2026-10-24)