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

# Import Copies and Run AI Correction - Examino Guide

> A complete end-to-end walkthrough for creating an exam, uploading student copies, launching AI correction, and retrieving graded results via the API.

Examino's correction pipeline moves from exam setup through file upload, batch copy creation, and AI-powered grading, all driven by the REST API. This guide walks you through every step in order, explains what each call does, and shows you how to recover from the failures most likely to occur in production.

<Steps>
  <Step title="Create the exam">
    Start by creating a new exam record and assigning it to your team. The `POST /exams` call returns an `examId` that anchors every subsequent call in this workflow, save it immediately.

    ```bash title="Create exam" theme={null}
    curl -X POST https://app.examino.ai/api/v1/exams \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"teamId": "'"$TEAM_ID"'"}'
    ```

    Once the exam exists, patch it with the title, level, and language so it's properly labelled in the dashboard and in exported reports.

    ```bash title="Update exam settings" theme={null}
    curl -X PATCH https://app.examino.ai/api/v1/exams/$EXAM_ID \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "title": "Marketing digital, partiel S1",
        "level": "Bachelor 2",
        "lang": "fr"
      }'
    ```

    <Tip>
      Export `EXAM_ID` as an environment variable right away. Every call in the remaining steps references it, and overwriting it by accident mid-session is a common source of confusion.
    </Tip>
  </Step>

  <Step title="Validate the rubric in the web app">
    Before you can upload copies, an instructor must import the exam subject, let Examino generate the grading rubric, and confirm it from the **Examino web interface**. The API does not expose a rubric-write endpoint, this step is intentionally human-gated.

    Poll the exam until `questionsValidated` flips to `true`:

    ```bash title="Check rubric readiness" theme={null}
    curl https://app.examino.ai/api/v1/exams/$EXAM_ID \
      -H "Authorization: Bearer $EXAMINO_API_KEY"
    ```

    ```json title="Response (rubric ready)" theme={null}
    {
      "examId": "...",
      "title": "Marketing digital, partiel S1",
      "questionsValidated": true
    }
    ```

    <Note>
      Do not proceed to Step 3 until `questionsValidated` is `true`. Copies uploaded against an unvalidated rubric will be rejected when you try to launch correction.
    </Note>
  </Step>

  <Step title="Upload files">
    Uploading a file is a two-part process: you first register the file with Examino to obtain a pre-signed upload URL, then push the raw bytes directly to object storage using that URL.

    ```bash title="Register file and upload content" theme={null}
    # 1. Register the file - get fileId and pre-signed URL
    RESPONSE=$(curl -s -X POST https://app.examino.ai/api/v1/uploads \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "copie-dupont.pdf",
        "fileType": "application/pdf",
        "fileSize": 1048576
      }')

    FILE_ID=$(echo "$RESPONSE" | jq -r .fileId)
    UPLOAD_URL=$(echo "$RESPONSE" | jq -r .upload.url)

    # 2. Push bytes directly to object storage
    curl -X PUT "$UPLOAD_URL" \
      -H "content-type: application/pdf" \
      -H "content-length: 1048576" \
      --data-binary @copie-dupont.pdf
    ```

    <Tip>
      The `PUT` goes directly to object storage and does **not** count against your Examino API rate limit. You can fire as many parallel uploads as your network allows, saturating this step is almost always faster than serialising it.
    </Tip>

    Repeat this process for each student copy. Collect all `fileId` values returned by the registration step; you will attach them to copies in the next step.
  </Step>

  <Step title="Create copies">
    Attach your uploaded files to the exam by creating copy records. You can submit up to **50 copies per request**, batch liberally to reduce round-trips.

    ```bash title="Create copies (batch)" theme={null}
    curl -X POST https://app.examino.ai/api/v1/exams/$EXAM_ID/copies \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "copies": [
          {
            "studentName": "Camille Dupont",
            "idProvidedByUser": "ETU-2026-0417",
            "fileIds": ["'"$FILE_ID"'"]
          }
        ]
      }'
    ```

    <Note>
      Set `idProvidedByUser` to your student information system (SIS) identifier. It is the recommended key for matching Examino results back to your own records after correction completes, results are not returned in submission order.
    </Note>
  </Step>

  <Step title="Launch correction">
    Before launching, verify that your team has enough credits. The cost is **1 credit per started 20-page block per copy**, a 12-page copy costs 1 credit, a 45-page copy costs 3.

    ```bash title="Check credit balance" theme={null}
    curl "https://app.examino.ai/api/v1/credits?teamId=$TEAM_ID" \
      -H "Authorization: Bearer $EXAMINO_API_KEY"
    ```

    Once confirmed, launch correction across all copies in the exam:

    ```bash title="Launch correction" theme={null}
    curl -X POST https://app.examino.ai/api/v1/exams/$EXAM_ID/corrections \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "idempotencyKey": "sis-'"$EXAM_ID"'-run-1",
        "scope": "all"
      }'
    ```

    <Warning>
      Always supply a meaningful `idempotencyKey`. If the request times out or your scheduler fires twice, replaying the exact same call with the same key returns the original result instead of launching a duplicate correction. See the [Idempotency guide](/guides/idempotency) for key-design recommendations.
    </Warning>
  </Step>

  <Step title="Retrieve results">
    Poll the corrections endpoint every 15–30 seconds. The job is finished when no copy has a status of `pending`, `processing`, or `retry`.

    ```bash title="Poll for results" theme={null}
    curl https://app.examino.ai/api/v1/exams/$EXAM_ID/corrections \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      | jq '[.items[] | select(.status == "success") | {id: .idProvidedByUser, note: .noteTotal, sur: .noteMax}]'
    ```

    ```json title="Sample output" theme={null}
    [
      { "id": "ETU-2026-0417", "note": 14.5, "sur": 20 },
      { "id": "ETU-2026-0418", "note": 11,   "sur": 20 }
    ]
    ```

    <Warning>
      Always filter on `status === "success"` before aggregating scores. Copies with status `error` have no valid `noteTotal`, including them in calculations silently corrupts averages and grade distributions.
    </Warning>
  </Step>
