> ## Documentation Index
> Fetch the complete documentation index at: https://docs.examino.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Corrections API - Launch Grading and Retrieve Results

> Submit a correction launch for an exam's copies and poll for grading results, including scores, AI-generated feedback, and instructor review status.

The Corrections API lets you trigger AI-powered grading for an exam and retrieve the results. Launching a correction is a single API call that enqueues all matching copies, Examino processes them asynchronously. You then poll `GET /corrections` to track progress and collect scores and feedback when they're ready.

***

## Launch corrections

Enqueues AI grading for copies in an exam. The response is immediate and does not wait for any copy to be processed. You can submit an entire class of 1300 copies in a single request.

**Required scope:** `corrections:write`

<Warning>
  The rubric must be validated in the web app before you launch (`questionsValidated: true` on the exam object). A launch attempted before validation is rejected.
</Warning>

<ParamField body="idempotencyKey" type="string" required>
  A unique key you generate to deduplicate launch requests. Between 8 and 128 characters. If you retry with the same key, Examino returns the original result without re-launching or charging credits. See the Idempotency guide for recommendations.
</ParamField>

<ParamField body="scope" type="string" default="all">
  Which copies to include:

  * `all`, every copy in the exam.
  * `unreviewed`, only copies whose correction has not yet been validated by an instructor. Use this to re-grade without overwriting work your team has already signed off on.
</ParamField>

<ParamField body="preserveEdits" type="boolean" default={false}>
  When relaunching after a rubric change, set to `true` to keep any manually-edited corrections as-is instead of recalculating them from scratch.
</ParamField>

```bash title="Request" theme={null}
curl -X POST https://app.examino.ai/api/v1/exams/aa11bb22-cc33-44dd-88ee-ff0011223344/corrections \
  -H "Authorization: Bearer $EXAMINO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotencyKey": "sis-partiel-s1-2026-09-17-001",
    "scope": "all",
    "preserveEdits": false
  }'
```

```json title="Response 202" theme={null}
{
  "launchId": "c0ffee00-1111-4222-8333-444455556666",
  "launched": true,
  "alreadyRegistered": false,
  "targetCopiesCount": 1300
}
```

<Note>
  A `200 OK` (instead of `202`) indicates that the same `idempotencyKey` was already used. No new launch was created and no credits were reserved. The launch and every resulting credit consumption or refund are attributed to the API key used for this request, not to the user who created it.
</Note>

### Response fields

<ResponseField name="launchId" type="string">
  UUID identifying this correction launch.
</ResponseField>

<ResponseField name="launched" type="boolean">
  `true` when the request was accepted and copies will be processed.
</ResponseField>

<ResponseField name="alreadyRegistered" type="boolean">
  `true` when the provided `idempotencyKey` was already used for a previous launch. No duplication occurred and no credits were reserved for this call.
</ResponseField>

<ResponseField name="targetCopiesCount" type="integer">
  Number of copies included in this launch.
</ResponseField>

### Business refusal errors

When the launch cannot proceed, the API returns a structured error envelope:

```json title="Error envelope" theme={null}
{
  "error": {
    "code": "unprocessable",
    "message": "Le lancement a été refusé.",
    "details": { "reason": "INSUFFICIENT_CREDITS" }
  },
  "requestId": "…"
}
```

Check `error.details.reason` to determine the cause:

| `reason`                      | Status | Meaning                                                                                                      |
| ----------------------------- | ------ | ------------------------------------------------------------------------------------------------------------ |
| `INSUFFICIENT_CREDITS`        | 422    | Your credit balance is too low for the target volume. Top up in the Credits section.                         |
| `GRADING_SCALE_LOCKED`        | 422    | The rubric is not in a state that allows a launch. Check validation status in the web app.                   |
| `NO_COPIES`                   | 422    | No copies match the requested `scope`.                                                                       |
| `SUBJECT_PAGE_LIMIT_EXCEEDED` | 422    | The exam subject exceeds the maximum page limit for your plan.                                               |
| `COPY_PAGE_LIMIT_EXCEEDED`    | 422    | At least one copy exceeds the 100-page limit.                                                                |
| `LAUNCH_ALREADY_PENDING`      | 409    | A correction launch is already in progress for this exam. Wait for it to complete before submitting another. |

***

## Get correction results

Returns one row per copy with its active correction result. Use this endpoint to track progress after a launch and to export grades when processing is complete.

**Required scope:** `corrections:read`

Results are sorted by `index` then by creation date, matching the order of `GET /copies`.

<Warning>
  **Always filter on `status === "success"` before aggregating.** A correction row is attached to a copy at launch time, before any result is ready, rows in `processing` or `error` status have null scores. Including them in grade averages would count as zeros and produce incorrect results.
