---
title: "Start Self Study Attempt"
method: POST
path: "/knowledge-quiz/my-courses/{course_id}/start"
tags: ["knowledge-quiz"]
---

# Start Self Study Attempt

`POST /knowledge-quiz/my-courses/{course_id}/start`

## 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/knowledge-quiz/my-courses/{course_id}/start`
- Auth: OAuth 2 (password, token URL `api/login`)

Start a new self-study quiz attempt for user's own course.
Generates random variation selections and question order.

## Path parameters

- `course_id` string, required

## Request body

- StartSelfStudyRequest: Optional request body for starting a self-study attempt.
  - `auto_correct_question_ids` string[], nullable

## Response `200`

Successful Response

- SelfStudyAttemptResponse: Response for a self-study quiz attempt (no assignment).
  - `id` string, required
  - `quiz_id` string, required
  - `user_id` string, required
  - `course_id` string, required
  - `status` string, required
  - `score_out_of_20` number, required
  - `correct_count` integer, required
  - `total_questions` integer, required
  - `attempt_number` integer, required
  - `started_at` string, date-time, required
  - `completed_at` string, date-time, nullable
  - `answers` QuizAnswerRecord[]
    - `question_id` string, required
    - `answer_type` 'choice' | 'open'
    - `selected_answer_index` integer, nullable
    - `is_correct` boolean
    - `photo_urls` string[]
    - `grading_status` 'processing' | 'graded' | 'error' | 'rejected', nullable
    - `grading_error` string, nullable
    - `rubric_evaluation` RubricEvaluation: One evaluation pass over a submission against a specific rubric. Multiple may exist per submission attempt (initial AI + teacher overrides); the latest one is canonical.
      - `rubric_id` string, required
      - `author` union, required
        - AIAuthor
          - `type` 'ai'
          - `prompt_id` string, required
          - `model` string, nullable
        - HumanAuthor
          - `type` 'human'
          - `user_id` string, required
      - `feedback` string, required
      - `questions` RubricQuestionEvaluation[], 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.
              - …
        - `criterion_outcomes` CriterionOutcome[]
          - `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.
              - …
          - `student_label` string, nullable
          - `student_note` string, nullable
        - `question_outcome` QuestionOutcome: The grader's verdict on one question of a points rubric: where the answer sits on the five-level ladder, plus what the student is told. `unanswered` is not a rung on the ladder — it says there was nothing on the page to grade (on the photo path it also covers "this part of the copy is unreadable", so illegibility is never laundered into a low score). It implies `score == 0`, which `validate_question_evals_match_rubric` enforces. `alternative_method` records that the route read on the copy is not the corrigé's. It is informational — a valid alternative route scores 4 like any other correct answer — but it is the signal that says whether the scheme is doing the job it was built for. `teacher_points_awarded` is the mark a human wrote on this question (EXC-765). It is `None` on every AI-authored outcome, which is the whole back catalogue, and when it is set it is what the question earns — `question_award` is the one place that arbitration happens. A teacher grades in quarter-points against an allocation ("2,5 sur 3"), a shape the 0..4 ladder cannot express, so the figure is stored rather than folded back into a rung. The rung is still kept coherent with it: the override service re-derives `score` through `score_for_award` so the label, the v2 projection and the analytics never contradict the mark on the copy. `rule_effect` is a teacher rule that moved this question's points away from the ladder (EXC-955), `None` on every outcome graded without one, which is the whole back catalogue. It is kept even when it was not applied, for audit. When it was applied, the question earns `rule_effect.points` (`question_award`), and `score` has been re-derived from that figure through `score_for_award`, exactly as for a teacher's mark; the demands' own rung survives on `rule_effect.ladder_score`. A teacher's mark still wins over it. Evaluations stored while the effect was an operator and an operand carry a `rule_adjustment` key instead; the model ignores unknown keys, so they read as graded without one. `demand_states` are the grader's judgements of the question's demands, one per demand in order, and what the question earns without a teacher's mark or rule (`demand_points`): finer steps than the rung, and a form flaw costs an eighth of the question instead of a quarter. `None` on every outcome stored before that rule (they keep `award_points` of their rung) and on a question with nothing to grade. `score` is still the ladder rung computed from the same states, and is what labels, disclosure, savoir-faire outcomes and the v2 projection read.
          - `score` integer, required
          - `unanswered` boolean
          - `note` string, required
          - `evidence` EvidenceSpan[]
            - `excerpt` string, required
            - `span` TextSpan, required: Resolved char offsets into a `SubmissionAttempt.solution_blocks[i].transcribed_text`. Backend computes start/end at write time.
              - …
          - `alternative_method` boolean
          - `teacher_points_awarded` number, nullable
          - `rule_effect` RuleEffect: A teacher rule that moved one question's points away from the ladder (EXC-955): "no unit = 0", "-0.5 per missing unit", "half the points", rules the demand states cannot express. The grader gives the question's final points itself (`proposed_points`) and says in free text which rule changed them (`text`). Code does not re-derive the figure from the rule; it only decides whether the figure stands (`resolve_rule_effect`): a teacher rule has to exist, the model has to have named one, and the figure has to sit inside the allocation. When it stands, `points` is the quantized figure and is what the question earns; when it does not, `points` is None, `ignored_reason` says why, and the question earns the ladder's figure as if nothing had been proposed. Stored for audit either way. `ladder_score` and `ladder_points` are what the demands gave before the rule, kept here because an applied effect re-derives `QuestionOutcome.score` from `points` (`outcome_with_rule_effect`), so the outcome's own score no longer says what the ladder awarded. `ladder_points` is what the demands alone earn: `demand_points` of their states since that rule replaced the rung's figure, `award_points` of `ladder_score` on effects stored before it (the name predates the change and is kept so both read alike). `text` and `proposed_points` are kept as the model gave them.
            - `text` string, nullable, required
            - `proposed_points` number, nullable, required
            - `ladder_score` integer, required
            - `ladder_points` number, required
            - `points` number, nullable, required
            - `applied` boolean, required
            - `ignored_reason` 'no_teacher_rules' | 'no_rule_stated' | 'not_graded' | 'out_of_range', nullable
          - `demand_states` string[], nullable
        - `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.
              - …
          - `categories` ErrorCategory[], nullable
        - `carried_from_attempt` integer, nullable
      - `question_zones` QuestionZone[]
        - `question_indexes` integer[], required
        - `boxes` PhotoBBox[], required
          - `photo_index` integer, required
          - `x` number, required
          - `y` number, required
          - `width` number, required
          - `height` number, required
      - `computed_grade` number, required
      - `max_grade` number, required
      - `evaluated_at` string, date-time, required
      - `carried_photo_urls` string[]
      - `copied_from_session_id` string, nullable
    - `points_earned` number
    - `points_max` number
    - `sheet_verdict` 'correct' | 'partial' | 'wrong', nullable

