Authoring lessons
These endpoints build and maintain lessons: creating one from resources or topics, changing its settings, what it teaches, who can take it, its tags and its picture, writing its material again, splitting one that covers unrelated subjects, and closing, reopening or deleting it. The reads that go with them are under Lessons. Requiring a lesson of people is a separate act, under Assigning training.
They need a token granted the lesson:write scope, which is an OAuth path
today. It can be granted by an organization admin or a team admin. The person
behind the token must also be allowed to do the act in Mereon, checked on every
call, so a demotion takes effect on the next one.
Which lessons you can change
Section titled “Which lessons you can change”A lesson you name has to be one you can find through
GET /v1/lessons, or the call is a 404. It also has to be one
you may edit (or delete, for a delete), as in the lesson editor, or the call is
a 403. An organization admin can change any lesson; a team admin can change the
lessons shared with the teams they administer.
The ids a body names have to be ones the reads give you. A resource has to be
published, finished indexing and one you can open; a topic has to be live and
readable by you. Anything else is a 404 that names the ids it could not use,
without saying why.
A topic also has to fit who the lesson is for, as in the lesson editor. A topic
brings its resources into the lesson’s material, so a lesson for everyone can
only take topics whose resources everyone can open, a lesson for some teams only
topics those teams can open, and an admins-only lesson no topic with a resource
restricted to a team. A topic that does not fit is a 400. The same check runs
when a lesson is shared with everyone.
The lesson
Section titled “The lesson”Most of these return the lesson as it now stands, the same shape
GET /v1/lessons/{id} reads, so you do not have to read it
back. Creating a lesson, or changing what it teaches or its difficulty, starts
writing its material, which takes a few minutes. The lesson exists at once, and
its material follows.
Create a lesson
Section titled “Create a lesson”POST /v1/lessons
{ "title": "Forklift safety", "visibility": "teams", "teamIds": ["…"], "contentIds": ["…", "…"], "topicIds": ["…"]}| Field | Type | Notes |
|---|---|---|
visibility |
string | Required. Who can take it: everyone, teams or admins. Only an organization admin may use everyone or admins. |
teamIds |
string[] | With teams only. Up to 50, from GET /v1/teams. A team admin names teams they administer. |
contentIds |
string[] | Resources from GET /v1/content, in the order they are taught. Up to 50. |
topicIds |
string[] | Topics from GET /v1/topics, taught after the resources. Up to 50. Give at least one resource or topic. |
title |
string | One line, up to 255 characters. Kept as given. Leave it out and the lesson is “New Lesson” until its material is read and it is named from it. |
format |
string | GUIDED (the default) re-teaches the material with questions, in an order the model decides. CURATED presents the resources as they are, in the order given, with practice questions between them. It cannot be changed later. |
difficulty |
string | SIMPLE (the default), MODERATE, CHALLENGING or ADVANCED. MODERATE and CHALLENGING need a plan that includes advanced questions, and ADVANCED one that also includes text simulations. |
thoroughness |
string | MINIMAL, STANDARD (the default), THOROUGH or INTENSIVE. The last two need a plan that includes advanced thoroughness. |
requiredContentIds |
string[] | CURATED only: the resources in contentIds a learner has to finish before moving on. |
Sharing a lesson with a team also lets the team open the resources it teaches from. A lesson for everyone cannot teach from a resource restricted to teams. Creating a lesson assigns it to nobody.
Change a lesson
Section titled “Change a lesson”PATCH /v1/lessons/{id}
{ "difficulty": "MODERATE" }Any of title, description, difficulty, thoroughness and
simulationsEnabled; only what is given changes, and at least one is needed. A
new difficulty writes the quiz plan and the material again. A new thoroughness,
or turning simulations on or off, writes the quiz plan again. The format is
fixed when the lesson is created, and sending it is a 400.
Replace what a lesson teaches from
Section titled “Replace what a lesson teaches from”PUT /v1/lessons/{id}/content
{ "contentIds": ["…", "…"], "topicIds": ["…"] }contentIds replaces the lesson’s resources, in the order given. topicIds
replaces its topics the same way; leave it out to keep the ones it has. This
replaces rather than adds, so read the current list first and send the whole
list you want. A topic already on the lesson may stay after it was retired. A
lesson has to keep at least one resource or topic; to withdraw it, close it.
requiredContentIds works as on create, for a curated lesson.
When what the lesson teaches from changed, its quiz plan and material are written again.
{ "lesson": { "id": "…", "title": "Forklift safety", "…": "…" }, "retainedTeamAccess": [ { "contentId": "…", "title": "Old dock procedure", "teams": [{ "id": "…", "name": "Dock crew" }] } ]}A team the lesson is shared with keeps access to a resource taken out of it.
retainedTeamAccess lists those, so you can change the resource’s own sharing
in Mereon if they should not. Below organization scope it names only your own
teams.
Change who a lesson is shared with
Section titled “Change who a lesson is shared with”PUT /v1/lessons/{id}/sharing
{ "visibility": "teams", "teamIds": ["…"] }Who can take the lesson from now on, replacing who could before: everyone,
the teams in teamIds, or admins only. The rules are the ones on create. A
team admin changes only the teams they administer; a lesson’s other teams are
left as they are. This decides who can take a lesson, not who has to.
Set a lesson’s tags
Section titled “Set a lesson’s tags”PUT /v1/lessons/{id}/tags
{ "tags": ["forklifts", "warehouse"] }The tags the lesson carries from now on, by name, replacing the ones it had; an
empty list clears them. Up to 50. Each has to be one of your organization’s
tags, from GET /v1/tags: a name it does not have is a 400.
Make a new tag with POST /v1/tags.
Returns the lesson.
Write a lesson’s material again
Section titled “Write a lesson’s material again”POST /v1/lessons/{id}/regenerate
Starts writing the lesson’s material again from what it teaches from, for instance after its sources were corrected.
{ "lessonId": "…", "started": true }started is false when the material was already being written, in which case
nothing new was started.
Close a lesson
Section titled “Close a lesson”POST /v1/lessons/{id}/close
Withdraws a lesson the organization no longer teaches. It leaves the catalog and cannot be assigned, and everyone’s history with it is kept. Reopen it to put it back.
{ "reason": "Replaced by the 2026 course", "inFlightAccess": "keep" }| Field | Type | Notes |
|---|---|---|
reason |
string | Up to 2,000 characters, kept in the lesson’s history. |
inFlightAccess |
string | What happens to people part-way through. keep (the default) lets them finish. revoke stops them now and tells each of them. People who completed it are never touched. |
{ "lesson": { "id": "…", "status": "CLOSED", "…": "…" }, "stoppedCount": 0 }stoppedCount is how many people revoke stopped. Closing a lesson that is
already closed is a 409.
Reopen a lesson
Section titled “Reopen a lesson”POST /v1/lessons/{id}/reopen
Puts a closed lesson back in the catalog, where it can be assigned again. Find
closed lessons with includeClosed=true on
GET /v1/lessons. People stopped when it closed stay stopped,
and have to be assigned it again. Reopening an open lesson is a 409.
Delete a lesson
Section titled “Delete a lesson”DELETE /v1/lessons/{id}
Deletes a lesson nobody has been enrolled in, such as one made by mistake.
{ "lessonId": "…", "outcome": "deleted", "requiredCount": 2, "roleCount": 1 }A lesson can be required of people who have not started it yet, by their role
or an assignment. Deleting it cancels those requirements and takes it out of
those roles’ training. Neither stops the delete, since nobody’s record is
erased, but the response says how many: requiredCount people were still
required to take it, and roleCount roles included it. Tell your user when
either is not zero.
A lesson anyone has been enrolled in is refused with a 409
(LESSON_HAS_ENROLLMENTS) whose message gives the counts, because deleting it
would erase their progress, completions and scores. Close it instead. This is
the same rule in the Mereon app and the chat assistant.
A team admin does not delete a lesson: it stops being shared with the teams they administer and stays for everyone else.
{ "lessonId": "…", "outcome": "unshared", "teamIds": ["…"] }Split a lesson
Section titled “Split a lesson”A lesson whose topics fall into groups with nothing between them can be split into separate lessons, in two steps: ask for a plan, then apply it, edited or not.
Propose a split
Section titled “Propose a split”POST /v1/lessons/{id}/split-plan
Asks a model how to split the lesson, and returns the plan. Nothing changes.
{ "lessonId": "…", "summary": "The lesson covers forklift operation and loading dock safety, which share no topics.", "lessons": [ { "tempId": "a", "title": "Forklift operation", "description": "…", "rationale": "…", "topics": [{ "id": "…", "name": "Pre-shift inspection" }], "contents": [{ "id": "…", "title": "Forklift SOP", "type": "document" }] } ]}A lesson whose topics are all connected, or that has fewer than two, is a 400.
Only for someone who may edit the lesson and can open everything it teaches
from, which applying the plan needs.
Apply a split
Section titled “Apply a split”POST /v1/lessons/{id}/split
{ "visibility": "teams", "teamIds": ["…"], "lessons": [ { "tempId": "a", "title": "Forklift operation", "description": "…", "topicIds": ["…"], "contentIds": ["…"] }, { "tempId": "b", "title": "Loading dock safety", "description": "…", "topicIds": ["…"], "contentIds": ["…"] } ]}The part with the most topics is written into the lesson being split, which
keeps its id, its enrollments and its sharing, and from then on teaches that
part only. Every other part becomes a new lesson, shared as visibility says,
with the split lesson’s format, difficulty and thoroughness, and its resources
keep whether they had to be finished. Material is written for each. Between 2
and 10 parts, each with its own tempId; a part’s description may be empty,
as the plan can leave it.
The parts can only use the topics and resources the plan was made from, which
are the ones the lesson teaches: a part that names anything else is a 400. A
lesson for everyone cannot take a resource restricted to teams, and that
includes the lesson being split when it is already shared with everyone.
Everything is checked before anything is written, so a split is applied whole or
refused.
{ "lessons": [ { "tempId": "a", "lessonId": "…", "title": "Forklift safety", "url": "…", "kept": true }, { "tempId": "b", "lessonId": "…", "title": "Loading dock safety", "url": "…", "kept": false } ]}Needs permission to create lessons as well as to edit this one, and to open every resource named.
Choose a lesson’s picture
Section titled “Choose a lesson’s picture”The picture people see on a lesson card. It can be a resource the lesson already teaches from, or an image you upload. An uploaded image is what people see until you remove it. The resource choice stays, and shows again once the uploaded image is gone.
Choose a resource
Section titled “Choose a resource”PATCH /v1/lessons/{id}/poster
{ "posterContentId": "cnt_44de..." }posterContentId is a resource already in the lesson, from
GET /v1/lessons/{id}. null clears that choice. A resource
the lesson does not teach is a 400. Returns the lesson.
Upload an image
Section titled “Upload an image”POST /v1/lessons/{id}/poster-image
Send multipart/form-data with one field, file: a JPEG, PNG or WebP of at
most 10 MB, from portrait 1:2 through wide 4:1. Returns the lesson, with status
201. The uploaded image is what people see from then on.
Remove an uploaded image
Section titled “Remove an uploaded image”DELETE /v1/lessons/{id}/poster-image
Removes the uploaded image. The resource chosen as the picture, if any, is what people see again. Returns the lesson.
Errors
Section titled “Errors”| Status | When |
|---|---|
400 |
The body is invalid, teamIds does not match visibility, the lesson would have nothing to teach from, a resource is still indexing, a topic does not fit the lesson’s audience, a resource restricted to teams would go to everyone, the plan does not include the difficulty or thoroughness, a lesson cannot be split, a split part reaches past the lesson being split, the picture is not a resource in the lesson, the poster file is not a JPEG, PNG or WebP, the poster is over 10 MB, or the poster’s proportions are outside portrait 1:2 through wide 4:1. |
403 |
The token lacks lesson:write, the person may not do this act, may not change this lesson, or may not share with the audience asked. |
404 |
The lesson, a resource or a topic is not one you can see. |
409 |
The lesson is already closed or open, or people have been enrolled in a lesson you asked to delete. |
429 |
Over the rate limit. Retry-After says when to try again. |
Sharing, tagging, choosing a picture, uploading or removing one, closing, reopening and deleting are limited to 30 calls a minute and 300 an hour per connection. The MCP tools that do the same acts are held to the same numbers, counted per tool.