</Warning>

```bash title="Request" theme={null}
curl "https://app.examino.ai/api/v1/exams/aa11bb22-cc33-44dd-88ee-ff0011223344/corrections" \
  -H "Authorization: Bearer $EXAMINO_API_KEY"
```

```json title="Response 200" theme={null}
{
  "items": [
    {
      "copyId": "11112222-3333-4444-8555-666677778888",
      "studentName": "Camille Dupont",
      "idProvidedByUser": "ETU-2026-0417",
      "index": 1,
      "reviewedAt": "2026-09-16T08:12:44.001Z",
      "correctionId": "99990000-aaaa-4bbb-8ccc-ddddeeeeffff",
      "status": "success",
      "noteTotal": 14.5,
      "noteMax": 20,
      "comment": "Une copie solide sur la partie stratégique…",
      "strongPoints": "Maîtrise du vocabulaire, exemples pertinents.",
      "gaps": "Le calcul du ROI reste approximatif.",
      "updatedAt": "2026-09-16T08:12:44.001Z"
    },
    {
      "copyId": "22223333-4444-5555-8666-777788889999",
      "studentName": "Alex Martin",
      "idProvidedByUser": "ETU-2026-0418",
      "index": 2,
      "reviewedAt": null,
      "correctionId": "88887777-6666-4555-8444-333322221111",
      "status": "processing",
      "noteTotal": null,
      "noteMax": null,
      "comment": null,
      "strongPoints": null,
      "gaps": null,
      "updatedAt": "2026-09-17T09:01:02.512Z"
    }
  ],
  "total": 2
}
```

### Response fields

<ResponseField name="copyId" type="string">
  UUID of the copy.
</ResponseField>

<ResponseField name="studentName" type="string | null">
  Student name as provided at copy creation.
</ResponseField>

<ResponseField name="idProvidedByUser" type="string | null">
  Your own student identifier, returned as-is. Use this to join results back to your SIS.
</ResponseField>

<ResponseField name="index" type="integer | null">
  Copy position within the exam, used for stable ordering.
</ResponseField>

<ResponseField name="reviewedAt" type="string | null">
  ISO 8601 timestamp of when an instructor validated this correction. `null` means it has not yet been reviewed.
</ResponseField>

<ResponseField name="correctionId" type="string | null">
  UUID of the active correction for this copy. `null` if the copy has never been put into correction.
</ResponseField>

<ResponseField name="status" type="string | null">
  Current status of the correction:

  * `pending`, queued, not yet started
  * `processing`, currently being graded
  * `success`, grading complete, results available
  * `error`, grading failed for this copy
  * `retry`, a transient failure occurred; Examino will retry automatically
  * `null`, this copy has never been put into correction
</ResponseField>

<ResponseField name="noteTotal" type="number | null">
  Score earned by the student. `null` until grading completes, and always `null` in competency grading mode.
</ResponseField>

<ResponseField name="noteMax" type="number | null">
  Maximum possible score for this exam. `null` until grading completes, and always `null` in competency grading mode.
</ResponseField>

<ResponseField name="comment" type="string | null">
  AI-generated overall feedback on the copy. `null` until grading completes.
</ResponseField>

<ResponseField name="strongPoints" type="string | null">
  AI-generated summary of what the student did well. `null` until grading completes.
</ResponseField>

<ResponseField name="gaps" type="string | null">
  AI-generated summary of areas for improvement. `null` until grading completes.
</ResponseField>

<ResponseField name="updatedAt" type="string">
  ISO 8601 timestamp of the last update to this correction row.
</ResponseField>

***

## Polling for completion

<AccordionGroup>
  <Accordion title="Recommended polling strategy">
    Poll `GET /exams/{examId}/corrections` every **15–30 seconds** after launching. The batch is fully complete when no item in the response has a `status` of `pending`, `processing`, or `retry`.

    ```text theme={null}
    while any(item.status in ["pending", "processing", "retry"]):
        wait 15–30 seconds
        fetch GET /exams/{examId}/corrections
    ```

    Once complete, filter on `status === "success"` to collect results. Any items left in `error` failed permanently.
  </Accordion>

  <Accordion title="Handling error copies">
    A copy with `status: "error"` means grading failed for that specific copy. Successfully graded copies are **not re-billed** if you relaunch.

    To retry failed copies:

    1. Investigate the cause in the web app (e.g. illegible scan, corrupt file).
    2. Replace or re-upload the copy file if necessary.
    3. Launch again with a **new `idempotencyKey`**.

    Copies that already have `status: "success"` will not be recalculated or re-charged.
  </Accordion>
</AccordionGroup>
