Skip to main content

Integration guide: building a mock-exam flow

This walks through a complete, production-shaped integration: let a student pick an exam, sit a timed past paper, and see a scored result — backed by verified MirrorMingo content.

We use the TypeScript SDK; the shape is identical in every language.

Architecture

Browser / app ──► Your backend ──► MirrorMingo Exam API
(holds the API key)

Keep the API key on your server. The browser talks to your backend; your backend talks to MirrorMingo. This keeps the key secret and lets you add your own auth, caching and analytics.

MirrorMingo School Intelligence is not part of this public API. It remains a school-subscription surface inside the MirrorMingo school dashboard.

1. Let the student choose an exam

// GET /api/exams (your backend)
app.get("/api/exams", async (req, res) => {
const exams = await mm.exams.list({ country: req.query.country as string });
res.json(exams);
});

Exam metadata (names, sections, scoring) changes rarely — cache it for a day.

2. List verified past papers

app.get("/api/exams/:examKey/papers", async (req, res) => {
const catalog = await mm.mockExams.listPapers(req.params.examKey);
res.json(catalog);
});

Each paper has a stable paper_id grouped under a year. Render a year picker.

3. Start a timed attempt

app.post("/api/exams/:examKey/start", async (req, res) => {
const attempt = await mm.mockExams.open(req.params.examKey, {
paperId: req.body.paperId,
questionLimit: 20,
learnerId: req.user.id,
});
// Persist attempt.attempt_id against the signed-in student so a refresh
// resumes the same attempt. Start your countdown from the exam's duration.
res.json(attempt);
});

For long papers, page with mm.mockExams.questions(examKey, { paperId, questionOffset, learnerId }). has_visual: true marks questions that reference a figure — render a note for those.

4. Submit and score

app.post("/api/exams/:examKey/submit", async (req, res) => {
const result = await mm.mockExams.submit(req.params.examKey, {
attemptId: req.body.attemptId,
answers: req.body.answers, // { [question.answer_id]: "C", ... }
status: req.body.timedOut ? "expired" : "submitted",
learnerId: req.user.id,
});
res.json(result.feedback);
});

feedback carries the score and per-question correctness/explanations.

5. (Optional) React to scoring with webhooks

Instead of blocking the UI on scoring, subscribe to mock_attempt.scored and update the student's dashboard when the event arrives — see Webhooks.

Production checklist

  • API key stored in a secret manager, never shipped to the client
  • Exam/paper metadata cached (≥1 day)
  • attempt_id persisted per student so attempts survive a refresh
  • learnerId passed consistently for open, page, submit and history calls
  • Server-side countdown; submit with status: "expired" on timeout
  • Handle 429 with backoff (the SDK does this automatically)
  • Sandbox used in CI; production key only in prod