# iblai-api-studio — author a course on Open edX Studio

> Mirrored from [`iblai/vibe`](https://github.com/iblai/vibe) · [`skills/content/iblai-api-studio/SKILL.md`](https://github.com/iblai/vibe/blob/main/skills/content/iblai-api-studio/SKILL.md). This page is generated — edit it in the repository, not here.

Studio is the Open edX CMS behind the ibl.ai LMS. The `/iblai-api-studio-*`
skills drive it over its own session-authenticated HTTP endpoints (no UI, no
Api-Token) and check the result on the LMS the way `lms.ibl.ai` does. Each
skill is one capability with verified endpoints; this page is the map.
**A full build needs most of them — read the ones in the build order now, in
one pass, before the first call.**

| Where | Production | Test server |
|---|---|---|
| Studio (authoring) | `https://studio.learn.iblai.app` | `https://studio.learn.iblai.org` |
| LMS API (verification) | `https://learn.iblai.app` | `https://learn.iblai.org` |
| LMS app (what users open) | `https://lms.ibl.ai` | `https://lms.iblai.org` |

## Preflight (30 seconds, always)

```bash
node .claude/skills/iblai-api-studio-auth/scripts/studio-login.mjs --check \
  && grep -q '^STUDIO_ORG=.\+' studio.env && echo "STUDIO_ORG set" || echo "STOP: run /iblai-api-studio-auth (and set STUDIO_ORG)"
```

No `studio.env`, an expired session, or an empty `STUDIO_ORG` → `/iblai-api-studio-auth`
first. Do not start creating anything until this prints two `ok`s and the org key.

## Build order

```
0. /iblai-api-studio-auth            preflight above; sign in once → studio.env
1. plan the outline (below)          sections → subsections → units → components, on paper first
2. /iblai-api-studio-course-create   POST /api/ibl/manage/course/ → course_key (+ delete)
3. /iblai-api-studio-settings        dates, pacing, about page, images, catalog fields — BEFORE any publish
   /iblai-api-studio-grading         assignment types + weights — subsections reference these
4. /iblai-api-studio-outline         ONE call builds every section/subsection/unit (+ html/problem) and publishes
   then, always: rename the html blocks (the builder names them all "Text"),
   set graderType per graded subsection (the spec has no such key), republish
   — or block by block —
   /iblai-api-studio-section · -subsection · -unit    POST /xblock/ {parent_locator, category}
5. /iblai-api-studio-html            text blocks: data = HTML fragment
   /iblai-api-studio-problem         one problem block per unit holding all its questions (OLX)
   /iblai-api-studio-pdf             pdf blocks (advanced module + asset + save_pdf handler)
6. /iblai-api-studio-team            staff / instructor by email (any time)
7. /iblai-api-studio-publish         {publish:"make_public"} on the course root; verify states
8. /iblai-api-studio-lms             enroll, read the published outline as a user, catalog check
```

**Settings before content, content before the first publish.** Pacing
(`self_paced`) cannot change once the course has started, enrollment must open
before the start date, and a published-but-empty course is what users see
in the catalog. Configure, build, review the outline, then publish once.

## Bulk or one block at a time?

Both are first-class; pick by situation, not by convenience:

| Situation | Use |
|---|---|
| **New course**, outline known up front, text + questions | `/iblai-api-studio-outline` — `POST /api/v1/ibl/course/create_full_course` builds and publishes the whole tree in one request (a 5-section course is 1 call instead of ~120) |
| **Existing course, one change**: add one section or lesson, insert a unit, fix one component's text, replace a quiz, reorder, rename, hide, delete a single block, work in an unpublished draft | the individual skills (`-section`, `-subsection`, `-unit`, `-html`, `-problem`, `-pdf`) — they act on exactly one block by locator and never touch the rest |
| **Existing course, many changes**: rewrite all readings, add units across lessons, restructure sections | `/iblai-api-studio-outline` "Bulk edits — reconcile": `scripts/reconcile.py` diffs a spec against the live outline and creates/updates/reorders (and, only on request, deletes) idempotently, then publishes what changed |
| Both | build the skeleton + text with the bulk call, then add PDFs and later edits block by block or by reconcile, then republish what changed — the reconcile driver adopts the builder's `"Text"` html blocks on its first run, provided the spec keeps the built component order |

The same block is named three ways across endpoints — `locator`/`id` +
`display_name` + `category` (stock), `xblock_locator`/`id` + `display_name` +
`type` (v1), `block_id` + `name` + `block_type` (container children). The
table in `/iblai-api-studio-outline` maps them.

Never run the bulk builder against a course that already has content to
"add a section": it appends **and publishes everything**, including drafts.

**Deleting a course** (`/iblai-api-studio-course-create`) is permanent and
destroys user enrollments and progress. It is guarded by explicit checks
in that skill; hiding a course (`/iblai-api-studio-settings`) is almost always
what is meant.

Budget for block-by-block: 1 call per section/subsection/unit, 2 per html or
problem block, 4 per pdf block, plus 1 publish. Keep every returned locator
in a local JSON (`build.json`) keyed by the outline path so a re-run resumes
instead of duplicating.

## Planning a production-grade outline

Enterprise courses are judged on structure before content. Plan the tree
before the first request and keep it consistent:

- **Start from outcomes.** 3–6 measurable learning objectives for the course
  (verbs from Bloom's taxonomy: describe, apply, analyse, evaluate); every
  section maps to at least one, every graded subsection assesses one.
- **Sections = modules or weeks** (4–8 for a short course), each with a
  one-line goal in its name: `Week 2 · Reading data critically`.
- **Subsections = lessons** (2–4 per section, 20–40 minutes each). Name them
  by topic, not by type. Mark graded ones with `graderType` and a due date.
- **Units = one idea per page** (3–6 per subsection, 5–10 minutes each), in a
  repeatable rhythm: *context/overview* → *content* (html, video, pdf) →
  *practice* (one problem block with several questions) → *summary/next steps*.
- **Assessment alignment.** Formative checks (unlimited attempts, hints,
  `showanswer: finished`) inside lessons; summative work (limited attempts,
  weighted) in dedicated subsections whose grader type exists in the policy.
- **Consistency.** Same naming pattern at every level, same unit rhythm, same
  HTML component styles (see `/iblai-api-studio-html`), a course-level intro
  section (welcome, objectives, how grading works, syllabus PDF) and a closing
  section (summary, further reading, feedback).
- **Accessibility and scope.** Alt text on every image, headings in order,
  readings under ~1,500 words per unit, video under 10 minutes.

Write the plan as the JSON spec that `/iblai-api-studio-outline` accepts; it
doubles as the review artefact for the user before anything is created.

## Content quality — and how to keep sub-agents on the rails

Course content is the product. Every page a user opens must be **well
written, presentable and beautiful, informative without being distracting,
well structured and organized, carefully planned and reviewed.** That bar is
easy to state and easy to lose when writing is split up.

**Sub-agents drift.** When sections or units are delegated to parallel agents,
the pieces come back with different voices, different heading levels,
different callout styles, uneven depth (one unit a rich reading, the next
three sentences), invented terminology, or questions that do not match the
objectives. Nothing in Studio stops that. Regulate it explicitly:

1. **One content brief, given to every agent verbatim** — course objectives,
   audience and reading level, tone (second person, plain, no filler), the
   unit rhythm (overview → content → one practice block → summary), the
   naming pattern, the HTML patterns allowed (`/iblai-api-studio-html`), the
   question rules (`/iblai-api-studio-problem`), target length per unit
   (300–1,200 words of reading, 3–5 questions), and the glossary of terms
   already defined in earlier units.
2. **Produce the spec, not the calls.** Sub-agents return JSON fragments for
   `/iblai-api-studio-outline` (or `data` strings for single blocks); only the
   coordinating agent posts to Studio, after review. No sub-agent gets
   `studio.env`.
3. **Review every fragment against the brief before it is posted:** objective
   coverage, depth (no unit under ~150 words of real content, no placeholder
   text, no "in this section we will…" padding), consistent headings and
   callouts, valid HTML fragment and valid OLX, a title on every block, hints
   and solutions on every question, terminology consistent with earlier
   units, nothing that repeats a neighbouring unit. Send thin or divergent
   pieces back with the specific gap; do not "fix in post" by posting and
   patching.
4. **Read the whole course once, in order,** as a user would (the published
   outline from `/iblai-api-studio-lms`, or the spec end to end) before the
   first publish. Transitions between sections, an intro that sets
   expectations, an outro that closes them.

Beautiful means calm: consistent components, generous spacing, one accent
colour, no decoration that competes with the reading.

## Keys you will pass around

| Thing | Shape | Where it comes from |
|---|---|---|
| course key | `course-v1:<org>+<number>+<run>` | course create response `course_key` |
| course root block | `block-v1:<org>+<number>+<run>+type@course+block@course` | derived from the course key; `parent_locator` for sections and the bulk builder |
| any block (usage key) | `block-v1:<org>+<number>+<run>+type@<category>+block@<hex>` | `locator` from `POST /xblock/` / `xblock_locator` from the v1 routes |
| category | `chapter` · `sequential` · `vertical` · `html` · `problem` · `pdf` | fixed |

**URL-encode course keys in query strings** (`course_key=course-v1%3Aorg%2Bnum%2Brun`);
path segments accept them raw.

## The snippet every skill starts with

```bash
set -a; . ./studio.env; set +a
S=(-s -b "studio_session_id=$STUDIO_SESSION; csrftoken=$STUDIO_CSRF" -H "X-CSRFToken: $STUDIO_CSRF" \
   -H "Origin: $STUDIO_URL" -H "Referer: $STUDIO_URL/" -H "Accept: application/json" -H "Content-Type: application/json")
```

`Accept: application/json` makes Studio answer JSON; `X-CSRFToken` + `Origin`
make writes pass CSRF. `302` to `/login` = expired session; a `500` HTML
"Studio Server Error" on a documented call = a malformed cookie (see
`/iblai-api-studio-auth` "Reading failures") or a parameter the skill marks as
broken.

## Which skill when

| The user wants… | Skill |
|---|---|
| "connect", "log in to Studio", a call returned 302/403/500 | `/iblai-api-studio-auth` |
| a new course, list my courses, the course key, delete a course | `/iblai-api-studio-course-create` |
| the whole outline (and text/questions) in one go | `/iblai-api-studio-outline` |
| weeks/modules/sections; rename, reorder, release dates, hide from users | `/iblai-api-studio-section` |
| lessons/subsections; due dates, graded as Homework/Exam, timed | `/iblai-api-studio-subsection` |
| units/pages inside a lesson; order components; duplicate; delete | `/iblai-api-studio-unit` |
| text, reading, rich content, callouts, tables, images, embeds | `/iblai-api-studio-html` |
| a PDF reader block | `/iblai-api-studio-pdf` |
| quiz, questions, multiple choice, checkbox, numeric, text, dropdown | `/iblai-api-studio-problem` |
| dates, pacing, description, about page, images, intro video, catalog tags, advanced settings, uploads | `/iblai-api-studio-settings` |
| who can edit the course; add/remove staff or instructors | `/iblai-api-studio-team` |
| grading policy, assignment types, weights, pass mark, grace period | `/iblai-api-studio-grading` |
| make it live, publish, discard draft, "why can't users see it" | `/iblai-api-studio-publish` |
| enroll a user, what the user sees, is it in the catalog | `/iblai-api-studio-lms` |

Outside this family: AI-generated course drafts through the ibl.ai pipeline →
`/iblai-vibe-course-create`; org roles (`course-creator`, `org-instructor`) and
user admin → `/iblai-api-management`; catalog programs/pathways/enrollment with
an Api-Token → `/iblai-api-catalog`; Canvas → `/iblai-api-canvas-course-builder`.

## Reporting back

End with the course key, the LMS app URL
`$LMS_APP_URL/platform/<org>/courses/<course_key>`, counts (sections /
subsections / units / components), publish state, and anything that failed.
An unpublished build is invisible to users — say so explicitly.