## Other responses

- `422`: Validation Error

## Changes

> 55 revisions in range; 1 not diffed.

- **2026-10-15** `7925dd21e4d0`: 1 info
  - added the optional property `answers/items/sheet_verdict` to the response with the `200` status
- **2026-10-13** `60775565116f`: 1 info
  - added the optional property `answers/items/rubric_evaluation/anyOf[subschema #1: RubricEvaluation]/copied_from_session_id` to the response with the `200` status
- **2026-09-30** `518c1e6178a3`: 2 info
  - added the optional property `answers/items/rubric_evaluation/anyOf[subschema #1: RubricEvaluation]/questions/items/question_outcome/anyOf[subschema #1: QuestionOutcome]/demand_states` to the response with the `200` status
  - added the optional property `answers/items/rubric_evaluation/anyOf[subschema #1: RubricEvaluation]/questions/items/question_outcome/anyOf[subschema #1: QuestionOutcome]/rule_effect` to the response with the `200` status
- **2026-09-25** `a8ae0e0c0570`: 2 info
  - added the optional property `answers/items/rubric_evaluation/anyOf[subschema #1: RubricEvaluation]/carried_photo_urls` to the response with the `200` status
  - added the optional property `answers/items/rubric_evaluation/anyOf[subschema #1: RubricEvaluation]/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/knowledge-quiz/my-courses/:course_id/start/post.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)
