- Create a product
- Create lessons
- Set when lessons open
- Add images
- Add materials
- Add videos
- Group lessons into sections
- Activate the product
- Publish the miniApp and the web version
Before you start
You need:- An API token — see Authentication.
- The bot ID — from List bots. The miniApp and the web version belong to a bot.
- Your files:
- images (covers, notification pictures) — JPG, PNG, or WebP, up to 20 MB;
- lesson materials — up to 50 MB: PDF, DOC, DOCX, XLS, XLSX, PPT, PPTX, TXT, CSV, MP3, MP4, WAV, OGG, ZIP, RAR, JPG, JPEG, PNG, WebP;
- videos — MP4 files to upload, or links to YouTube, Vimeo, or Bunny videos.
1. Create a product
data.id as PRODUCT_ID. platform must be miniApp — that’s the miniApp product (the same product works in the web version). Product names are unique within a bot: a duplicate name returns 422 name. A new product is inactive; you activate it in step 8.
Optional product settings:
- Description — Update product with
description. - Cover — Update product cover, multipart
file: JPG, PNG, or WebP up to 20 MB. - Invite message — the message a subscriber gets in the bot when they get access to the product. Set it with Update product invite:
invite_textandinvite_button(required),invite_web_button(optional), button stylesprimary,success, ordanger. The message has a button that opens the miniApp; the web button is added only while the web version is published. A picture for it — Update invite image.
2. Create lessons
Create lessons with Create lesson. Each new lesson is added to the end of the product.data.id as LESSON_ID.
name— up to 255 characters, shown in the lesson list.text— the lesson body in HTML: paragraphs, bold, italic, links, lists. Up to 4000 characters of text, not counting tags.access_time— when the lesson opens, see step 3. If omitted, the lesson opens immediately.settings— the notification sent when the lesson opens, see Notify when a lesson opens.
lesson_ids in the new order), Duplicate lesson, Deactivate lesson, Delete lesson.
3. Set when lessons open
Each subscriber has their own schedule. It starts when the subscriber gets access to the product.access_time.type decides when each lesson opens for them:
Step by step: after_previous
immediately for the first lesson and after_previous for the rest to make a course that goes one lesson at a time.
After a delay: duration
delay_type is seconds, minutes, hours, or days. For “one lesson a day”, give lesson N a delay of N − 1 days.
On a date: date
At a time of day: time
delay_time after the subscriber got access: today if that time hasn’t passed yet, otherwise tomorrow. Only the time of day is used; pass it with an offset (without one it’s read as UTC). delay_number is required for this type — pass 0.
Manually: never
The lesson doesn’t open by itself. Open it for a subscriber with Set product lessons:
access_time type — for example, to give one subscriber all lessons at once.
Changing the schedule later
When you changeaccess_time or activate or deactivate a lesson, the schedule is recalculated for subscribers who already have access. You can add and reorder lessons in a running course.
When a subscriber completes all lessons, the product is finished for them.
Notify when a lesson opens
The bot can send a message when a lesson opens for a subscriber. Passsettings when you create or update the lesson:
lesson_open_text— Telegram HTML;{{lesson_name}}and{{product_name}}are replaced with the lesson and product names. If empty, a default text in the bot’s language is used.lesson_open_buttonopens the miniApp.lesson_open_web_buttonis added only while the web version is published.lesson_open_button_style,lesson_open_web_button_style—primary,success, ordanger.- The picture for the message is the lesson’s notification image, see step 4.
4. Add images
Both are multipart uploads of onefile: JPG, PNG, or WebP up to 20 MB.
5. Add materials
Materials are files or links listed under the lesson. Add them with Add lesson material, one per request. A file — multipartfile, with an optional display name (without it, the file name is shown):
name and url (http or https), no file:
name, and url for links), Reorder (media_ids in the new order), and Delete.
6. Add videos
A lesson has at most one video. Adding a video replaces the current one.- Upload a file
- Link to YouTube, Vimeo, or Bunny
The file goes directly to the video hosting over tus, a resumable upload protocol — not through the Zenedu API.1. Request an upload with Create video upload. The upload must finish before The response is The video moves from
file_size is the exact file size in bytes:expires_at.2. Create the tus upload. Send every header from upload.headers with each request:201 with a Location header. It may be a path (/tusupload/…) — then it’s relative to the endpoint host. Upload-Metadata values are base64-encoded (filetype video/mp4, title).3. Send the file:204 means the file is uploaded. Any tus client library works the same way: give it the endpoint, the headers, and the file.4. Report that the upload is complete with Complete video upload:uploading to processing.5. Wait for processing. The video goes uploading → processing → ready on its own, usually within a few minutes. Check with Get lesson video. ready means the video is processed and can be played; failed means the hosting couldn’t process the file — upload it again.7. Group lessons into sections
Sections are optional headings that group lessons in the miniApp and the web version. Create one with Create section:- Update section —
nameandlesson_ids. Thelesson_idsyou pass replace the section’s lessons. - Reorder sections —
section_idsin the new order. - Delete section.
8. Activate the product
9. Publish the miniApp and the web version
Products and lessons are shown on two platforms:
Both platforms show the same products and lessons, and you don’t add products to them: the Content section of each lists every active miniApp product the subscriber has access to. Bot products are listed too only if
show_bot_products is on in the platform settings.
Each platform is configured and published separately: you can run only the miniApp, only the web version, or both.
Draft and published version
Each platform has a draft and a published version. Settings, menu, profile, logo, and onboarding changes go to the draft; subscribers see them only after you publish. Products, lessons, and their content are not part of the draft — they change for subscribers right away.- Call Get miniApp or Get web version first: it prepares the platform if it hasn’t been set up yet and returns
is_published,has_changes(the draft differs from the published version),app_url, and the settings. - Change the draft.
- Publish: Publish miniApp or Publish web version.
DELETE …/publish). The draft stays; subscribers who open an unpublished platform see it as disabled.
Settings
Update miniApp settings and Update web settings take the same fields, set per platform:brand_name,open_button_name,appearance(system,light,dark),brand_color(#RRGGBB),disable_branding;show_bot_products— also list bot products in Content;support_type(botorlink) andsupport_text— the Support item;phone_login_enabled— web version only: sign-in with a phone number.
Menu and profile
Each platform has its own menu and profile items: Content, Shop, Subscriptions, Support, and others. You can add your own links and a Shop item (create menu item, create profile item, the same under/web), reorder, update, move between menu and profile, and delete links. A subscriber without products opens the Shop first if it has offers; otherwise Content.
Onboarding
Onboarding screens are shown on the first open of the miniApp only — see Create onboarding screen. The web version has no onboarding.Web address
The web version has an address on a Zenedu subdomain. Change it with Update web address:web_handle— 3–32 characters: lowercase letters, digits, and hyphens, not at the start or end. It must be unique; some names are reserved.custom_domain_enabled— serve the web version on your own domain. Requires a paid Pro plan, otherwise the request fails; the domain itself is set in the dashboard.
app_url in Get web version.
Things to keep in mind
- Lessons are created inactive. Pass
"active": trueor activate them — otherwise subscribers see an empty product. - The schedule starts with access.
durationandtimecount from the moment the subscriber got access, not from the previous lesson. For one lesson after another, useafter_previous. - Videos aren’t ready immediately. An uploaded video is
uploading, thenprocessing, and can be played once it’sready.