Owner Abhishek P

V1 API Custom Tests

Source: OpenAPI 3.1.0 spec + src/api/v1/custom_test/routes.py, src/api/v1/custom_test/schemas.py Tag: Custom Tests - Create, submit, discard, and list custom tests.

All endpoints require Bearer Token authentication. Response data is encrypted when feature flag is enabled.


POST /custom-tests

Summary: Create Custom Test

Create a new custom test with configurable MCQ selection, duration, mode, and explanation settings.

Parameters

NameInRequiredDescription
course_idqueryrequiredCourse ID
x-dev-timeheaderoptionalEpoch ms (13 digits)

Request Body

{
  "mcq_selection_filters": {
    "selection_type": 1,
    "taxonomy_ids__in": [
      "60d5ec49f1b2c72e4c8b0001",
      "60d5ec49f1b2c72e4c8b0002"
    ],
    "year__in": [2022, 2023],
    "tag_ids__in": null,
    "question_type_distribution": {
      "1": 60,
      "2": 30,
      "3": 10
    }
  },
  "number_of_mcqs": 20,
  "duration_in_mins": 30,
  "test_mode": 2,
  "explanation_mode": 3,
  "explanation_detail_level": 2,
  "mcq_algorithm": 3
}
FieldTypeDefaultConstraintsDescription
mcq_selection_filtersobjectnulloptionalMCQ selection criteria (see below)
number_of_mcqsinteger10>0, <=120Number of MCQs to select
duration_in_minsinteger10>0, <=300Test duration in minutes
test_modeinteger1enumSTUDY=1, EXAM=2, MULTI_PLAYER=3
explanation_modeinteger1enumALL=1, WRONG_ONLY=2, SHOW_AT_END=3
explanation_detail_levelinteger1enumSHORT=1, FULL=2
mcq_algorithminteger3enumV1=1 (question type dist), V2=2 (taxonomy-first), V3=3 (simple filters)

mcq_selection_filters fields:

FieldTypeDescription
selection_typeintegerADAPTIVE=1, MANUAL=2. Default: ADAPTIVE
taxonomy_ids__inarrayFilter by taxonomy IDs
year__inarrayFilter by years (e.g. [2022, 2023])
tag_ids__inarrayFilter by tag IDs
question_type_distributionobjectMap of QuestionTypeEnum to percentage. Keys: "1" (PYQ), "2" (DQ), "3" (EQ). Must sum to 100

Conditions:

  • For V3 algorithm (mcq_algorithm=3): number_of_mcqs must be between CUSTOM_TEST_V3_MIN_MCQS and CUSTOM_TEST_V3_MAX_MCQS; error 1003
  • question_type_distribution values must sum to exactly 100; error 1003 if not
  • If no MCQs match the filters: error 6906 (CUSTOM_TEST_NO_MCQS_FOUND_DURING_CREATION)
  • Rate limits: error 6909 (free limit exceeded) or 6910 (daily limit exceeded)

Success Response

