Quanta logoQuanta API

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

GradePoints
AA7
AB6
BA5
AC, BB4
BC, CA, CB3
AE, CC2
AF, BE, CE, EA, EB, EC1
BF, CF, EE, EF, FA, FB, FC, FE, FF0

0–1 Scale

GradePoints
AA, AB, BA1
Everything else0

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.

1.Feedback quality: On average, ~80% of feedback is fully acceptable and ~5% is not great. The remaining ~15% falls somewhere in between and varies by subdomain and input type, e.g. geometry and/or handwriting tend to score lower.
2.Response time: No images: ~1.5–4 minutes. With images: ~3–6 minutes (each image adds processing time).
3.Cost: ~$0.17 per submission on average, though this varies significantly depending on whether images are included.

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.world

Requirements

FeatureDescription
ProtocolHTTPS (REST)
Request formatJSON or multipart/form-data (CSV upload for bulk review)
Response formatJSON
AuthenticationBearer token (API key)

Available Endpoints

MethodEndpointAuth RequiredDescription
GET/api/healthNoService health check
POST/api/reviewYesSingle solution AI review
POST/api/bulk-reviewYesSubmit multiple solutions for async review
GET/api/jobs/:jobIdYesCheck 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

FieldTypeDescription
status"ok" | "degraded"Overall service health
uptime_msnumberMilliseconds since the server process started
timestampstringISO 8601 timestamp of the response
checks.databaseobjectSupabase connectivity status
checks.environmentobjectRequired environment variable status
{
  "status": "ok",
  "uptime_ms": 3567429,
  "timestamp": "2026-03-12T20:35:25.711Z",
  "checks": {
    "database": {
      "status": "ok"
    },
    "environment": {
      "status": "ok"
    }
  }
}
Example Request
curl https://api.quanta.world/api/health

Authentication

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

  1. Log in at api.quanta.world
  2. Navigate to API Keys
  3. Click Add Key — optionally set a nickname and expiration date
  4. 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:

json
{
  "single_review": true,
  "bulk_review": true
}
PermissionDescription
single_reviewSubmit one solution at a time for review
bulk_reviewSubmit multiple solutions in a single request

Usage Limits

Each API key also has usage limits:

json
{
  "bulk_review_individual_limit": 100,
  "max_reviews_per_day": 500
}
RestrictionDescription
bulk_review_individual_limitMax number of input solutions in any one bulk review
max_reviews_per_dayMax 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

FieldTypeRequiredDescription
taskobjectYesThe problem definition (see below)
input_solutionPromptBlock[]YesThe student's solution — text, images, or files
reviewing_parametersobjectNoOptional grading parameters (see below)

task Object

FieldTypeRequiredDescription
contentPromptBlock[]YesThe problem statement shown to the student
correct_solutionsarrayYesReference solutions to help the AI grade (see below)
is_context_requiredbooleanNoWhether extra context is needed. Default: false
context_detailsPromptBlock[]NoExtra context blocks. Use [] if not needed

correct_solutions[] Items

FieldTypeRequiredDescription
sloganstringYesShort label for this approach, e.g. "Direct proof"
contentPromptBlock[]YesThe full reference solution content

PromptBlock Object

Used by task.content, input_solution, correct_solutions[].content, and context_details.

FieldTypeRequiredDescription
typestringYesOne of "text", "image_url", "image", "file"
textstringIf type is "text"The text content (supports LaTeX)
image_url.urlstring (URL)If type is "image_url"Publicly accessible image URL
file.file_idstringIf type is "file"File ID for an uploaded file
file_urlstring (URL)If type is "file"Publicly accessible file URL

reviewing_parameters Object

FieldTypeRequiredDefaultDescription
strictnessstringNo"auto"Grading strictness — see values below
aux_rubricobjectNo{ "-", "-" }Custom grading rubric
aux_rubric.validitystringNo"-"What makes an answer correct
aux_rubric.qualitystringNo"-"What makes a presentation good
num_sanity_rerunsinteger (1–10)No3How many sanity check reruns for confidence

strictness Values

ValueDescription
"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.

json
{
  "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"
  }
}
FieldTypeDescription
successbooleanWhether the request was processed successfully
overall_gradestringTwo-character grade: first character is validity (AF), second is quality (AF). Example: BA = validity B, quality A
model_output.statusstringPipeline status — indicates whether sanity check passed and full review was completed
model_output.sanityobjectSanity check result: sanity_status is "Pass" or "Fail". If failed, the review stops early with grade FF
model_output.validityobjectCorrectness assessment: includes validity_grade (AF), mistakes found, and good aspects
model_output.qualityobjectPresentation assessment: includes quality_grade (AF), clarity issues, and formatting notes
model_output.feedback_summarystringHuman-readable feedback summary suitable for showing to the student
metadata.costsobjectCost breakdown in USD for each pipeline stage
metadata.timesobjectTiming breakdown in seconds for each pipeline stage

Example 1: Text-based Solution Review

Combinatorics problem with a text-based student solution.

Text-based Review
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.

Unrelated Image — Sanity Fail
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.

Images in Task & 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).

Hand-written Solution Review
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.

FieldTypeRequiredDescription
fileFile (CSV)YesCSV file containing review tasks. Max 10MB, up to 100 rows.

CSV Column Reference

ColumnRequiredFormatDescription
task.contentYesJSON arrayProblem statement as a PromptBlock[] JSON array
task.correct_solutionsYesJSON arrayReference solutions as a JSON array of solution objects
input_solutionYesJSON arrayStudent solution as a PromptBlock[] JSON array
task.is_context_requiredNoTRUE/FALSEWhether extra context is needed. Default: FALSE
task.context_detailsNoJSON arrayExtra context blocks (only used when is_context_required is TRUE)
reviewing_parameters.aux_rubricNoJSON objectGrading rubric with validity and quality fields
reviewing_parameters.strictnessNostringOne 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.

json
{
  "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.

Complete Bulk Review Example
# 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

ParameterTypeDescription
jobIdstring (UUID)The job ID returned from the bulk review endpoint

Response

json
{
  "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

StatusDescription
"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

StatusDescription
"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
Example Request
curl https://api.quanta.world/api/jobs/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "Authorization: Bearer your-api-key"

HTTP Status Codes

CodeMeaning
200Request succeeded — review completed, job created, or job status returned
400Invalid request — malformed JSON, missing required fields, invalid CSV, or bad job ID format
401Missing, invalid, deactivated, or expired API key
403API key lacks the required permission for this endpoint
404Resource not found (e.g. job ID does not exist)
500Internal server error
503Service degraded — one or more health checks failed