Content Models and Authoring
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:
- draft is one-way. From
draftthe only legal exit is→ released; a lesson can never return todraft(usewithdrawnto pull a lesson). Amongreleased,deprecated,withdrawn, andarchivedevery move is legal and reversible. - The release invariant. A lesson may only become
releasedordeprecatedif it already has at least oneLessonPublication(released ⇒ a LessonPublication exists). The reverse is allowed — a lesson may hold a publication while it staysdraft.
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:
- Create the lesson (createLessonV2) and, for assessed material, a mission under it (createMissionV2).
- Create challenges under the mission
(createChallengeV2). A
challenge carries a localized
nameJTO, an optionalcanonicalSourceJTO(the canonical prompt/source material), and an immutablesortOrderwithin its mission. - 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:
- Create a lesson variant
(createLessonVariantV2)
pointing at the published lesson asset —
lessonPathnameplus an explicitlessonAssetVersion. Unversioned asset references are rejected. - 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:
challengeRefis minted once and must be unique within the publication.- Each canonical challenge may be bound at most once per publication.
sortOrdermust be sequential starting at 1 with no gaps — binding order is document order.- All bound challenges must belong to the publication’s mission.
- Publications are immutable; to change bindings, publish again.
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:
- 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’sweightandscoreReductionMethodology(see Evaluation Strategies). The publication’s lesson must bereleasedordeprecated(see Lifecycle Status) — adraft,archived, orwithdrawnlesson cannot be assigned. (The generated-assignments composite path auto-releases a just-publisheddraftlesson for you.) - 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:
formCountis assignment-scoped (on the assignment-create request):nulldelivers one uniform form to everyone;0deals an individualized choice subset per student;K ≥ 1materializesKfixed forms and deterministically assigns each student one of them.selectionPolicy(sameForAllorrandomFromPool) is per bound challenge (on the assignment-challenge request), not on the assignment itself.
4 — Delivery and Evaluation
Students consume assignments through the student-scoped read paths (listChallengeAssignmentsV2):
- A student’s per-challenge assignment record is created lazily on first read, along with their form or dealt choice subset. Deals are deterministic and immutable — the same student always sees the same form and choices.
- Variant reads project only what the student may see: the prompt
(
queriesMPlusM) and, for structured-choice variants, the dealt choices. Answer keys are never sent to clients —acceptedChoiceIDsand AI grading configuration stay server-side, and grading is always performed server-side. - Students submit attempts (createChallengeAttemptV2). For structured-choice variants, every submitted choice ID must come from the student’s dealt subset; an out-of-deal ID is a request error, not a wrong answer.
- Grading produces one result per attempt, retrieved with the attempt or via its result endpoint; timing depends on the variant’s evaluation strategy.
Separately from grading, the learner-facing navigation endpoint (listUserSectionLessonsV2) surfaces two schedule fields sourced from the assignment, each with a different scope:
availableTimestampopens the lesson content itself — before it, the student sees that a lesson is assigned (and when it opens) but cannot yet resolve its asset locator. Content has no upper time bound and stays readable indefinitely once opened.acceptUntilTimestampcloses only the mission (new submissions), not the lesson content. A student can keep reviewing a lesson and their past attempts read-only after this timestamp passes; they simply can no longer submit new attempts. This is enforced at submission time (#5949), keyed on the student’s submission timestamp, so a submission already accepted before the close still completes grading even if that grading finishes afterward.
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. |
Missions.statusandAssignments.statusboth use this exact enum (draft,released,deprecated,withdrawn,archived). An assignment is learner-visible only whilereleased; awithdrawn(formerlycancelled) assignment is excluded from the student-facing lesson and enrollment reads and from every challenge access gate, carrying the same pulled/revoked semantics.- Note that a lesson’s authoring status is independent of its deliverability:
deliverability is conferred by a lesson publication, not by the lesson’s
own status, so authored lessons routinely remain
draftwhile being published and delivered.
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:
ChallengeAssignments.status(assigned,started,submitted,graded,excused,closed) — a student’s progress through a materialized challenge.ChallengeAttempts.status(attempt execution/grading progression) — the lifecycle of a single submitted attempt.
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:
ChallengeResults.maxScoreis the variant/rubric scale — it is derived server-side, never authored by a caller. Deterministic (sync) grading writesawardedScore ∈ {0.0, 1.0}againstmaxScore = 1.0; AI grading (aiSync/aiAsync) writes the model’sawardedScoreagainst the variant’sevaluationConfigJSON.scoring.maxScore. It expresses how well the attempt scored on that variant’s own scale.AssignmentChallenges.weightis the assignment-time weight — the challenge’s absolute points-when-fully-correct in this assignment. It is chosen per challenge when the assignment is created (challengeGradingon createAssignmentV2), is not a grading max, and is not frozen into the publication (adjusting it re-derives the rollup; instructors may tune it per assignment).
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:
earnedScoreFrom{Required,Optional}— ΣearnedPointsForChallenge(the rollup formula above) over the SELECTED attempt of each challenge in that partition.maximumPossibleScoreFrom{Required,Optional}— a fixedΣ weightover everyAssignmentChallengesrow in that partition, computed as a SEPARATE aggregate that never touchesChallengeResults. This is deliberate: deriving the denominator from result rows instead would mix incompatible variant/rubric scales (sincemaxScorevaries per challenge) and would silently drop unattempted challenges from the count — an unattempted optional challenge worth 5 points would report “0 of 0” instead of the honest “0 of 5”. A challenge nobody has opened yet must still count toward the denominator.completedChallengeCountFrom{Required,Optional}— the count of challenges in that partition with a graded selected result. Stated plainly:completedmeans graded, not passed. A zero-scoring attempt is completed the moment it is graded, exactly like any other score — there is no pass/fail concept anywhere in this schema (see “Grading is points-only” above). A mastery signal is explicitly out of scope for this block.totalChallengeCountFrom{Required,Optional}— the fixed count ofAssignmentChallengesrows in that partition, from the same aggregate as the possible-score denominator.
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:
- Inline math:
$...$ - Display math:
$$...$$
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:
- Prompt text
- Accepted alternatives
- Distractors
- Evaluation guidance
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:
- Version 1 — text and alias shapes.
evaluationMode(textEquivalence,singleSelect,multipleSelect,casEquivalence) selects which ofacceptedResponse/acceptedResponses/acceptedExpressions/aliasesapply, with per-modemodifiers. - Version 2 — structured choices, select modes only. The authored pool
lives in
choices[](uniquechoiceIDs), correct answers inacceptedChoiceIDs(never projected to students), and deal rules inpresentation(presentCount,alwaysIncludeAccepted,shuffle). Version 2 powers assignment form dealing (formCount) and per-student choice subsets.
Each version forbids the other version’s fields; the payload is validated once, at variant creation.
Where To Go Next
- AI, Jobs, and Integrations for AI-backed generation and grading contracts.
- Security and Authorization for the headers, envelopes, and access rules that apply to authoring endpoints.
- Platform Concepts for realms and realm containment — publications must publish into a realm containing the course realm, and assignments target the section realm or a descendant.