{
  "status": "success",
  "is_data_encrypted": 0,
  "data": {
    "id": "60d5ec49f1b2c72e4c8b9999",
    "short_uid": "CT00456",
    "mcqs": [
      {
        "id": "60d5ec49f1b2c72e4c8b4567",
        "display_uid": "UPSC-POL-001",
        "short_uid": "M00123",
        "question": "Which article of the Indian Constitution deals with the Right to Equality?",
        "question_format": 1,
        "q_pview": "Which article of the Indian Constitution deals with the Right to Equality?",
        "option_1": "Article 12",
        "selected_option_1_count": 245,
        "option_2": "Article 14",
        "selected_option_2_count": 1893,
        "option_3": "Article 19",
        "selected_option_3_count": 312,
        "option_4": "Article 21",
        "selected_option_4_count": 189,
        "correct_option": "option_2",
        "question_type": 1,
        "year": 2023,
        "root_taxonomy_id": "60d5ec49f1b2c72e4c8b0001",
        "taxonomy_ids": ["60d5ec49f1b2c72e4c8b0001", "60d5ec49f1b2c72e4c8b0002", "60d5ec49f1b2c72e4c8b0003"],
        "sole": "U2FsdGVkX1+abc123...",
        "sort_order": 15,
        "tag_ids": ["60d5ec49f1b2c72e4c8baaaa"],
        "docket_id": "60d5ec49f1b2c72e4c8bdddd",
        "is_deleted": false,
        "ordered_block_ids": ["B001"],
        "block_details": [
          {
            "id": "60d5ec49f1b2c72e4c8beeee",
            "short_uid": "B001",
            "docket_id": "60d5ec49f1b2c72e4c8bdddd",
            "title": "Right to Equality - Key Provisions",
            "conte": "U2FsdGVkX1+xyz789...",
            "is_deleted": false
          }
        ]
      }
    ],
    "duration_in_mins": 30,
    "course_id": 1,
    "creation_params": {
      "number_of_mcqs": 20,
      "duration_in_mins": 30,
      "test_mode": 2,
      "explanation_mode": 3,
      "explanation_detail_level": 2,
      "mcq_algorithm": 3,
      "mcq_selection_filters": { ... }
    },
    "sort_order": 5,
    "l1_taxonomy_ids": ["60d5ec49f1b2c72e4c8b0001"],
    "status": 2,
    "message": null,
    "l1_tax_ids": ["60d5ec49f1b2c72e4c8b0001"]
  },
  "error": null,
  "app_actions": null
}
FieldDescription
statusAlways LIVE=2 on creation
messagePresent when fewer MCQs than requested, e.g. “You requested 20 but we only found 15 unattempted MCQs”
mcqsFull MCQ details in v1 MCQ detail shape (same as /mcqs/sync)
l1_taxonomy_ids / l1_tax_idsUnique L1 root taxonomy IDs across all MCQs in this test

Error Responses

Error CodeCondition
1003Invalid parameters (V3 MCQ count out of range, distribution doesn’t sum to 100)
6906No MCQs found matching the selection filters
6909Free custom test limit exceeded
6910Daily custom test limit exceeded

POST /custom-tests/{custom_test_id}/discard

Summary: Discard Custom Test

Discard a custom test without submission. Marks the test as finished (status changes from LIVE to DISCARDED).

Parameters

NameInRequiredDescription
custom_test_idpathrequiredCustom test ID (ObjectId)
course_idqueryrequiredCourse ID
x-dev-timeheaderoptionalEpoch ms (13 digits)

Success Response

{
  "status": "success",
  "is_data_encrypted": 0,
  "data": null,
  "error": null,
  "app_actions": null
}

Note: data is null (JSON null). Never encrypted even when feature flag is on.

Error Responses

Error CodeCondition
6900Custom test not found
1010Test already submitted or discarded (OPERATION_NOT_ALLOWED)

POST /custom-tests/{custom_test_id}/submission

Summary: Submit Custom Test

Submit answers for a custom test. Calculates results with scoring, analytics, and percentile distribution.

Parameters

NameInRequiredDescription
custom_test_idpathrequiredCustom test ID (ObjectId)
course_idqueryrequiredCourse ID
x-dev-timeheaderoptionalEpoch ms (13 digits)

Request Body

{
  "answers": {
    "60d5ec49f1b2c72e4c8b4567": "option_2",
    "60d5ec49f1b2c72e4c8b4568": "option_1",
    "60d5ec49f1b2c72e4c8b4569": -1,
    "60d5ec49f1b2c72e4c8b456a": "option_3"
  },
  "started_at": 1714400000000,
  "ended_at": 1714401800000,
  "silly_mistake_mcq_ids": ["60d5ec49f1b2c72e4c8b4568"],
  "streak": 3,
  "guessed_mcq_ids": ["60d5ec49f1b2c72e4c8b456a"],
  "marked_for_review_mcq_ids": ["60d5ec49f1b2c72e4c8b4569"]
}
FieldRequiredDescription
answersrequiredDict: key = mcq_id (ObjectId), value = "option_1" through "option_4", or -1 for skipped
started_atoptionalTest start time in epoch ms
ended_atoptionalTest end time in epoch ms
silly_mistake_mcq_idsoptionalMCQ IDs the user marked as silly mistakes
streakoptionalConsecutive correct answers streak
guessed_mcq_idsoptionalMCQ IDs the user indicated were guesses (defaults to empty list)
marked_for_review_mcq_idsoptionalMCQ IDs marked for later review (defaults to empty list)

