Marksy

Grades IB coursework (IA, EE, TOK) against the official criteria before the student submits it.

category
$ product
status
live
active
2025-07 → 2026-09 · 373 commits
made
◧ started by hand then kept building with agents · started by hand, continued with agents
numbers
● 3.9k users (live) · ● CA$374 this month (live) · ● CA$1.9k best month (apr 2026)
stack
next.js · trpc · prisma · postgres · stripe · llm apis
links
marksyib.com ↗
timeline
·

Main feature that my customers use

An IB student uploads a draft of an Internal Assessment (IA), an Extended Essay (EE) or a Theory of Knowledge (TOK) essay. Marksy gives an estimated mark for each criterion.

Each mark comes with the words in the draft that support it. Then Marksy gives a short list of changes, with the most valuable change first.

The criteria come from the official IB subject guides. Marksy has criteria for more than 15 subjects. A free account gets 5 gradings each month. Bulk grading takes several drafts at once.

Marksy result: marks per criterion with highlighted evidence in the draft
fig. 01 · one criterion, with the evidence highlighted in the draft · 2026
Marksy fix list next to the draft
fig. 02 · the fix list · 2026

The other tools

Marksy started as one grader. The same students needed more tools, so it grew:

  • Oral practice. A timed oral, follow-up questions from the model, and feedback against the oral criteria.
  • Textbooks for Physics and Biology, SL and HL, with simulations inside the chapters.
  • A source checker. It tells the student which sources to keep, review or replace.
  • A subject picker, an exam timetable, a predicted-grade calculator and an IB-to-GPA calculator.
Marksy oral practice results
fig. 03 · oral results · 2026
Marksy textbook chapter with a projectile simulation
fig. 04 · a simulation inside a physics chapter · 2026

Past papers

From March to August 2026, Marksy also had IB past papers. A student opened a real paper in the browser and answered each question in text or with a photo of handwritten working. Marksy marked the answers against the official markscheme. A student could practise one question at a time or sit the full paper with a timer. The catalog had 81 papers across 26 subject and level combinations.

This feature no longer exists because of legal issues.

Ingestion

Each paper came in as two PDFs, the exam and its markscheme. Some papers had a third PDF, for example a resource booklet. A script turned each pair into one bundle in 6 steps:

  1. Extract the exam. PyMuPDF reads the text of each page. A vision model (Gemini 3.1 Flash Lite) gets each page as an image and returns bounding boxes for the figures, graphs, tables and multiple-choice panels, each with a type and a short description. QR codes, redactions and blank areas get their own types, so later steps can ignore them.
  2. Extract the markscheme text the same way, without the figures.
  3. Structure the exam. Gemini reads the PDF natively and writes a markdown draft. GLM 5.1 compiles the draft into an anchor format. Each question sits between {{question_start:q1}} and {{question_end:q1}}. Each marked subpart gets a {{response:...}} anchor where the answer goes. Each figure gets an {{asset:...}} anchor where the question mentions it. Quality gates check the output for missing anchors, summarised text and broken LaTeX. A failed output goes back to the model with the list of problems.
  4. Structure the markscheme into marking groups. A group holds the parts that share marks, for example when a later part can get follow-through marks from an earlier part.
  5. Link the markscheme to the exam. Most references match by question number. A model resolves the rest.
  6. Write the bundle, with a list of issues such as unmatched figures and unlinked marks.

PyMuPDF reads the text of each page. A vision model draws a box around each figure and gives it a type.

exam · pdf page1.(a)(b)2.ABCDgraphmcq panelqr code · ignoredvision modelgraphcurve of f, two turning pointsmcq paneloptions A to Dqr codeignored by later steps## 1The graph of f is shown below.![graph](p1-graph-1)(a) Find f′(x). [2](b) Find the stationary points. [3]## 2Which is the derivative of eˣ?A · B · C · D{{question_start:q1}}The graph of f is shown below.{{asset:p1-graph-1}}(a) Find f′(x). [2]{{response:q1__part_a:structured:auto}}(b) Find the stationary points. [3]{{response:q1__part_b:structured:auto}}{{question_end:q1}}{{question_start:q2}} …{{response:q2:mcq:auto}}{{question_end:q2}}quality gate· every part has an answer anchor· text is verbatim· latex parsesback to the model:q1 (b) has no response anchorattempt 2 · passedmarkscheme1af′(x) = 3x² − 3M1 A11b3x² − 3 = 0M1x = ±1A1 A1 ft2Cgroup g1 · q1__part_a, q1__part_b · 5 marksmcq key · q2 → C · no modellinked · same question numbermodel_linked · a model matched itbundlequestions2answer anchors3figures1marking groups1mcq keys1links2 linked · 1 model_linkedissuesnone
fig. 05 · one paper through the 6 steps. click a step to jump to it. the paper is made up, the anchors are the real format

Figure extraction took the most attempts. I tried pdffigures2, DeepFigures and Mistral OCR before I used a vision model to draw the boxes.

I ingested the papers in daily batches, newest exams first, with up to 6 papers in parallel.

Marking

Multiple-choice answers were checked against the answer key, without a model. Each other answer went to a vision model (Qwen3 VL) together with its marking group. Up to 10 groups were marked in parallel. The prompt held the markscheme for the group and the IB marking conventions: method, accuracy and reasoning marks, follow-through, mark caps and the rules for which marks depend on others. An empty answer got 0 without a model call.

How it started

I made the first commit on 2025-07-03. The first grading worked on the next day. Stripe billing followed three days later.

In December 2025, I added shareable result links and a history of past runs. Students can go back to old results. I also plan to use the public links for search traffic.

In March 2026, I added comparison pages: Marksy against a competitor, competitor against competitor, and alternatives to each competitor. A template makes all of them from one data set.

Around the model

My opinion: Marksy is an LLM wrapper. All of the work is in the parts around the model.

About 10% of the model responses that must be JSON are not valid JSON. Every response goes through two layers of repair:

  1. A deterministic repair with jsonrepair.
  2. If that fails, a second model gets the broken output and repairs it. It gets 3 attempts. It can also return an abort keyword when a faithful repair is not possible.

After the repair, the server rejects results that give a criterion a score of 0 out of 0. A failed grading does not count against the usage limit of the student.

At first, I billed plan changes at the start of the next month. That was a mistake. A plan change must be billed when it happens.