Developers

Kerata API

Check code for AI from scripts, CI or your learning platform. A small REST API over HTTPS that returns JSON.

Quick start

Create a key under Account → API keys, keep it in an environment variable, and call the API with it:

Shell
export KERATA_API_KEY="paste your key here"
curl -s https://kerata.org/api/v1/me -H "Authorization: Bearer $KERATA_API_KEY"

Base URL: https://kerata.org/api/v1. Requests and responses are JSON unless an endpoint takes a file upload.

Authentication

Send your key in the Authorization header as a bearer token (X-API-Key: <key> works too). A key acts as you, inside the workspace that was active when you created it. Keys are shown once, when you create them, and Kerata stores only a hash; if you lose one, revoke it and create another. Keep keys out of source control and client-side code.

HTTP
Authorization: Bearer <your API key>

Limits

Kerata is free during the public beta. Every account gets the same allowance:

Requests a minute, per keyAPI calls a monthChecks a monthClass checks a monthStudents per class check
102,0002,00020400

Over the per-minute limit you get 429 with a Retry-After header; over a monthly allowance, 402. Your current usage is on Usage & plan and in GET /api/v1/me.

Errors

Errors return a JSON body {"detail": "a readable message"} and a meaningful status code:

400The request is malformed (for example an unsupported URL).
401The key is missing, revoked or mistyped.
402A monthly allowance is used up.
403The key's account isn't allowed to do this.
404Not found, or a result your key can't see.
409The result isn't ready yet; keep polling.
413The upload is too large.
422The input isn't valid, or the text isn't source code.
429Too many requests, or the service is busy; retry after the delay given.
503Checking is temporarily unavailable; try again shortly.

Scans

GET/api/v1/me

Who the key belongs to

The key's owner, workspace, plan, and this month's usage and limits.

POST/api/v1/scan

Check pasted code

Body: code (the source, at least 5 lines) and an optional filename, which helps detect the language. The result is saved to your history.

Shell
curl -s https://kerata.org/api/v1/scan -H "Authorization: Bearer $KERATA_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile code solution.py '{code: $code, filename: "solution.py"}')"

POST/api/v1/scan/upload

Check a source file or a .zip of a project

Multipart form with a file field. A single file can be up to 1 MB (its first 200 KB is checked); a zip can hold up to 80 source files.

Shell
curl -s https://kerata.org/api/v1/scan/upload -H "Authorization: Bearer $KERATA_API_KEY" -F file=@project.zip

POST/api/v1/scan/github

Check a public GitHub repository, folder, pull request or commit

Body: url. For a pull request or commit, only the lines it adds are scored. Add "history": true to also look at the repository's commit history; the response then includes history_job, which you poll at /api/v1/jobs/{id}.

Shell
curl -s https://kerata.org/api/v1/scan/github -H "Authorization: Bearer $KERATA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://github.com/owner/repo/pull/123"}'

Class checks

A class check compares a whole assignment: AI likelihood for every submission, similarity between students, matches against your past terms and, when the export has several attempts, edit history. It runs in the background: start it, then poll until it's done.

POST/api/v1/class-checks

Start a class check

Multipart form. file is the submissions export: a zip with a folder or file per student, a Canvas “Download Submissions” zip, a Gradescope export, or PrairieLearn downloads.

FieldDefaultWhat it does
filerequiredThe submissions export (.zip, or a PrairieLearn .csv)
starternoneStarter code to leave out of the similarity comparison
extranonePrairieLearn submissions CSV, which adds attempt times
assignmentfile nameA name for the check
peer, ai, history, corpus_pasttrueWhich checks to run
langautoForce a language
course_id, assignment_idnoneFile the check under one of your courses
Shell
curl -s https://kerata.org/api/v1/class-checks -H "Authorization: Bearer $KERATA_API_KEY" \
  -F file=@hw3_submissions.zip -F starter=@starter.zip -F assignment="HW 3: linked lists"
# {"id": "u3x...", "status": "queued", "url": "/api/v1/jobs/u3x..."}

GET/api/v1/class-checks/{id}

Status, then the ranked student list

status goes from queued to running (with progress from 0 to 1) to done or error. When it's done, result.table lists the students in review order, each with the reasons it was ranked there.

Shell
ID=u3x...
until [ "$(curl -s https://kerata.org/api/v1/class-checks/$ID -H "Authorization: Bearer $KERATA_API_KEY" | jq -r .status)" = done ]; do sleep 3; done
curl -s https://kerata.org/api/v1/class-checks/$ID -H "Authorization: Bearer $KERATA_API_KEY" \
  | jq '.result.table[] | {student, tier: .priority.tier, reasons: [.priority.signals[].text]}'

GET/api/v1/class-checks/{id}/students/{student}

Evidence for one student

The student's files with their highlighted lines, the closest matches with other students, and edit history when there is one. GET /api/v1/class-checks/{id}/pair?a=…&b=… returns two students' matched passages side by side.

GET/api/v1/class-checks/{id}/export.csv

Export the student list

Also available as export.json with the full results. Exports are recorded in your workspace's audit log.

Shell
curl -s https://kerata.org/api/v1/class-checks/$ID/export.csv -H "Authorization: Bearer $KERATA_API_KEY" -o hw3.csv

Saved results

GET/api/v1/results

Your saved scans and class checks, newest first

Filter with kind (scan or class) and course_id; page with limit (up to 200) and before set to the previous response's next. GET /api/v1/results/{id} returns one result.

Shell
curl -s "https://kerata.org/api/v1/results?kind=class&limit=20" -H "Authorization: Bearer $KERATA_API_KEY"

A result is visible to the person who ran it, the workspace's owners and admins, and the instructors and TAs of the course it's filed under. Anything else returns 404.

Reading a result

A scan response (abridged):

JSON
{"overall": 0.97, "verdict": "likely AI", "confidence": "high", "reliable": true,
 "thresholds": {"ai": 0.96, "mixed": 0.44},
 "files": [{"filename": "solution.py", "overall": 0.97, "highlight": true, "line_probs": [0.12, 0.91, "…"]}],
 "scan_id": "Qm9…"}
  • overall is the AI likelihood from 0 to 1. verdict is one of likely AI, contains AI-like section, mixed / uncertain, likely human or too short to judge reliably.
  • Read the cutoffs from thresholds in each response instead of hard-coding them: they change when the detector is updated.
  • When reliable is false (very short code), show the verdict, not a score.
  • line_probs rank lines for highlighting within a file; they aren't calibrated probabilities. Only shade lines when highlight is true.
  • attribution, the AI tools the style is most similar to, is present only when the verdict is likely AI. Treat it as a lead.
  • Text that isn't source code is refused with 422.

Scores are evidence for a person to review. Please show them that way in your own tools: see How it works.

OpenAPI schema

For code generators and API clients: the machine-readable schema is at /openapi.json, with an interactive explorer at /docs.