Skip to content

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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 /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": ["…"] }

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.

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.

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.

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.

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.

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.

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.

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.