Content Models and Authoring

Back to API Overview

This page describes how instructional content moves through the platform — authoring, publishing, assigning, and delivery — and the content formats and object shapes those workflows use. Endpoint links open the exact operation contracts in the API explorer.

Content Lifecycle

Authored content flows through four stages. Each stage creates immutable records that the next stage builds on, so published material never changes underneath students:

flowchart TD
    subgraph AUTHOR ["1 — Authoring"]
        A1["Create lesson"] --> A2["Create mission"]
        A2 --> A3["Create challenges"]
        A3 --> A4["Create challenge variants"]
    end
    subgraph PUBLISH ["2 — Publishing"]
        P1["Create lesson variant<br/>(pin lesson asset version)"] --> P2["Create publication<br/>with challenge bindings"]
    end
    subgraph ASSIGN ["3 — Assigning"]
        S1["Create assignment from publication<br/>for a section"]
    end
    subgraph DELIVER ["4 — Delivery"]
        D1["Student reads assigned challenges"] --> D2["Student submits attempts"]
        D2 --> D3["Grading produces results"]
    end
    AI["AI-assisted generation<br/>(reviewed assessment packages)"] -. "materializes challenges + variants" .-> AUTHOR
    AUTHOR --> PUBLISH --> ASSIGN --> DELIVER

Object Model

