Bubble Sheet (OMR) Marking

Scan a stack of multichoice answer sheets. Get back per-student scores, QR-decoded identities, and crops of any ambiguous bubble — billed per sheet.

How it works

  1. Upload a single multi-page PDF stacking many students (one per page) or several individual files.
  2. The platform detects the template from the first page — anchor squares, footer metadata ("Total: 30 | Options: 4"), and optional QR code.
  3. For each page, the AI reads every bubble and reports the chosen letter, blank, or multi-mark.
  4. QR-decoded student identity populates `student_id` and `qr_payload` (e.g. `E10-S42`); printed name fills `student_name`.
  5. Each question is graded deterministically against the answer key.
  6. Ambiguous bubbles get a cropped PNG for manual review; manual override updates the score.

Highlights

  • Multi-student PDF — one upload, one student per page, dozens of submissions in one job.
  • QR-encoded identity — robust ground-truth student ID without OCR mistakes.
  • Self-describing template — no editor needed; the sheet's footer and anchors define the grid.
  • Multi-mark / blank detection — clearly distinguished from "unreadable", with reason codes.
  • Crops for ambiguous bubbles — render in your review UI for 1-click overrides.
  • Per-sheet billing — predictable: 1 quota unit per student sheet, regardless of question count.
  • Separate quota dimension — your package controls `bubble_sheets_included` independently of auto-grading and essays.
  • Audit trail — every job records `bubble_sheets_charged`, `bubble_quota_source`, `subscription_id`.

Quick start (cURL)

curl -X POST https://aiquestions.intrazero.com/api/v1/grading/bubble-sheets \
  -H "Authorization: Bearer $API_KEY" \
  -H "Accept: application/json" \
  -F "student_sheets[]=@CS201_midterm_stack.pdf" \
  -F 'answer_key_json=[{"number":"1","answer":"B"},{"number":"2","answer":"A"},{"number":"3","answer":"D"}]' \
  -F "subject=CS201 Midterm"

Example response

{
  "job_id": "uuid",
  "mode": "bubble_sheet",
  "status": "needs_review",
  "submissions_count": 21,
  "bubble_sheets_charged": 21,
  "bubble_quota_source": "subscription",
  "template": { "questions": 30, "options": 4, "exam_label": "E10" },
  "submissions": [
    {
      "student_index": 1,
      "student_name": "Test Student 1",
      "student_id": "22",
      "qr_payload": "E10-S22",
      "score": 18, "max_score": 30,
      "status": "completed"
    }
  ]
}

Reason codes

  • blank — no bubble filled (graded as 0, no review needed)
  • multi_mark — more than one bubble filled (graded as 0, no review needed)
  • ambiguous — confidence below threshold (status=`unreadable`, crop URL provided for manual review)
  • missing — question expected by the key but not detected on the sheet (status=`unreadable`)

Add-on feature

Bubble sheet marking is a separately-priced add-on. Your package needs bubble_sheet_marking_enabled = true with bubble_sheets_included > 0, or an entity-level extra_bubble_sheets pool.

Contact us to get started →