Documentation
Last updated: March 29, 2026
Introduction
At the core of our platform is the Quanta API, an algorithm that does LLM-powered reviews of input solutions. It handles inputs in various formats, including text, images, PDFs, or a mix of all three, and its focus is on evaluating reasoning, not just final answers. In fact, a correct answer with no explanation will receive the lowest possible grade.
Two-letter grading system
Each submission receives a two-letter grade (e.g. "AB") on a scale from A (Perfect Enough) to F (No meaningful progress):
First letter
Focuses on technical correctness, even if the solution is short or has minor gaps.
Second letter
Focuses on the clarity and consistency of the logical flow.
It is up to you how you want to map this overall grade to a number (i.e. points), here are the two most common options we use internally:
0–7 Scale
| Grade | Points |
|---|---|
| AA | 7 |
| AB | 6 |
| BA | 5 |
| AC, BB | 4 |
| BC, CA, CB | 3 |
| AE, CC | 2 |
| AF, BE, CE, EA, EB, EC | 1 |
| BF, CF, EE, EF, FA, FB, FC, FE, FF | 0 |
0–1 Scale
| Grade | Points |
|---|---|
| AA, AB, BA | 1 |
| Everything else | 0 |
After lots of tests and adjustments, we're ready to open this API to our beta partners. This documentation explains how to use it.
Setting Expectations
The API is not perfect, but has proven highly useful — particularly for easier tasks with lenient mode enabled, which covers the majority of most teachers' time spent on reviewing students' work.
Note: this is the worst this model will ever be. It will only get cheaper, faster, and more accurate over time — though that takes feedback and iteration.
API Overview
Base URL
https://api.quanta.worldRequirements
| Feature | Description |
|---|---|
| Protocol | HTTPS (REST) |
| Request format | JSON or multipart/form-data (CSV upload for bulk review) |
| Response format | JSON |
| Authentication | Bearer token (API key) |
Available Endpoints
| Method | Endpoint | Auth Required | Description |
|---|---|---|---|
| GET | /api/health | No | Service health check |
| POST | /api/review | Yes | Single solution AI review |
| POST | /api/bulk-review | Yes | Submit multiple solutions for async review |
| GET | /api/jobs/:jobId | Yes | Check bulk job status and results |
GET /api/health
No authentication required. Use this to verify the API is reachable and all dependencies are operational before making substantive calls.
Response Fields
| Field | Type | Description |
|---|---|---|
status | "ok" | "degraded" | Overall service health |
uptime_ms | number | Milliseconds since the server process started |
timestamp | string | ISO 8601 timestamp of the response |
checks.database | object | Supabase connectivity status |
checks.environment | object | Required environment variable status |
{
"status": "ok",
"uptime_ms": 3567429,
"timestamp": "2026-03-12T20:35:25.711Z",
"checks": {
"database": {
"status": "ok"
},
"environment": {
"status": "ok"
}
}
}curl https://api.quanta.world/api/healthAuthentication
All protected endpoints require an API key passed as a Bearer token in the Authorization header.
Authorization: Bearer <your-api-key>Getting an API Key
- Log in at api.quanta.world
- Navigate to API Keys
- Click Add Key — optionally set a nickname and expiration date
- Copy your key immediately — it is only shown in full once
You can create multiple API keys, each with its own nickname and optional expiration date. Keys can be deactivated or deleted at any time from the API Keys page. Deactivated or expired keys will return a 401 Unauthorized error.
Permissions
Each API key carries a permissions object:
{
"single_review": true,
"bulk_review": true
}| Permission | Description |
|---|---|
single_review | Submit one solution at a time for review |
bulk_review | Submit multiple solutions in a single request |
Usage Limits
Each API key also has usage limits:
{
"bulk_review_individual_limit": 100,
"max_reviews_per_day": 500
}| Restriction | Description |
|---|---|
bulk_review_individual_limit | Max number of input solutions in any one bulk review |
max_reviews_per_day | Max number of reviewed input solutions per day |
POST /api/review
Submit a single student solution for AI review. Requires the single_review permission.
Request Body
The /api/review endpoint accepts three primary components:task (the underlying problem), input_solution (the student's work), and reviewing_parameters (to calibrate strictness and style).
Top-level Fields
| Field | Type | Required | Description |
|---|---|---|---|
task | object | Yes | The problem definition (see below) |
input_solution | PromptBlock[] | Yes | The student's solution — text, images, or files |
reviewing_parameters | object | No | Optional grading parameters (see below) |
task Object
| Field | Type | Required | Description |
|---|---|---|---|
content | PromptBlock[] | Yes | The problem statement shown to the student |
correct_solutions | array | Yes | Reference solutions to help the AI grade (see below) |
is_context_required | boolean | No | Whether extra context is needed. Default: false |
context_details | PromptBlock[] | No | Extra context blocks. Use [] if not needed |
correct_solutions[] Items
| Field | Type | Required | Description |
|---|---|---|---|
slogan | string | Yes | Short label for this approach, e.g. "Direct proof" |
content | PromptBlock[] | Yes | The full reference solution content |
PromptBlock Object
Used by task.content, input_solution, correct_solutions[].content, and context_details.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | One of "text", "image_url", "image", "file" |
text | string | If type is "text" | The text content (supports LaTeX) |
image_url.url | string (URL) | If type is "image_url" | Publicly accessible image URL |
file.file_id | string | If type is "file" | File ID for an uploaded file |
file_url | string (URL) | If type is "file" | Publicly accessible file URL |
reviewing_parameters Object
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
strictness | string | No | "auto" | Grading strictness — see values below |
aux_rubric | object | No | { "-", "-" } | Custom grading rubric |
aux_rubric.validity | string | No | "-" | What makes an answer correct |
aux_rubric.quality | string | No | "-" | What makes a presentation good |
num_sanity_reruns | integer (1–10) | No | 3 | How many sanity check reruns for confidence |
strictness Values
| Value | Description |
|---|---|
"auto" | The model decides based on the problem. Balanced — not too strict, not too kind. Best for most use-cases. |
"lenient" | Forgives minor mistakes. Thinks from the standpoint of 'What could the student have meant?'. Avoids downgrading unless there are major gaps. |
"strict" | Even minor errors are flagged and likely lead to a downgrade. Not commonly needed since "auto" is already relatively strict. |
Response
A successful review returns the overall grade, detailed feedback per dimension, and cost/timing metadata.
{
"success": true,
"feedback_type": "AI-generated",
"overall_grade": "BA",
"model_output": {
"status": "Sanity Checks Passed, Full Review Completed",
"sanity": {
"sanity_status": "Pass",
"sanity_status_justification": "...",
"sanity_confidence_level": "98%",
"sanity_statuses_reruns": "Pass, Pass, Pass"
},
"validity": {
"validity_grade": "B",
"nontrivial_mistakes_or_unjustified_claims": "...",
"explanation_good_aspects_summary": "..."
},
"quality": {
"quality_grade": "A",
"major_clarity_issues": "...",
"presentation_and_formatting": "...",
"quality_good_aspects": "..."
},
"feedback_summary": "Your proof correctly uses the definition of even numbers..."
},
"metadata": {
"model_name": "quanta-senji-30-09-2025",
"costs": {
"total_cost_usd": 0.0147,
"sanity_total_cost_usd": 0.0032,
"validity_total_cost_usd": 0.0058,
"quality_total_cost_usd": 0.0057
},
"times": {
"time_taken_to_review_s": "24.05",
"time_spent_on_sanity_check_s": "5.10",
"time_spent_on_validity_review_s": "9.80",
"time_spent_on_quality_review_s": "9.15"
},
"timestamp": "2026-03-12T20:42:51.572Z"
}
}| Field | Type | Description |
|---|---|---|
success | boolean | Whether the request was processed successfully |
overall_grade | string | Two-character grade: first character is validity (A–F), second is quality (A–F). Example: BA = validity B, quality A |
model_output.status | string | Pipeline status — indicates whether sanity check passed and full review was completed |
model_output.sanity | object | Sanity check result: sanity_status is "Pass" or "Fail". If failed, the review stops early with grade FF |
model_output.validity | object | Correctness assessment: includes validity_grade (A–F), mistakes found, and good aspects |
model_output.quality | object | Presentation assessment: includes quality_grade (A–F), clarity issues, and formatting notes |
model_output.feedback_summary | string | Human-readable feedback summary suitable for showing to the student |
metadata.costs | object | Cost breakdown in USD for each pipeline stage |
metadata.times | object | Timing breakdown in seconds for each pipeline stage |
Example 1: Text-based Solution Review
Combinatorics problem with a text-based student solution.
curl -X POST https://api.quanta.world/api/review \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"task": {
"content": [
{
"type": "text",
"text": "How many six-digit numbers contain exactly three zeros in their decimal representation, with the additional constraint that no two zeros are adjacent? Note: numbers do not begin with a zero."
}
],
"is_context_required": false,
"context_details": [],
"correct_solutions": [
{
"slogan": "Direct Computation",
"content": [
{
"type": "text",
"text": "**Answer:** $9^3 = 729$\n\n**Explanation:**\n1. Note that since there are 3 non-adjacent zeros, they have to be in places 2, 4, 6.\n2. The remaining 3 digits can each be 1–9, giving $9 \\cdot 9 \\cdot 9 = 729$."
}
]
}
]
},
"input_solution": [
{
"type": "text",
"text": "Firstly, there are 9-1=8 possibilities for the first digit since it cannot be zero. Now, the zeros can'\''t be adjacent so they must be in positions 2, 4 and 6, and for the remaining 2 positions there are 10*10 options. However, we counted some options twice, specifically where there are non-zeros in positions 3 and 5. Thus N = 800 - 9x9 = 729."
}
],
"reviewing_parameters": {
"aux_rubric": {
"validity": "No points should be deducted for the lack of explanation of why zeros must be in positions 2, 4 and 6, this is counted as trivial.",
"quality": "This is an easy and short problem, so there is NO need to be too judgemental about the presentation!"
},
"strictness": "auto"
}
}'Example 2: Unrelated Image Solution (Sanity Fail)
When the student submits an image that is completely unrelated to the problem, the sanity check fails early and the review returns with an FF grade. This example also shows is_context_required and context_details usage.
curl -X POST https://api.quanta.world/api/review \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"task": {
"content": [
{
"type": "text",
"text": "Following that other problem, what is the answer if we need 2 or 5 instead of 1 or 4?"
}
],
"is_context_required": true,
"context_details": [
{
"type": "text",
"text": "That other problem refers to: There is a six-sided die with numbers 1, 2, ..., 6 on its faces. Find the probability of rolling 1 or 4. So this problem is the same, just 2 and 5 instead of 1 or 4."
}
],
"correct_solutions": [
{
"slogan": "Direct computation, nothing fancy",
"content": [
{
"type": "text",
"text": "**Answer:** 1/3\n\n**Explanation:** There are six possibilities. Precisely two are of interest. Hence the answer is 2/6 = 1/3."
}
]
}
]
},
"input_solution": [
{
"type": "image_url",
"image_url": {
"url": "https://ujngnciigcpczmsloboz.supabase.co/storage/v1/object/public/quanta-storage/image-example.png"
}
}
],
"reviewing_parameters": {
"aux_rubric": {
"validity": "-",
"quality": "-"
},
"strictness": "auto"
}
}'Example 3: Images in Task and Solution
Both the problem statement and the student's solution can contain images. Use image_url blocks in task.content, correct_solutions[].content, and input_solution.
curl -X POST https://api.quanta.world/api/review \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"task": {
"content": [
{
"type": "text",
"text": "Is it possible to form a square from a 100 T-shapes shown in the image below (that consist of 4 squares)?"
},
{
"type": "image_url",
"image_url": {
"url": "https://ujngnciigcpczmsloboz.supabase.co/storage/v1/object/public/quanta-storage/internal-testing/tasks/drafts/images/1775307123018-tetramino.png"
}
}
],
"is_context_required": false,
"context_details": [],
"correct_solutions": [
{
"slogan": "Construction with Repetition",
"content": [
{
"type": "text",
"text": "**Answer:** Yes, it is possible.\n\n**Explanation:** 1. From 4 of these pieces we can form a 4 x 4 square as shown in the image below:"
},
{
"type": "image_url",
"image_url": {
"url": "https://ujngnciigcpczmsloboz.supabase.co/storage/v1/object/public/quanta-storage/internal-testing/tasks/drafts/images/1775307441736-4_by_4_split.png"
}
},
{
"type": "text",
"text": "2. And if we arrange these 4 x 4 squares into a 5 x 5 grid, we obtain a 20 x 20 square. The total number of tetraminos used is 4 * (5 * 5) = 100, as required."
}
]
}
]
},
"input_solution": [
{
"type": "text",
"text": "First note that we can split the 20 x 20 square into 25 squares of 4 x 4. Secondly, note that we can combine 4 tetraminos into a 4 x 4 square as shown in the image below, and hence we can split the 20 x 20 square into 25 x 4 = 100 tetraminos!"
},
{
"type": "image_url",
"image_url": {
"url": "https://ujngnciigcpczmsloboz.supabase.co/storage/v1/object/public/quanta-storage/internal-testing/submissions/c25fa4b1-4e47-4518-bec4-447f7966b8b4/drafts/images/1775308999163-input_solution_4x4.png"
}
}
],
"reviewing_parameters": {
"aux_rubric": {
"validity": "-",
"quality": "-"
},
"strictness": "lenient"
}
}'Example 4: Hand-written Solution with Custom Rubric
A hand-written student solution submitted as an image, with a custom aux_rubric that enforces specific grading criteria (e.g. requiring proof of a particular claim for a top grade).
curl -X POST https://api.quanta.world/api/review \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{
"task": {
"content": [
{
"type": "text",
"text": "Lazy Ania has divided the (closed) interval AB into 20 identical (closed) intervals and called each of them a **big** one. Whereas diligent Tony has divided that same interval into 2019 identical (closed) intervals and called each of them a **small** one. Let'\''s call a small interval **good** if it lies fully inside one of the big intervals. How many good intervals are there?"
}
],
"is_context_required": false,
"context_details": [],
"correct_solutions": [
{
"slogan": "Direct computation, nothing fancy",
"content": [
{
"type": "text",
"text": "**Answer**: 2000\n**Explanation**:\n1. A small interval won'\''t be good if it has at least one endpoint of one of the big intervals lying inside. But, any endpoint, except A and B, of each big interval lies inside exactly one of the small intervals. Therefore, there are 19 small intervals which are not good, so the answer is $2019-19=2000$.\n2. Remark: we relied on the fact that except A and B there are no endpoints of big intervals which coincide with endpoints of small intervals. We can see this as follows: if the $k$th endpoint of a big interval happens to be the $m$th endpoint of a small one, then $k \\cdot AB/20=m \\cdot AB/2019$, i.e. $2019k=20m$, but $20$ and $2019$ are co-prime, so $20$ divides $k$ and therefore $k \\geq 20$. However $k \\leq 20$, so that coinciding endpoint has to be B."
}
]
}
]
},
"input_solution": [
{
"type": "image_url",
"image_url": {
"url": "https://ujngnciigcpczmsloboz.supabase.co/storage/v1/object/public/quanta-storage/internal-testing/submissions/c25fa4b1-4e47-4518-bec4-447f7966b8b4/drafts/images/1775313731690-photo_2026_04_04_15_42_03.jpeg"
}
}
],
"reviewing_parameters": {
"aux_rubric": {
"validity": "It is important to prove that none of the endpoints of big and small intervals coincide. If this is not proven, the grade can be maximum B (if everything else is perfect).",
"quality": "-"
},
"strictness": "auto"
}
}'POST /api/bulk-review
Submit multiple student solutions for asynchronous AI review via CSV file upload. Requires the bulk_review permission. Unlike the single review endpoint, this returns immediately with a job ID. Poll GET /api/jobs/:jobId to track progress and retrieve results.
Request Format
Send a multipart/form-data request with the CSV file as the file field.
| Field | Type | Required | Description |
|---|---|---|---|
file | File (CSV) | Yes | CSV file containing review tasks. Max 10MB, up to 100 rows. |
CSV Column Reference
| Column | Required | Format | Description |
|---|---|---|---|
task.content | Yes | JSON array | Problem statement as a PromptBlock[] JSON array |
task.correct_solutions | Yes | JSON array | Reference solutions as a JSON array of solution objects |
input_solution | Yes | JSON array | Student solution as a PromptBlock[] JSON array |
task.is_context_required | No | TRUE/FALSE | Whether extra context is needed. Default: FALSE |
task.context_details | No | JSON array | Extra context blocks (only used when is_context_required is TRUE) |
reviewing_parameters.aux_rubric | No | JSON object | Grading rubric with validity and quality fields |
reviewing_parameters.strictness | No | string | One of auto, lenient, strict. Default: auto |
JSON values in cells are parsed automatically. LaTeX expressions with backslashes (e.g. \frac) are handled correctly.
Response
Returns immediately with a job ID. The reviews are processed asynchronously in the background.
{
"success": true,
"job": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"total_items": 2,
"created_at": "2026-03-13T12:00:00.000Z"
},
"metadata": {
"timestamp": "2026-03-13T12:00:00.150Z"
}
}Complete Example: Create CSV & Run Bulk Review
Full example showing how to programmatically create a CSV file with multiple review tasks, upload it for bulk review, poll until completion, and display results with grades and feedback.
# Step 1: Download the template CSV above and fill in your review tasks.
#
# Step 2: Upload CSV for bulk review
curl -X POST https://api.quanta.world/api/bulk-review \
-H "Authorization: Bearer your-api-key" \
-F "file=@bulk-review-template.csv"
# Step 3: Poll for job status (replace JOB_ID with the id from step 2)
curl https://api.quanta.world/api/jobs/JOB_ID \
-H "Authorization: Bearer your-api-key"
# Repeat step 3 until job.status is "completed" or "failed".GET /api/jobs/:jobId
Retrieve the status and results of a bulk review job. Use this to poll for progress after submitting a bulk review request. Requires a valid API key (any permission). You can only access jobs created with your own API key.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
jobId | string (UUID) | The job ID returned from the bulk review endpoint |
Response
{
"success": true,
"job": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "completed",
"total_items": 2,
"processed_items": 2,
"failed_items": 0,
"total_cost_usd": 0.0294,
"created_at": "2026-03-13T12:00:00.000Z",
"started_at": "2026-03-13T12:00:00.150Z",
"completed_at": "2026-03-13T12:01:05.000Z"
},
"results": [
{
"index": 0,
"status": "success",
"grade": "AA",
"feedback_summary": "Excellent proof by contradiction...",
"output": { "..." : "full review output" },
"processing_time_ms": 24000,
"error_message": null
},
{
"index": 1,
"status": "success",
"grade": "BA",
"feedback_summary": "Correct integration but...",
"output": { "..." : "full review output" },
"processing_time_ms": 18500,
"error_message": null
}
]
}Job Status Values
| Status | Description |
|---|---|
"pending" | Job created but processing has not started yet |
"processing" | One or more reviews are currently being processed |
"completed" | All reviews have been processed (some may have failed) |
"failed" | The entire job failed due to a system error |
Result Status Values
| Status | Description |
|---|---|
"pending" | Review has not been processed yet |
"processing" | Review is currently being processed by the AI |
"success" | Review completed — grade and feedback are available |
"error" | Review failed — see error_message for details |
curl https://api.quanta.world/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer your-api-key"HTTP Status Codes
| Code | Meaning |
|---|---|
200 | Request succeeded — review completed, job created, or job status returned |
400 | Invalid request — malformed JSON, missing required fields, invalid CSV, or bad job ID format |
401 | Missing, invalid, deactivated, or expired API key |
403 | API key lacks the required permission for this endpoint |
404 | Resource not found (e.g. job ID does not exist) |
500 | Internal server error |
503 | Service degraded — one or more health checks failed |