V1 API Sync
Source: OpenAPI 3.1.0 spec + Keystone backend source code Tag: Sync - Cursor-based sync endpoints for offline-first clients.
All endpoints require Bearer Token authentication. All use identical cursor-based pagination parameters. Response data is encrypted when feature flag is enabled.
Common Sync Parameters
| Name | In | Required | Constraints | Description |
|---|---|---|---|---|
course_id | query | required | CourseEnum | Course ID |
limit | query | optional | 1-120, default 10 | Number of results to return |
next_cursor | query | optional | base64 string | Forward pagination cursor from previous response |
prev_cursor | query | optional | base64 string | Backward pagination cursor |
x-dev-time | header | optional | 13 digits | Epoch ms |
How Cursors Work
All sync endpoints use timestamp+id cursors encoded as base64(json({updated_at, id})):
- Omit
next_cursorfor the first request - Backend queries:
WHERE updated_at > cursor.updated_at OR (updated_at == cursor.updated_at AND id > cursor.id) - Fetches
limit + 1items. If more thanlimit, setshas_more=trueand returnsnext_cursor - Pass
next_cursorfrom response into the next request
GET /mcqs/sync
Summary: List PYQ MCQs by Updated At (sync)
Retrieve a paginated list of PYQ MCQs with forward-only pagination by updated_at. Only PYQs are returned (DQ/EQ excluded). Filter by year or taxonomy_id, not both.
Additional Parameters
| Name | In | Required | Description |
|---|---|---|---|
year | query | optional | Filter MCQs by year (e.g. 2023). Mutually exclusive with taxonomy_id |
taxonomy_id | query | optional | Filter MCQs by taxonomy ID. Mutually exclusive with year |
Condition: year and taxonomy_id are mutually exclusive; error 1003 if both provided.
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": [
{
"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
}
]
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0NDAwMDAwMDAwLCJpZCI6IjYwZDVlYzQ5ZjFiMmM3MmU0YzhiNDU2NyJ9",
"prev_cursor": null,
"limit": 10,
"has_more": true
}
}
See MCQ docs for full field descriptions.
GET /tags/sync
Summary: Sync Tags by Updated At
Retrieve a paginated list of tags with forward-only pagination based on updated_at.
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": [
{
"id": "60d5ec49f1b2c72e4c8baaaa",
"name": "Fundamental Rights",
"updated_at": 1714400000000,
"created_at": 1714300000000,
"is_deleted": false
},
{
"id": "60d5ec49f1b2c72e4c8baaab",
"name": "Directive Principles",
"updated_at": 1714350000000,
"created_at": 1714200000000,
"is_deleted": false
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0MzUwMDAwMDAwLCJpZCI6IjYwZDVlYzQ5ZjFiMmM3MmU0YzhiYWFhYiJ9",
"prev_cursor": null,
"limit": 10,
"has_more": false
}
}
GET /dockets/sync
Summary: Sync Dockets by Updated At (Lightweight)
Retrieve a lightweight paginated list of dockets for sync. Excludes heavy fields (body, ai_run_results, audio_generated, trending_count, display_uid, tag_ids) to minimize payload size. Includes mcq_ids and mcq_count.
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": [
{
"id": "60d5ec49f1b2c72e4c8bdddd",
"short_uid": "D00456",
"title": "Fundamental Rights under Part III",
"root_taxonomy_id": "60d5ec49f1b2c72e4c8b0001",
"taxonomy_ids": [
"60d5ec49f1b2c72e4c8b0001",
"60d5ec49f1b2c72e4c8b0002",
"60d5ec49f1b2c72e4c8b0003"
],
"sort_order": 5,
"is_deleted": false,
"mcq_ids": ["M00123", "M00124", "M00125"],
"mcq_count": {
"pyq_count": 5,
"dq_count": 2,
"eq_count": 1
}
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0NDAwMDAwMDAwfQ==",
"prev_cursor": null,
"limit": 10,
"has_more": true
}
}
| Field | Description |
|---|---|
mcq_ids | MCQ short UIDs (not ObjectIds) denormalized from l4_docket_id |
mcq_count.pyq_count | Number of PYQ-type MCQs linked to this docket |
mcq_count.dq_count | Number of DQ-type MCQs |
mcq_count.eq_count | Number of EQ-type MCQs |
GET /taxonomy/sync
Summary: Sync Taxonomies by Updated At
Retrieve a paginated list of taxonomies with forward-only pagination based on updated_at. Taxonomies form a hierarchy: L1 (root) -> L2 -> L3 -> L4 (docket leaf).
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": [
{
"id": "60d5ec49f1b2c72e4c8b0001",
"display_uid": "UPSC-POL",
"short_uid": "T00001",
"name": "Polity & Governance",
"description": "Indian Polity, Constitution, and Governance",
"parent_id": null,
"course_id": 1,
"sort_order": 1,
"mcq_count": {
"pyq_count": 450,
"dq_count": 120,
"eq_count": 80
},
"docket_count": 35,
"updated_at": 1714400000000,
"is_deleted": false,
"status": 2,
"emoji": "🏛️"
},
{
"id": "60d5ec49f1b2c72e4c8b0002",
"display_uid": "UPSC-POL-FR",
"short_uid": "T00002",
"name": "Fundamental Rights",
"description": "Rights guaranteed under Part III of the Constitution",
"parent_id": "60d5ec49f1b2c72e4c8b0001",
"course_id": 1,
"sort_order": 1,
"mcq_count": {
"pyq_count": 85,
"dq_count": 20,
"eq_count": 15
},
"docket_count": 8,
"updated_at": 1714380000000,
"is_deleted": false,
"status": 2,
"emoji": null
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0MzgwMDAwMDAwfQ==",
"prev_cursor": null,
"limit": 10,
"has_more": true
}
}
| Field | Description |
|---|---|
parent_id | null if L1 (root taxonomy). Otherwise, parent taxonomy ObjectId |
mcq_count | Published MCQ counts by type for this taxonomy |
docket_count | Number of dockets associated with this taxonomy |
status | DRAFT=1, PUBLISHED=2, UNPUBLISHED=3, ARCHIVE=4 |
emoji | Display emoji, primarily for L1 (root) taxonomies. null for deeper levels |
course_id | CourseEnum value |
GET /years/sync
Summary: Sync Years by Updated At
Retrieve a paginated list of years with published MCQ counts.
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": [
{
"year": 2023,
"pyq_count": 150,
"dq_count": 45,
"eq_count": 30,
"is_deleted": false,
"status": 2
},
{
"year": 2022,
"pyq_count": 140,
"dq_count": 50,
"eq_count": 25,
"is_deleted": false,
"status": 2
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0MzAwMDAwMDAwfQ==",
"prev_cursor": null,
"limit": 10,
"has_more": false
}
}
| Field | Description |
|---|---|
pyq_count | Number of published PYQs for this year |
dq_count | Number of published DQs |
eq_count | Number of published EQs |
status | DRAFT=1, PUBLISHED=2, UNPUBLISHED=3, ARCHIVE=4 |
GET /custom-tests/sync
Summary: Sync Custom Tests by Updated At
Retrieve a paginated list of the user’s custom tests (lightweight, without MCQ details).
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"]
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0NDAxODAwMDAwfQ==",
"prev_cursor": null,
"limit": 10,
"has_more": false
}
}
| Field | Description |
|---|---|
status | DISCARDED=1, LIVE=2, SUBMITTED=3 |
marks | Decimal string when submitted; null when LIVE or DISCARDED. Can be negative |
See Custom Tests docs for full details.
GET /bookmark-collections/sync
Summary: Sync Bookmark Collections
Get a paginated list of the user’s bookmark collections.
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": [
{
"id": "60d5ec49f1b2c72e4c8bbbbb",
"name": "All Bookmarks",
"description": null,
"is_default": true,
"course_id": 1,
"emoji": "📚",
"mcq_count": 47,
"short_uid": "BC000",
"is_deleted": false,
"created_at": 1714300000000
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": null,
"prev_cursor": null,
"limit": 10,
"has_more": false
}
}
See Bookmark Collections docs for full details.
GET /mcqs_attrs/sync
Summary: List MCQ Reactions
Retrieve a paginated list of the user’s MCQ attributes (reactions, bookmarks, attempt history).
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": [
{
"id": "60d5ec49f1b2c72e4c8baaaa",
"mcq_id": "60d5ec49f1b2c72e4c8b4567",
"last_attempt_option": "option_2",
"guessed": false,
"bookmark_status": 1,
"bookmark_collection_ids": ["60d5ec49f1b2c72e4c8bcccc"],
"bookmarked_at": 1714400000000,
"like_status": 1,
"root_taxonomy_id": "60d5ec49f1b2c72e4c8b0001",
"taxonomy_ids": ["60d5ec49f1b2c72e4c8b0001", "60d5ec49f1b2c72e4c8b0002", "60d5ec49f1b2c72e4c8b0003"],
"year": 2023
}
],
"error": null,
"app_actions": null,
"pagination": {
"next_cursor": "eyJ1cGRhdGVkX2F0IjoxNzE0NDAwMDAwMDAwfQ==",
"prev_cursor": null,
"limit": 10,
"has_more": true
}
}
See MCQ Actions docs for full field details.