---
title: "Get Attempt Detail"
method: GET
path: "/admin/multimodal-submissions/attempts/{session_id}/{exercise_id}/{attempt_index}"
tags: ["admin_multimodal_submissions"]
---

# Get Attempt Detail

`GET /admin/multimodal-submissions/attempts/{session_id}/{exercise_id}/{attempt_index}`

## Request

- Base URL: `https://api.excellence-ai.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.excellence-ai.com/admin/multimodal-submissions/attempts/{session_id}/{exercise_id}/{attempt_index}`
- Auth: OAuth 2 (password, token URL `api/login`)

## Path parameters

- `session_id` string, required
- `exercise_id` string, required
- `attempt_index` integer, required

## Response `200`

Successful Response

- AttemptDetail: One photo attempt with what an admin needs to check its correction: the exercise statement, the photos, the rubric and the evaluation made against it. `exercise` / `rubric` / `evaluation` are `None` when the exercise was deleted, no rubric can be resolved, or the attempt was never graded (processing / error / rejected).
  - `session` AttemptDetailSession, required
    - `id` string, required
    - `user_id` string, required
    - `user_email` string, nullable
    - `assignment_id` string, nullable
    - `exercise_ids` string[], required
  - `exercise_id` string, required
  - `attempt` AttemptDetailAttempt, required
    - `attempt_index` integer, required
    - `photo_urls` string[], required
    - `posted_at` string, date-time, required
    - `checked_at` string, date-time, required
    - `grading_status` string, nullable
    - `grading_error` string, nullable
    - `transcription` string, nullable
    - `feedback` string, required
    - `correctness` string, required
    - `device_type` string, nullable
    - `submission_scope` string, nullable
    - `model` string, required
    - `model_source` 'stored' | 'inferred', required
  - `exercise` AttemptDetailExercise: The statement as the student saw it. `content` keeps its inline `figure://` markers so the frontend renders it with the same component as the student view; `figure_image_urls` is the rendered image of every completed statement figure, for a fallback display.
    - `id` string, required
    - `collection` string, required
    - `title` string, nullable
    - `content` string, required
    - `figure_image_urls` string[], required
  - `rubric` RubricResponse: Minimal: drops `exercise_id` (lives in the URL) and `exercise_collection` / `created_at` (frontend doesn't read them today). `author` is flattened to a Literal to avoid leaking prompt ids / user ids. `scoring_scheme` is appended last and is derived, never stored — a new bundle reads it to decide which shape to render; an old one ignores it.
    - `id` string, required
    - `questions` RubricQuestionResponse[], required
      - `question_text` string, required
      - `skills` SkillRef[], required
        - `savoir_faire_id` string, required
        - `name` string, required
        - `description` string, required
      - `criteria` string[], required
      - `points` number, nullable
      - `expected_result` string, nullable
    - `author_type` 'ai' | 'human', required
    - `scoring_scheme` 'criteria' | 'points'
  - `evaluation` RubricEvaluationResponse
    - `rubric_id` string, required
    - `author_type` 'ai' | 'human', required
    - `author_model` string, nullable
    - `feedback` string, required
    - `questions` RubricQuestionEvaluationResponse[], required
      - `skill_outcomes` SkillOutcome[], required
        - `savoir_faire_id` string, required
        - `outcome` 'correct' | 'partial' | 'wrong' | 'unanswered', required
        - `note` string, required
        - `evidence` EvidenceSpan[], required
          - `excerpt` string, required
          - `span` TextSpan, required: Resolved char offsets into a `SubmissionAttempt.solution_blocks[i].transcribed_text`. Backend computes start/end at write time.
            - `block_id` string, required
            - `start` integer, required
            - `end` integer, required
      - `criterion_outcomes` CriterionOutcomeResponse[], required
        - `criterion_index` integer, required
        - `outcome` 'correct' | 'partial' | 'wrong' | 'unanswered', required
        - `note` string, required
        - `evidence` EvidenceSpan[], required
          - `excerpt` string, required
          - `span` TextSpan, required: Resolved char offsets into a `SubmissionAttempt.solution_blocks[i].transcribed_text`. Backend computes start/end at write time.
            - `block_id` string, required
            - `start` integer, required
            - `end` integer, required
      - `errors` FlaggedError[], required
        - `description` string, required
        - `evidence` EvidenceSpan[], required
          - `excerpt` string, required
          - `span` TextSpan, required: Resolved char offsets into a `SubmissionAttempt.solution_blocks[i].transcribed_text`. Backend computes start/end at write time.
            - `block_id` string, required
            - `start` integer, required
            - `end` integer, required
        - `categories` ErrorCategory[], nullable
      - `question_outcome` QuestionOutcomeResponse: The grader's verdict on one question of a points rubric. `max_score` travels so a client never hardcodes the ladder's top rung, and `points_awarded` / `points_available` are computed **here**, by the backend, through the one domain rule (`question_award`: the ladder, or a teacher's own mark when one was written). No client recomputes a grade from outcomes: the per-question awards sum to `computed_grade` exactly, which is what lets EXC-764 delete the frontend's duplicate weight table rather than extend it. Both point fields are `None` only when the response was built without the rubric in hand (see `RubricEvaluationResponse.from_domain`).
        - `score` integer, nullable, required
        - `max_score` integer, required
        - `unanswered` boolean, required
        - `note` string, required
        - `evidence` EvidenceSpan[], required
          - `excerpt` string, required
          - `span` TextSpan, required: Resolved char offsets into a `SubmissionAttempt.solution_blocks[i].transcribed_text`. Backend computes start/end at write time.
            - `block_id` string, required
            - `start` integer, required
            - `end` integer, required
        - `alternative_method` boolean, required
        - `points_awarded` number, nullable, required
        - `points_available` number, nullable, required
      - `carried_from_attempt` integer, nullable
      - `verdict` 'correct' | 'to_review'
    - `question_zones` QuestionZone[], required
      - `question_indexes` integer[], required
      - `boxes` PhotoBBox[], required
        - `photo_index` integer, required
        - `x` number, required
        - `y` number, required
        - `width` number, required
        - `height` number, required
    - `carried_photo_urls` string[]
    - `computed_grade` number, nullable, required
    - `max_grade` number, nullable, required
    - `evaluated_at` string, date-time, required

## Other responses

- `404`: Resource not found
- `422`: Validation Error

## Changes

> 55 revisions in range; 1 not diffed.

- **2026-10-13** `60775565116f`: 3 breaking, 1 info
  - response property `evaluation/anyOf[subschema #1: RubricEvaluationResponse]/computed_grade` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - response property `evaluation/anyOf[subschema #1: RubricEvaluationResponse]/max_grade` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - response property `evaluation/anyOf[subschema #1: RubricEvaluationResponse]/questions/items/question_outcome/anyOf[subschema #1: QuestionOutcomeResponse]/score` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - added the optional property `evaluation/anyOf[subschema #1: RubricEvaluationResponse]/questions/items/verdict` to the response with the `200` status
- **2026-09-25** `a8ae0e0c0570`: 2 info
  - added the optional property `evaluation/anyOf[subschema #1: RubricEvaluationResponse]/carried_photo_urls` to the response with the `200` status
  - added the optional property `evaluation/anyOf[subschema #1: RubricEvaluationResponse]/questions/items/carried_from_attempt` to the response with the `200` status
- …earlier changes not shown

[Operation history](https://skmtc.dev/excellence-ai/apis/excellence-learning/changes/admin/multimodal-submissions/attempts/:session_id/:exercise_id/:attempt_index/get.md)

---

[API](https://skmtc.dev/excellence-ai/apis/excellence-learning.md) - [All operations](https://skmtc.dev/excellence-ai/apis/excellence-learning/llms.txt) - [OpenAPI document](https://skmtc.dev/excellence-ai/apis/excellence-learning/revisions/379e4a57dee8?raw)