Conditions:

  • Answer values must be valid options ("option_1" through "option_4") or -1; error 1003 if invalid
  • List values in answers are auto-flattened to first item (validator handles array-wrapped values)

Scoring

  • +2 per correct answer
  • -0.66 per wrong answer
  • 0 for unattempted/skipped (-1)
  • Total marks can be negative

Success Response

{
  "status": "success",
  "is_data_encrypted": 0,
  "data": {
    "id": "60d5ec49f1b2c72e4c8bffff",
    "total_correct_count": 12,
    "marks": "21.36",
    "total_mcq_count": 20,
    "streak": 3,
    "duration_in_seconds": 1800,
    "percentile_distribution": {
      "0-10": 5,
      "10-20": 12,
      "20-30": 25,
      "30-40": 35,
      "40-50": 18,
      "50-60": 3,
      "60-70": 1,
      "70-80": 1,
      "80-90": 0,
      "90-100": 0
    },
    "taxonomy_wise_scores": [
      {
        "taxonomy_id": "60d5ec49f1b2c72e4c8b0001",
        "taxonomy_name": "Polity",
        "total_count": 8,
        "correct_count": 5,
        "marks": "7.02"
      },
      {
        "taxonomy_id": "60d5ec49f1b2c72e4c8b0002",
        "taxonomy_name": "History",
        "total_count": 12,
        "correct_count": 7,
        "marks": "14.34"
      }
    ],
    "custom_test_sort_order": 5
  },
  "error": null,
  "app_actions": null
}
FieldDescription
marksDecimal string. Scoring: +2 correct, -0.66 wrong, 0 skipped. Can be negative
duration_in_secondsCalculated from ended_at - started_at. 0 if timestamps not provided
percentile_distributionDistribution of user scores across percentile ranges
taxonomy_wise_scoresPer-subject breakdown of performance

Error Responses

Error CodeCondition
6900Custom test not found
1010Test already submitted or discarded (OPERATION_NOT_ALLOWED)
1003Invalid answer values

GET /custom-tests/sync

Summary: Sync Custom Tests by Updated At

Retrieve a paginated list of custom tests (lightweight, without MCQ details).

Parameters

NameInRequiredConstraintsDescription
course_idqueryrequiredCourseEnumCourse ID
limitqueryoptional1-120, default 10Number of results
next_cursorqueryoptionalbase64 stringForward pagination cursor
prev_cursorqueryoptionalbase64 stringBackward pagination cursor
x-dev-timeheaderoptional13 digitsEpoch ms

Success Response

{
  "status": "success",
  "is_data_encrypted": 0,
  "data": [
    {
      "id": "60d5ec49f1b2c72e4c8b9999",
      "short_uid": "CT00456",
      "number_of_mcqs": 20,
      "updated_at": 1714401800000,
      "status": 3,
      "created_at": 1714400000000,
      "is_deleted": false,
      "sort_order": 5,
      "marks": "21.36",
      "l1_tax_ids": ["60d5ec49f1b2c72e4c8b0001", "60d5ec49f1b2c72e4c8b0002"]
    },
    {
      "id": "60d5ec49f1b2c72e4c8b8888",
      "short_uid": "CT00455",
      "number_of_mcqs": 10,
      "updated_at": 1714300000000,
      "status": 1,
      "created_at": 1714300000000,
      "is_deleted": false,
      "sort_order": 4,
      "marks": null,
      "l1_tax_ids": ["60d5ec49f1b2c72e4c8b0001"]
    }
  ],
  "error": null,
  "app_actions": null,
  "pagination": {
    "next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0MzAwMDAwMDAwLCJpZCI6IjYwZDVlYzQ5ZjFiMmM3MmU0YzhiODg4OCJ9",
    "prev_cursor": null,
    "limit": 10,
    "has_more": false
  }
}
FieldDescription
statusDISCARDED=1, LIVE=2, SUBMITTED=3
marksDecimal string when submitted; null when not yet submitted. Can be negative

