The data model is intentionally minimal:Documentation Index
Fetch the complete documentation index at: https://docs.hawkings.education/llms.txt
Use this file to discover all available pages before exploring further.
Course → Unit → Lesson → Activity → Question, with Cohort as the run-time instance and free
metadata + external_ids on every resource. That tiny set covers
everything from a flat Google-Classroom-style course to a deeply nested
regulated training program — without forcing a domain-specific schema
on you.
This page shows three concrete examples that look very different on
paper but reuse the same primitives.
The mental model in one paragraph
A Course is the curriculum. A Cohort is one run of it. A Unit groups lessons and can nest into other units viaparent_unit_id when you need extra depth. A Lesson is the atomic
piece of content (markdown, video, reading). An Activity is
something the student does (quiz, flashcard, assignment). Anything
domain-specific (SEPE codes, CRNs, FUNDAE file numbers, Canvas IDs)
lives in metadata and external_ids — never in dedicated columns.
Example 1 — Replicate a Moodle course
Moodle’s structure is shallow:course → section → activity/resource.
Map it like this:
| Moodle | Hawkings |
|---|---|
course | Course |
section (week or topic) | Unit (top-level) |
mod_page / mod_resource | Lesson with type: markdown |
mod_quiz | Activity with type: quiz |
mod_assign | Assignment |
external_ids, you
can re-export the course back into the same Moodle instance and the
mapping survives.
Example 2 — Spanish vocational training (especialidad formativa)
A SEPE-registered especialidad formativa has three internal levels: módulos formativos → unidades formativas → epígrafes. This is where nestable units pay rent — you setparent_unit_id to compose
depth without inventing new resources.
| Especialidad formativa | Hawkings |
|---|---|
| Especialidad (e.g. ADGD0308) | Course |
| Módulo formativo (MF) | Unit (top-level) |
| Unidad formativa (UF) | Unit with parent_unit_id = MF |
| Epígrafe | Lesson |
| Convocatoria | Cohort |
Section resource, no
Epigrafe resource, no EspecialidadFormativa subclass. Depth comes
from parent_unit_id; domain semantics come from metadata. The same
SDK call shape works.
If you ship many courses in Spain and want typed fields (
codigoSepe
instead of metadata.codigo_sepe), the recommended path is a
companion package (e.g. @hawkings/sdk-spain) that wraps these
calls, not core SDK changes. See the Companion packages
guide for the pattern.Example 3 — A US university subject
A typical US college course (say CS 101 — Introduction to Computer Science) lives once in the catalog but is delivered multiple times per academic year, each section with its own instructor, schedule, and enrollment. This is the textbook use case for the Course / Cohort split.| University | Hawkings |
|---|---|
| Subject / catalog entry (CS 101) | Course |
| Course offering (Fall 2026 sec. 001) | Cohort |
| Week / unit | Unit (top-level) |
| Topic / lecture | Lesson |
| Problem set / quiz / midterm | Activity / Assignment |
Course and decide
explicitly whether existing cohorts inherit or stay frozen.
Example 4 — A program bundling multiple courses
Catalog model: you have a library of ~100 standalone courses (Python, Anthropic API, RAG, prompt engineering…). You don’t only sell them individually — you also sell named bundles like “AI Specialization” which packages three of them together, with a defined start and end date per intake. This is the same pattern as Coursera Specializations, edX MicroMasters, and Udacity Nanodegrees. It’s modeled with a third top-level resource:Program.
The three resources in play
| Resource | What it is | Has its own content tree? |
|---|---|---|
Course | Atomic catalog item (Python, RAG, etc.). Has its own units/lessons. | Yes |
Program | A named bundle that references course_ids. Has title, description. | No (delegates to its courses) |
Cohort | One time-boxed run. Belongs to either a course or a program. | — |
Program is lightweight. It does not
duplicate content; it references courses. If you update Python in
the catalog, every program that includes it gets the update for free.
The non-obvious question: what happens when a student enrolls into a program cohort?
You have to make a product decision. Both options are valid and the SDK supports both — pick the simpler one unless you have a reason not to.Option A — Global access, unified progress (recommended default)
When Alice is enrolled inintake2026:
- She automatically has access to the full content tree of all three referenced courses.
- Her progress, submissions, grades and certificates live under the program cohort, not in separate per-course cohorts.
- No sub-cohorts are created. The program cohort is the only container.
cohort.metadata.schedule or a small cohortSchedule resource.
Option B — Auto-generated sub-cohorts
Creating the program cohort transparently spins up one cohort per referenced course, all linked back:Content that belongs to the program, not to any single course
What if AI Specialization has its own onboarding video, capstone project, or graduation exam — content that doesn’t naturally belong to Python, Anthropic, or RAG individually? The clean answer: create it as a course, then add it to the program’scourse_ids:
access_via: "program_only" metadata lets your application hide
this course from the public catalog — the model itself doesn’t need a
special “program-exclusive” type.
Quick reference
| Question | Answer |
|---|---|
Is Program a required hierarchy level? | No. Optional, lightweight, lateral. |
| Can a course exist with no program? | Yes. Most courses will. |
| Can a course be in multiple programs? | Yes. Many-to-many. |
Does a cohort always have a course_id? | No. It has course_id XOR program_id. |
| Where does pricing go? | prices — points to either course_id or program_id. |
| Where does the program’s content live? | In the referenced courses. The program is just the bundle. |
| Can the program have exclusive content? | Yes — model it as a course, mark it access_via: "program_only". |
Subscription covers
many SubscriptionItems, each pointing at a Price for a Product.
Here: a Program Cohort covers many referenced Courses. Same
pattern, different domain.
The pattern, in one screen
Across all three examples — wildly different domains, depth, and vocabulary — you used the same five primitives:Unit (nestable)
Top-level when you need flat structure,
parent_unit_id when you
need depth. No Section, no Epigraph, no domain subclasses.Lesson + Activity + Question
The leaves. Content, exercises, questions — typed by a
type
discriminator, not by separate resources.When you actually need more
The model is deliberately small. Reach for one of these only when the core primitives genuinely can’t express what you need:| You want to… | Use |
|---|---|
| Group multiple courses (master, itinerary, learning path) | programs (lightweight many-to-many) |
| Sell courses or cohorts | prices + checkoutSessions |
| Map content into a third-party LMS | external_ids + the relevant exporter (SCORM, Common Cartridge, LTI) |
| Type strongly the domain-specific fields | A companion package, not core SDK |
| Track competencies / outcomes | outcomes (CASE-aligned, v2) |
Where Program fits — and where it does not
Program exists in the SDK but it is not a mandatory hierarchy
level above Course. It is a lateral resource you reach for only
when you need it. Two patterns to keep straight:
- Grouping multiple courses into a sellable bundle (a master’s, a
specialization, a learning path) → use
programsas shown in Example 4. Lightweight, many-to-many with courses, with its own cohorts and prices. - Domain hierarchy inside a single course (e.g. a SEPE
especialidad containing módulos formativos → unidades →
epígrafes) → that is not a program. It is a single
Coursewith nestedUnits, as shown in Example 2.
Program — just use the Course directly. Like Stripe doesn’t force
you to wrap every Product in a ProductCatalog, Hawkings doesn’t
force you to wrap every Course in a Program. Reach for it when
you’re actually bundling.