domain-skills/skool: classroom course authoring API#491
Conversation
Full create/fill/delete contract for Skool classroom courses discovered while
publishing a 28-lesson course: POST /courses for course+sections+lessons
(unit_type=module, nested by parent_id, 3 levels render), PUT /courses/{id}
with a FLAT {title,desc,transcript,video_id} body to set [v2] content, and the
field-tested traps (50-char title cap = 422, don't navigate mid-loop, chunked
payload + fire-and-poll to beat the socket timeout, stale __NEXT_DATA__).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
✅ Skill review passedReviewed 1 file(s) — no findings. |
There was a problem hiding this comment.
2 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="domain-skills/skool/course-authoring.md">
<violation number="1" location="domain-skills/skool/course-authoring.md:4">
P2: Consider adding a minimal `fetch` template that includes `method`, `headers: {'Content-Type': 'application/json'}`, and `body: JSON.stringify(...)`. Without it, a reader might pass the shown JSON objects directly as `body`, which silently coerces to `[object Object]` and produces confusing failures against the API.</violation>
<violation number="2" location="domain-skills/skool/course-authoring.md:20">
P2: The TipTap document unwrapping example `"[v2]" + JSON.stringify(doc.content)` assumes `doc.content` is always present. If a TipTap doc is empty or partially constructed and lacks a `content` array, `JSON.stringify(undefined)` returns `undefined`, which string-concatenates to `[v2]undefined` — an invalid body that will likely be rejected or fail to render. Consider adding a fallback so the expression safely yields `[v2][]` when `content` is absent.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| # Skool — classroom course authoring (api2.skool.com) | ||
|
|
||
| Create and fill classroom courses via Skool's private API. Auth is httpOnly cookies, so make | ||
| credentialed `fetch(..., {credentials:"include"})` calls **from a logged-in Skool page context** |
There was a problem hiding this comment.
P2: Consider adding a minimal fetch template that includes method, headers: {'Content-Type': 'application/json'}, and body: JSON.stringify(...). Without it, a reader might pass the shown JSON objects directly as body, which silently coerces to [object Object] and produces confusing failures against the API.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At domain-skills/skool/course-authoring.md, line 4:
<comment>Consider adding a minimal `fetch` template that includes `method`, `headers: {'Content-Type': 'application/json'}`, and `body: JSON.stringify(...)`. Without it, a reader might pass the shown JSON objects directly as `body`, which silently coerces to `[object Object]` and produces confusing failures against the API.</comment>
<file context>
@@ -0,0 +1,75 @@
+# Skool — classroom course authoring (api2.skool.com)
+
+Create and fill classroom courses via Skool's private API. Auth is httpOnly cookies, so make
+credentialed `fetch(..., {credentials:"include"})` calls **from a logged-in Skool page context**
+(run them via `js(...)` / `Runtime.evaluate`). Must be a group admin.
+
</file context>
|
|
||
| A lesson body (`desc`) is the literal string `[v2]` followed by compact JSON of a **top-level ARRAY** | ||
| of TipTap block nodes — NOT `{"type":"doc","content":[...]}`. If you have a doc-wrapped TipTap doc, | ||
| unwrap it: `"[v2]" + JSON.stringify(doc.content)`. Supported nodes seen in the editor toolbar: |
There was a problem hiding this comment.
P2: The TipTap document unwrapping example "[v2]" + JSON.stringify(doc.content) assumes doc.content is always present. If a TipTap doc is empty or partially constructed and lacks a content array, JSON.stringify(undefined) returns undefined, which string-concatenates to [v2]undefined — an invalid body that will likely be rejected or fail to render. Consider adding a fallback so the expression safely yields [v2][] when content is absent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At domain-skills/skool/course-authoring.md, line 20:
<comment>The TipTap document unwrapping example `"[v2]" + JSON.stringify(doc.content)` assumes `doc.content` is always present. If a TipTap doc is empty or partially constructed and lacks a `content` array, `JSON.stringify(undefined)` returns `undefined`, which string-concatenates to `[v2]undefined` — an invalid body that will likely be rejected or fail to render. Consider adding a fallback so the expression safely yields `[v2][]` when `content` is absent.</comment>
<file context>
@@ -0,0 +1,75 @@
+
+A lesson body (`desc`) is the literal string `[v2]` followed by compact JSON of a **top-level ARRAY**
+of TipTap block nodes — NOT `{"type":"doc","content":[...]}`. If you have a doc-wrapped TipTap doc,
+unwrap it: `"[v2]" + JSON.stringify(doc.content)`. Supported nodes seen in the editor toolbar:
+paragraph, text (marks: bold, italic, strike, code, link), heading (H1–H4), bulletList/orderedList/
+listItem, blockquote, codeBlock, horizontalRule, hardBreak, image, video. Simplest reliable subset:
</file context>
| unwrap it: `"[v2]" + JSON.stringify(doc.content)`. Supported nodes seen in the editor toolbar: | |
| unwrap it: `"[v2]" + JSON.stringify(doc.content ?? [])` |
… titles
Field-corrected while publishing: the classroom UI renders only 2 levels
(course -> lessons). A section-with-children stores fine via the API but shows
as an empty page with unreachable lessons. Use flat lessons under the course
root with '{m}.{l} ' title prefixes for module grouping.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="domain-skills/skool/course-authoring.md">
<violation number="1" location="domain-skills/skool/course-authoring.md:65">
P2: The concrete ~45-character base-title cap is based on a fixed 4-character prefix example (`"6.5 "`), but the prefix length grows with double-digit module or lesson numbers. For example, `"10.10 "` is 6 characters, so a 45-character base title would exceed the documented 50-character API limit and trigger the HTTP 422 error this section warns about. Consider phrasing this as a dynamic calculation (`base title length ≤ 49 - prefix.length`) or noting that the 4-char assumption only holds for single-digit module and lesson numbers.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
|
|
||
| - **Title max = 50 chars.** Titles ≥ 51 fail the create with **HTTP 422** (silent in the UI). Keep | ||
| sidebar titles ≤ 49; put the full title as an H1 in the body if needed. 50 exactly passed, but stay | ||
| under — and remember a `"6.5 "` numeric prefix eats 4 chars, so cap the base title at ~45. |
There was a problem hiding this comment.
P2: The concrete ~45-character base-title cap is based on a fixed 4-character prefix example ("6.5 "), but the prefix length grows with double-digit module or lesson numbers. For example, "10.10 " is 6 characters, so a 45-character base title would exceed the documented 50-character API limit and trigger the HTTP 422 error this section warns about. Consider phrasing this as a dynamic calculation (base title length ≤ 49 - prefix.length) or noting that the 4-char assumption only holds for single-digit module and lesson numbers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At domain-skills/skool/course-authoring.md, line 65:
<comment>The concrete ~45-character base-title cap is based on a fixed 4-character prefix example (`"6.5 "`), but the prefix length grows with double-digit module or lesson numbers. For example, `"10.10 "` is 6 characters, so a 45-character base title would exceed the documented 50-character API limit and trigger the HTTP 422 error this section warns about. Consider phrasing this as a dynamic calculation (`base title length ≤ 49 - prefix.length`) or noting that the 4-char assumption only holds for single-digit module and lesson numbers.</comment>
<file context>
@@ -54,7 +61,8 @@ no-ops):
- **Title max = 50 chars.** Titles ≥ 51 fail the create with **HTTP 422** (silent in the UI). Keep
- sidebar titles ≤ 49; put the full title as an H1 in the body if needed. 50 exactly passed, but stay under.
+ sidebar titles ≤ 49; put the full title as an H1 in the body if needed. 50 exactly passed, but stay
+ under — and remember a `"6.5 "` numeric prefix eats 4 chars, so cap the base title at ~45.
- **Update needs the flat body.** The create body's `{...,metadata:{desc}}` shape does nothing on PUT.
Discover/confirm by editing one lesson in the UI and capturing the PUT (see below).
</file context>
| under — and remember a `"6.5 "` numeric prefix eats 4 chars, so cap the base title at ~45. | |
| + under — and remember the prefix length varies (`"6.5 "` = 4 chars, `"10.10 "` = 6 chars, etc.), so cap the base title at `49 - prefix.length`. |
Adds domain-skills/skool/course-authoring.md documenting Skool's private classroom-course API, discovered end-to-end while publishing a 28-lesson course.
POST /courses (course+sections+lessons, unit_type=module nested by parent_id, 3-level render), PUT /courses/{id} with FLAT {title,desc,transcript,video_id} for [v2] content, plus traps: 50-char title cap -> 422, don't navigate mid-loop, chunked payload + fire-and-poll for the socket, stale NEXT_DATA.
🤖 Generated with Claude Code
Summary by cubic
Adds
domain-skills/skool/course-authoring.md, a guide to Skool’s private classroom-course API:POST /coursesfor course/lesson creation (hierarchy viaparent_id, UI renders only 2 levels—create all lessons under the course and use numbered titles) andPUT /courses/{id}with a flat{title,desc,transcript,video_id}body to set[v2]content. Covers key gotchas: 50‑char title cap (422), creation‑order display, stale__NEXT_DATA__, and using chunked fire‑and‑poll loops to avoid socket timeouts.Written for commit 9c53fbb. Summary will update on new commits.