Using Chalkmind in an app (Shikhar as the example)
An app that teaches, such as Shikhar, a school's app or a trading academy's site, is a host. It uses Chalkmind through the API alone, with its own key, like any customer. Nothing of the host lives in Chalkmind, and nothing of Chalkmind lives in the host: the host's server keeps the key and calls the API, and the host's app shows the board.
Once: the host's account and teachers
- The tenant. The owner makes the host's tenant (
POST /v1/tenants {"id": "shikhar", "name": "Shikhar"}). The answer's key manages the tenant; it stays on the host's server, never in the app. - One teacher per class and subject. The host makes them from the templates. Each one gets the class's library by chapter, the shared textbook, and the host's own notes:
`` POST /v1/teachers {"id": "shikhar-std10-science", "from": "std10-science", "name": "Std 10 Science", "names": {"gu": "ધોરણ 10 વિજ્ઞાન"}, "lang": "gu", "record": true, "notes": "GSEB Gujarati medium. Exams in March: board-exam style answers."} ``
A teacher of the host's own (spoken English, a crash course) is made the same way, without from.
- Books. If the host has its own notes for a teacher, it adds them as that teacher's book (
POST /v1/teachers/ID/book). - The year plan. The host's syllabus with its dates, for every batch of a teacher, or for one batch:
`` PUT /v1/teachers/shikhar-std10-science/plan {"chapters": [{"n": 1, "title": "Chemical reactions and equations", "by": "2026-06-30"}, {"n": 2, "by": "2026-07-15"}, …]} ``
Who is taught: the learner
Every lesson names its learner. The teacher's memory (its journal) is kept per teacher and per learner:
- A class or batch:
"learner": "batch-2026-10a". Every lesson of Std 10 Science with that batch goes into that teacher's journal with the batch. - One student (a one-to-one doubt class):
"learner": "student-<the host's student id>".
The host chooses its own ids, and they never leave its tenant.
A live class
- The host's server starts it, as a teacher, on a channel of its own:
`` POST /v1/board/shikhar-10a-science/command {"text": "Chapter 13: magnetic effects of electric current, the electromagnet", "teacher": "shikhar-std10-science", "learner": "batch-2026-10a", "chapter": 13, "fresh": true} ``
The teacher is told its journal with the batch before it starts: what it taught them, when, how often and how, what was shaky, and what the last lesson left out. With chapter, the batch's year plan counts the chapter as taught, and the teacher hears where the batch is in it.
- The host's server makes a viewer token for each student (
POST /v1/tokens {"channel": "shikhar-10a-science"}) and gives it to the app. - The app shows the board.
- The Flutter app uses
BoardView(channelUrl: 'https://DOMAIN/board/shikhar-10a-science', token: …). - A web page uses
https://DOMAIN/board/shikhar-10a-science/display?t=…&name=Ravi.
- The Flutter app uses
Students hear the teacher, see the board, answer its questions on their screens, and ask their own, typed or spoken.
- Homework. At the end of a full lesson the teacher sets homework, and the students' screens show it. They answer there, or in the host's app (
POST /v1/homework/ID/answers, with a photo of the notebook). It is marked, each student's marks become their notes, and the host hearshomework.checked. The next lesson starts from how the batch did. A chapter test is given the same way ("kind": "test", or "give a chapter test"): timed on the screens, or on paper, with photos of the answer sheets, and a paper to print (GET /v1/homework/ID/paper). - Recording. A teacher with
"record": trueis recorded. So is any class whose record button is pressed: the host's own console, orPOST /v1/board/CH/record. After the class,GET /v1/lessons/ID/recordinggives the video, for students who missed it.
Recordings in the host's own storage (Shikhar's R2)
Chalkmind makes the video; the host keeps it, in its own storage, and shows it in its own app.
- Record.
"record": trueon the class (or on its teacher), or the record button (POST /v1/board/CH/record {"on": true};{"on": false}stops it). - Ready. When the class ends, the video is made (only the parts recorded). The host's server hears
recording.ready:{lesson, channel, title, teacher, learner, started_at, duration_s, seconds, bytes, url}. - Copy. The host's server fetches
urlwith its key (GET /v1/lessons/ID/recording: an MP4, in byte ranges if it likes), and puts it into its own storage (Shikhar: its R2 bucket), under a name of its own. - Delete here.
DELETE /v1/lessons/ID/recording: Chalkmind keeps no copy. - Show. The host's app lists its class recordings from its own storage, with the title, teacher, batch, date and length the webhook gave.
// the host's server, on Chalkmind's webhook (check X-Chalkmind-Signature first)
if (event === 'recording.ready') {
const video = await fetch(`${CHALKMIND}${data.url}`, { headers: { Authorization: `Bearer ${KEY}` } });
await myR2.put(`class-recordings/${data.learner}/${data.lesson}.mp4`, video.body); // your own R2 client
await fetch(`${CHALKMIND}/v1/lessons/${data.lesson}/recording`, { method: 'DELETE', headers: { Authorization: `Bearer ${KEY}` } });
// and keep {title, teacher, learner, started_at, seconds} in your database, for the app's list of recordings
}
The element and the client
- On a web page: one element shows the board and tells the page what happens on it.
``html <script type="module" src="https://DOMAIN/web/embed.js"></script> <chalk-board channel="shikhar-10a-science" token="v1.…" learner="student-17" name="Ravi"></chalk-board> ``
It sends these events: chalk-lesson, chalk-ended, chalk-expired, chalk-answered {ask, choice, right}, chalk-asked, and chalk-recording. Try it at /web/embed-demo.html?channel=…&t=…&name=….
- In an Android WebView (the board page as the top-level page): there is no
<chalk-board>around it, so the page sends each event itself, as the message{source: "chalkmind", type, channel, ...data}(typeislesson,ended,expired,answered,asked,recording,turn,controls…, the same as thechalk-*events without the prefix). It goes four ways at once, use whichever suits the app:ChalkmindBridge.postMessage(text)andShikharBridge.postMessage(text)if the app added such a JavaScript channel (textis the message as a JSON string);window.postMessage(msg, '*'); and achalkmindCustomEvent onwindowwith the message indetail. - On the host's server:
web/chalkmind.jsis the API as methods (Node 18+):
``js import { Chalkmind } from 'https://DOMAIN/web/chalkmind.js'; const cm = new Chalkmind({ key: process.env.CHALKMIND_API_KEY, site: 'https://DOMAIN' }); await cm.classes.start('shikhar-10a-science', { teacher: 'shikhar-std10-science', text: 'Chapter 13', minutes: 40, record: true }); const { token } = await cm.tokens.create('shikhar-10a-science'); ``
Its parts are teachers, classes, tokens, lessons, learners and account. A refusal throws a ChalkmindError with the HTTP status.
- A live class. Open the board with
?chat=0&learner=ID&name=NAMEand send hands, "explain again", "understood" and questions from your own bar toPOST /v1/board/:ch/react?t=…and/doubt?t=…withwho= the same id: the board takes each as a student's turn, by name, at the next sentence (API.md, "A live class of the board's own teacher"). Listen tochalk-turn(open your question box for the student whose turn it is),chalk-controls(grey your buttons; a call while one is off gets 409), and read points withGET /v1/points?class=…. On an upright phone the board sits at the top of the frame. When the lesson ends the frame shows «વર્ગ પૂરો થયો» by itself (chalk-ended). Your pages may frame/board/:ch/display(frame-ancestors). - Tools for a live class: hold and resume (
POST /v1/board/:ch/hold,/resume), end now (/end), what the class may use (/controls), a longer or shorter class (POST /v1/board/:ch/minutes {minutes}, "+10 min"), and the class's state (GET /v1/principal/status: students, minutes left, teaching or stalled). - A class nobody can pass round. Give each student their own token:
POST /v1/tokens {channel, until, learner, screens: 1, mark: "Riya 1234"}. A link copied out of the app then opens on one screen only (a second closes the first, and you hearscreen.shared), it speaks only for that student, and their mark moves faintly over the board. - The Principal. Your server only listens:
class.started,class.stalled,class.recovered,class.errorandclass.ended(webhooks). It may look (GET /v1/principal/status) and, with a manage key, take a step by hand (POST /v1/principal/act {"class": "shikhar-t-123", "step": "nudge"}). See API.md, The Principal. - A student's screen for one class only. Make its viewer token for that class's channel, until the class's end:
cm.tokens.create('shikhar-t-123', { until: '2026-11-07T11:00:00+05:30' }). An open screen is closed at that time (chalk-expiredon the<chalk-board>), and the token opens no other channel and nothing after it.
- A class's time. With
minutes(5 to 240), the teacher wraps up in the last 3 minutes (your server hearslesson.ending), and the board stops the class at its end, a hard stop (lesson.endedwith"reason": "time"). After it, lines are refused and a student's question starts no new lesson. The board's own teacher ends the class itself once its goodbye is said ("reason": "teacher"): it does not wait for the last minute. Either way every student's screen shows that the class has ended, and a recorded class becomes its video (recording.ready), for your server to copy to its own storage.
What the host can show its own teachers and admins
| What | Route |
|---|---|
| The classes taught, with their teacher, batch, length and recording | GET /v1/lessons |
| What a teacher taught a batch, and how the batch did | GET /v1/teachers/ID/journal?learner=batch-2026-10a |
| Where a batch is in the year plan: chapters taught, next, and behind | GET /v1/teachers/ID/plan?learner=batch-2026-10a |
| Each student of a batch, and how they have done (answers, latest answers, what is shaky) | GET /v1/learners/batch-2026-10a?students=1 |
| A batch's homework, and each student's answers and marks | GET /v1/homework?class=batch-2026-10a, GET /v1/homework/ID |
A batch's week, and each student's (a page for parents: ?format=html); every Sunday by report.weekly | GET /v1/reports/class/batch-2026-10a, GET /v1/reports/student/ID |
Questions flagged: not about the lesson, unkind, personal, or a concern (a student who may need help; question.flagged at once) | GET /v1/flags?class=batch-2026-10a |
A teacher strictly by its syllabus: "scope": "book" on the teacher, "chapter": N on each lesson. Outside the book: one fixed line, flagged off_topic | PATCH /v1/teachers/ID {"scope": "book"} |
| What the teacher noted about a student or a batch | GET /v1/learners/ID |
| Minutes and cost this month | GET /v1/usage |
| Its teachers, their books and library | GET /v1/teachers, GET /v1/teachers/ID |
A host's admin page (for example, Shikhar's) is made from these: each teacher with its batches, what it has taught, the recordings, and the minutes used.
The rules a host keeps
- The key stays on the host's server. The app only ever gets viewer tokens.
- Its own channel prefix. Every channel starts with the host's prefix (
shikhar-…). Keys can be narrower: a key for one school, or one batch. - Its own student ids. A learner's id is the host's own id for the student or batch.
DELETE /v1/learners/IDforgets a student who leaves.