</Steps>

## Incident Recovery

Production pipelines encounter network errors, timeouts, and partial failures. The scenarios below cover the most common issues and explain how to recover without duplicating work or losing data.

<AccordionGroup>
  <Accordion title="PUT upload failed - the file content was never pushed">
    A failed `PUT` leaves you with a `fileId` registered in Examino but backed by no content. **Never attach a `fileId` whose upload did not complete**, it will cause the copy to fail correction.

    Re-register the file from scratch with a new `POST /api/v1/uploads` call. You will receive a fresh `fileId` and a new pre-signed URL. Discard the old `fileId` entirely and repeat the `PUT` with the new URL.
  </Accordion>

  <Accordion title="Copy creation returned a network error">
    Copy creation is **atomic**: either all copies in a batch are created or none are. There is no partial state to untangle.

    Before retrying, list the existing copies for the exam to check whether the batch landed:

    ```bash title="List existing copies" theme={null}
    curl https://app.examino.ai/api/v1/exams/$EXAM_ID/copies \
      -H "Authorization: Bearer $EXAMINO_API_KEY"
    ```

    If the copies are already there, do not resubmit the batch. If they are absent, retry the original request in full.
  </Accordion>

  <Accordion title="Correction launch timed out">
    Replay the exact same request with the **same `idempotencyKey`**. Examino will detect the duplicate and return the original launch result without creating a second correction or reserving credits again.

    A response containing `"alreadyRegistered": true` confirms the original launch went through, no further action is needed.

    ```bash title="Replay correction launch" theme={null}
    curl -X POST https://app.examino.ai/api/v1/exams/$EXAM_ID/corrections \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "idempotencyKey": "sis-'"$EXAM_ID"'-run-1",
        "scope": "all"
      }'
    ```
  </Accordion>

  <Accordion title="One or more copies have status 'error'">
    An `error` status means correction failed for that copy in the current run. Launch a **new correction with a new `idempotencyKey`**, Examino will only process copies that haven't already succeeded, so copies with `status: "success"` from the previous run are not re-billed and not re-processed.

    ```bash title="Retry failed copies" theme={null}
    curl -X POST https://app.examino.ai/api/v1/exams/$EXAM_ID/corrections \
      -H "Authorization: Bearer $EXAMINO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "idempotencyKey": "sis-'"$EXAM_ID"'-run-2",
        "scope": "all"
      }'
    ```
  </Accordion>

  <Accordion title="A copy was imported by mistake">
    Delete the copy permanently with `DELETE /api/v1/copies/{copyId}`. Examino automatically releases any credits that were reserved for that copy back to your balance.

    ```bash title="Delete a copy" theme={null}
    curl -X DELETE https://app.examino.ai/api/v1/copies/$COPY_ID \
      -H "Authorization: Bearer $EXAMINO_API_KEY"
    ```

    <Warning>
      Deletion is permanent and cannot be undone. Confirm the `copyId` before sending the request.
    </Warning>
  </Accordion>
</AccordionGroup>
