Skip to content
Scholametrix

Developer API

Add AI marking to your product in an afternoon

A small REST API with signed webhooks. Send written answers with a rubric; get back marks per criterion, highlight positions and reasons you can show teachers straight away.

Getting connected

1 · Get a key

We create a project for you and send its API key privately. Check it with GET /v1/me.

2 · Register your webhook

PUT /v1/webhook with your URL; you receive a signing secret for verifying results.

3 · Submit answers

POST /v1/evaluations per question. Results arrive at your webhook, or sync them with GET.

Submit answers, get structured results

Answers are idempotent per externalId, so retrying a request never marks twice. Use your exam ID as the groupId to follow progress and costs per exam.

POST https://api.scholametrix.tech/v1/evaluations
Authorization: Bearer $SCHOLAMETRIX_API_KEY
Content-Type: application/json

{
  "groupId": "cse-3101-midterm",
  "question": {
    "externalId": "q2",
    "text": "Explain how TCP provides reliable delivery.",
    "modelAnswer": "Sequence numbers, cumulative ACKs, retransmission…",
    "maxMarks": 10,
    "markStep": 0.5,
    "rubric": {
      "criteria": [
        { "id": "c1", "criterion": "Sequence numbers", "marks": 2 },
        { "id": "c2", "criterion": "Acknowledgements", "marks": 2 }
      ]
    }
  },
  "answers": [
    { "externalId": "ans-1029", "text": "TCP gives each byte a sequence number…" }
  ]
}

The result

Highlights are character offsets into the answer text, ready to render.

{
  "id": "1c0d…",
  "externalId": "ans-1029",
  "groupId": "cse-3101-midterm",
  "status": "completed",
  "maxMarks": 10,
  "suggestedScore": 8,
  "criteria": [
    { "id": "c1", "criterion": "Sequence numbers", "max": 2, "awarded": 2,
      "reason": "States sequence numbers allow reordering and detecting missing data." },
    { "id": "c5", "criterion": "Flow control", "max": 2, "awarded": 0,
      "reason": "Does not mention flow control or a sliding window." }
  ],
  "summary": "Explains sequence numbers, ACKs, retransmission and checksum, but omits flow control.",
  "highlights": [
    { "start": 0, "end": 133, "kind": "strength", "criterionId": "c1" }
  ],
  "missing": [{ "criterionId": "c5", "note": "No mention of flow control or sliding window." }],
  "review": null
}

Reference

Endpoints

MethodPathWhat it does
GET/v1/meCheck your key; see your project, plan and this month’s usage
PUT/v1/webhookRegister where results are sent; returns your signing secret
POST/v1/rubrics/draftDraft a rubric from a question and model answer
POST/v1/evaluationsSubmit one question with up to 500 answers
POST/v1/evaluations/tryMark one sample answer straight away (nothing stored)
GET/v1/evaluations/:idOne result: criterion marks, highlights, reasons
GET/v1/evaluationsList results by group, updated since a time (for sync)
GET/v1/groups/:groupId/progressHow far marking of an exam has got
POST/v1/evaluations/:id/reviewRecord the teacher’s decision and final marks
POST/v1/evaluations/:id/rerunMark an answer again
GET/v1/usageAnswers and rubric drafts for a period
GET/v1/billingYour charges per month, split by your groups

Webhooks

Signed, retried, safe to replay

  • Every delivery carries X-AMS-Signature: an HMAC-SHA256 over the timestamp and raw body.
  • Reply 2xx within 10 seconds; failures retry with exponential backoff, up to 8 attempts.
  • Deliveries can repeat or arrive out of order, so upsert by evaluation id.
  • Prefer polling? GET /v1/evaluations?groupId=…&updatedSince=… returns the same data.
import { createHmac, timingSafeEqual } from 'node:crypto';

// X-AMS-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
export function verify(secret: string, rawBody: string, header?: string) {
  if (!header) return false;
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Limits

Generous defaults

Answers per request
500
Characters per answer
20,000
Highlights per answer
Up to 8
Rubric criteria
Up to 12

Full API documentation is shared with every project. Ask for access.

Building an LMS or exam product?

Tell us about your platform. We’ll set up a sandbox project and help you ship AI marking to your users.