GET /custom-tests/{custom_test_id}

Summary: Get Custom Test By Id Or Short Id

Retrieve the full custom test including all MCQ details. When submitted, each MCQ includes the user’s selected option.

Parameters

NameInRequiredDescription
custom_test_idpathrequiredCustom test ObjectId or short_uid string
course_idqueryrequiredCourse ID
x-dev-timeheaderoptionalEpoch ms (13 digits)

Success Response (submitted test)

{
  "status": "success",
  "is_data_encrypted": 0,
  "data": {
    "id": "60d5ec49f1b2c72e4c8b9999",
    "short_uid": "CT00456",
    "mcqs": [
      {
        "id": "60d5ec49f1b2c72e4c8b4567",
        "short_uid": "M00123",
        "question": "Which article of the Indian Constitution deals with the Right to Equality?",
        "question_format": 1,
        "option_1": "Article 12",
        "option_2": "Article 14",
        "option_3": "Article 19",
        "option_4": "Article 21",
        "correct_option": "option_2",
        "question_type": 1,
        "year": 2023,
        "taxonomy_ids": ["60d5ec49f1b2c72e4c8b0001", "60d5ec49f1b2c72e4c8b0002", "60d5ec49f1b2c72e4c8b0003"],
        "selected_option": "option_2",
        "block_details": [ ... ]
      }
    ],
    "duration_in_mins": 30,
    "result": {
      "id": "60d5ec49f1b2c72e4c8bffff",
      "total_correct_count": 12,
      "marks": "21.36",
      "total_mcq_count": 20,
      "streak": 3,
      "duration_in_seconds": 1800,
      "percentile_distribution": { ... },
      "taxonomy_wise_scores": [ ... ],
      "custom_test_sort_order": 5
    },
    "submission": {
      "id": "60d5ec49f1b2c72e4c8beeee",
      "answers": {
        "60d5ec49f1b2c72e4c8b4567": "option_2",
        "60d5ec49f1b2c72e4c8b4568": "option_1",
        "60d5ec49f1b2c72e4c8b4569": "-1"
      },
      "started_at": 1714400000000,
      "ended_at": 1714401800000,
      "silly_mistake_mcq_ids": ["60d5ec49f1b2c72e4c8b4568"],
      "streak": 3,
      "guessed_mcq_ids": [],
      "marked_for_review_mcq_ids": ["60d5ec49f1b2c72e4c8b4569"]
    },
    "creation_params": {
      "number_of_mcqs": 20,
      "duration_in_mins": 30,
      "test_mode": 2,
      "mcq_algorithm": 3
    },
    "sort_order": 5,
    "l1_taxonomy_ids": ["60d5ec49f1b2c72e4c8b0001", "60d5ec49f1b2c72e4c8b0002"],
    "status": 3,
    "l1_tax_ids": ["60d5ec49f1b2c72e4c8b0001", "60d5ec49f1b2c72e4c8b0002"]
  },
  "error": null,
  "app_actions": null
}

Notes:

  • mcqs[].selected_option is present only when test is submitted. Contains "option_1" through "option_4", "-1" for skipped, or null
  • result is null when test is LIVE or DISCARDED
  • submission is null when test is LIVE or DISCARDED
  • submission.answers values are always strings (even -1 becomes "-1")

Error Responses

Error CodeCondition
6900Custom test not found