erDiagram
    Course ||--o{ Lesson : contains
    Course ||--o{ Section : contains
    Lesson ||--o{ Mission : plans
    Lesson ||--o{ LessonVariant : "versioned asset pointers"
    Mission ||--o{ Challenge : orders
    Challenge ||--o{ ChallengeVariant : "immutable derived forms"
    Lesson ||--o{ LessonPublication : publishes
    LessonVariant ||--o{ LessonPublication : "pinned by"
    LessonPublication ||--o{ ChallengeBinding : "mints challengeRef"
    ChallengeBinding }o--|| Challenge : references
    ChallengeBinding }o--|| ChallengeVariant : pins
    LessonPublication ||--o{ Assignment : "assigned as"
    Section ||--o{ Assignment : receives
    Assignment ||--o{ AssignmentChallenge : "copied bindings"
    AssignmentChallenge ||--o{ ChallengeAssignment : "per student, lazy"
    ChallengeAssignment ||--o{ ChallengeAttempt : records
    ChallengeAttempt ||--o| ChallengeResult : grades

Course, Subject, and Term provide the organizational frame; Section is the course subdivision that assignments target. The authoring axis (Mission → Challenge → ChallengeVariant) holds reusable canonical material; the delivery axis (LessonPublication → Assignment) freezes a selection of that material for students.

Lifecycle Status

A lesson carries a status that governs its availability across two audiences: authors deciding what may be newly assigned, and learners keeping access to what was already assigned. This is a lifecycle flag — it is not the same thing as a LessonPublication. A publication is the immutable content artifact (see Publishing); status is the lifecycle state that gates assignability. A lesson can hold a publication while its status is still draft.

Lessons.status takes these values:

status assignable to new sections? keeps existing access?
draft ✗ (never assigned)
released ✓ ✓
deprecated ✓ (discouraged) ✓
archived ✗ ✓
withdrawn ✗ ✗ (access revoked)

Transitions are performed with updateLessonStatusV2 (PATCH /v2/lessons/{lessonEID}/status), the sole path off draft:

stateDiagram-v2
    [*] --> draft : createLessonV2
    draft --> released : release (requires a publication)
    released --> deprecated
    deprecated --> released
    released --> withdrawn
    withdrawn --> released
    released --> archived
    archived --> released

Two rules constrain transitions:

A no-op transition (target status equal to the current status) is rejected.

Missions and Assignments adopt the same uniform lifecycle vocabulary in a follow-on change; this section documents the Lessons model.

1 — Authoring

Authors work inside a course realm and build reusable canonical material:

  1. Create the lesson (createLessonV2) and, for assessed material, a mission under it (createMissionV2).
  2. Create challenges under the mission (createChallengeV2). A challenge carries a localized nameJTO, an optional canonicalSourceJTO (the canonical prompt/source material), and an immutable sortOrder within its mission.
  3. Create challenge variants (createChallengeVariantForChallengeV2) — the concrete deliverable form per locale and evaluation strategy. A variant carries the student-facing prompt (queriesMPlusM, in Markdown+Math) plus its evaluation payload (see Evaluation Strategies).

Challenge variants are immutable copy-on-write revisions: once created, a variant row never changes. Editing produces a new revision, and everything downstream (publications, assignments) keeps pointing at the exact revision it pinned.

AI-assisted generation fits here as an authoring accelerator: reviewed assessment packages (see the generator operations in AI, Jobs, and Integrations) are materialized as ordinary challenges and variants with createGeneratedChallengesFromLessonV2 — no special runtime objects are involved.

2 — Publishing

Publishing freezes a lesson and a selection of challenge variants into an immutable, deliverable snapshot:

  1. Create a lesson variant (createLessonVariantV2) pointing at the published lesson asset — lessonPathname plus an explicit lessonAssetVersion. Unversioned asset references are rejected.
  2. Create the publication (createLessonPublicationV2) with the lesson variant, the published realm (which must contain the lesson’s course realm), an optional mission, and challengeBindings.

Each binding names a challengeRef (a publication-scoped string handle), the canonical challenge, the exact challenge variant to deliver, a sortOrder, and isRequired. The binding rules below are validated at creation and are the 400-level failures integrators most commonly hit:

createGeneratedAssignmentFromLessonV2 is the atomic AI shortcut: it materializes a reviewed assessment package as challenges and variants, publishes them, and creates the assignment in one call.

3 — Assigning

Assigning connects a publication to students through a section:

  1. Create the assignment (createAssignmentV2) naming the section, a target realm (the section realm or a descendant), the lesson publication, the availability window (availableTimestamp, dueTimestamp, acceptUntilTimestamp), and the required per-challenge grading spec (challengeGrading): exactly one entry per published challenge, each setting that challenge’s weight and scoreReductionMethodology (see Evaluation Strategies). The publication’s lesson must be released or deprecated (see Lifecycle Status) — a draft, archived, or withdrawn lesson cannot be assigned. (The generated-assignments composite path auto-releases a just-published draft lesson for you.)
  2. The publication’s bindings are copied into assignment challenges in binding order; additional per-challenge configuration uses createAssignmentChallengeV2.

Two delivery controls live at different scopes — they are set by different calls:

4 — Delivery and Evaluation

Students consume assignments through the student-scoped read paths (listChallengeAssignmentsV2):

Separately from grading, the learner-facing navigation endpoint (listUserSectionLessonsV2) surfaces two schedule fields sourced from the assignment, each with a different scope:

Availability Lifecycle Status

The authoring records — lessons, missions, and assignments — share one uniform availability-lifecycle status vocabulary describing where the record sits in its authoring/deliverability lifecycle:

Value Meaning
draft Being authored; not yet made available.
released Made available for delivery.
deprecated Superseded but still resolvable; discouraged for new use.
withdrawn Pulled/revoked; no longer available (previously cancelled on assignments).
archived Retired from the working set.

This availability lifecycle is distinct from the two runtime state machines, which describe progress/execution rather than availability and are never renamed to the vocabulary above:

Evaluation Strategies

Every challenge variant declares an evaluationStrategy that determines how attempts are graded:

Strategy Grading Evaluation payload
sync Deterministic, in-request responsesJSON (see below)
aiSync AI-graded in-request; result returned with the attempt evaluationConfigJSON (rubric and prompt configuration)
aiAsync AI-graded deferred; client polls the attempt for the result evaluationConfigJSON
jobAsync Reserved for future job-orchestrated grading — variants can be created, but attempts against them are rejected until grading ships —

aiSync and aiAsync share the same evaluationConfigJSON contract (rubric criteria, scoring, prompt resource); the strategy chooses when grading happens, the configuration defines what is graded. See AI, Jobs, and Integrations for the grading prompt contracts (for example grade-short-freeform-v1).

Score scale, weight, and reduction

Grading is points-only — there is no pass bar. A challenge is “completed” once it is graded, which is a separate notion from whether the student earned full points.

Two distinct scales combine at grade-rollup time, and it is important not to conflate them:

The two multiply at rollup:

earnedPointsForChallenge = (ChallengeResults.awardedScore / ChallengeResults.maxScore) × weight
missionPossiblePoints    = Σ weight

Each challenge also carries a reduction methodology (AssignmentChallenges.scoreReductionMethodology, one of first, best, or last), chosen per challenge at assignment-create time alongside weight. When a student has more than one attempt at a challenge, the reduction methodology selects which attempt’s result feeds the rollup above: the first attempt, the best-scoring attempt, or the last attempt. Like weight, it is mutable per assignment rather than frozen into the publication.

Delivered points and coverage: the learner lesson-list mission block

The rollup above is surfaced to learners as an 8-field mission grade block on each listable row of GET /v2/users/{userRef}/sections/{sectionEID}/lessons (mission, non-null iff isListable — context nodes carry none, the same shape of invariant as assignment’s nullability). The eight fields split into a …FromRequired and a …FromOptional set of four, partitioned by AssignmentChallenges.isRequired:

A partition with zero challenges (for example a mission with no optional challenges at all) reports 0 / 0.00 across all four of its fields — a genuinely empty partition and an unattempted-but-real partition are distinguished by the denominator, not by nullability: 0.00 / 0.00 / 0 / 0 for zero challenges versus 0.00 / 5.00 / 0 / 1 for one unattempted challenge worth 5 points.

Internationalization

Language-specific values are represented as nested JSON objects whose keys are locale codes such as en_US, es_ES, or fr_FR. Fields following this convention carry the JTO suffix — nameJTO, descriptionJTO, canonicalSourceJTO. Clients should choose the most appropriate localized value without assuming only one language will be present.

Example:

{
  "nameJTO": {
    "en_US": "Create user",
    "es_ES": "Crear usuario"
  }
}

Markdown+Math (M+M)

Markdown+Math is the shared format for human-readable content that combines GitHub-flavored Markdown with embedded LaTeX mathematics.

Preferred delimiters:

Legacy LaTeX delimiters such as \(...\) and \[...\] are not preferred for generated content. Use dollar-delimited math consistently so Markdown+Math content stays valid when embedded in JSON strings.

Use M+M for lesson text, challenge content, worked examples, and other instructional material that needs both structure and mathematical notation. The student-facing variant prompt field queriesMPlusM carries M+M content.

Challenge Source Shapes

Challenges.canonicalSourceJTO

Canonical challenge source content uses a localized outer shape (a JTO object) with a stable inner document structure. The field is optional on challenge creation. Authoring clients should preserve the distinction between:

ChallengeVariants.responsesJSON

Deterministic (sync) variants carry a ChallengeVariantResponsesJSON payload describing the response mode, accepted answers, and evaluation constraints. The payload’s version field selects the schema shape — it is unrelated to the variant row’s copy-on-write revision number:

Each version forbids the other version’s fields; the payload is validated once, at variant creation.

Where To Go Next