V1 API Auth
Source: OpenAPI 3.1.0 spec +
src/api/v1/user/routes.py,src/api/v1/user/schemas.pyTag: Auth - Login, OTP, token refresh, logout.
POST /auth/send-email-otp
Summary: Request OTP
Send a one-time password (OTP) to the provided email for authentication.
Auth: None required (public endpoint)
Parameters
| Name | In | Required | Description |
|---|---|---|---|
platform | query | required | Login platform: web, ios, ios_t, an, an_t |
application | query | required | Application identifier: wri, prx, arivu |
device_id | query | optional | Device ID for mobile platforms |
x-dev-time | header | optional | Epoch time in ms (13 digits) from client device |
Request Body
{
"email": "user@example.com",
"encrypted_data": null
}
Conditions:
- At least one of
emailorencrypted_datamust be provided; error code1003(INVALID_PARAMETERS) if neither - When
encrypted_datais provided, it is decrypted using CryptoV2.2 (secret=device_id,salt=x_dev_time) to extract the email - After decryption, email must be non-empty; error code
1003if empty
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": {
"message": "OTP sent successfully to user@example.com",
"expiry_time": 1714400300000,
"oti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"error": null,
"app_actions": null
}
| Field | Type | Description |
|---|---|---|
message | string | Confirmation message |
expiry_time | integer | Epoch ms when OTP expires |
oti | string | One-time identifier for this OTP request (pass to verify) |
Error Responses
{
"status": "failure",
"is_data_encrypted": 0,
"data": null,
"error": {
"code": 1003,
"message": "Either email or encrypted_data is required"
},
"app_actions": null
}
POST /auth/verify-email-otp
Summary: Verify OTP
Verify the OTP sent to the email and generate authentication tokens.
Auth: None required (public endpoint)
Parameters
| Name | In | Required | Description |
|---|---|---|---|
platform | query | required | Login platform |
application | query | required | Application identifier |
device_id | query | optional | Device ID for mobile platforms |
course_id | query | optional | Course ID - when provided, includes subscription details and creates default bookmark collection |
x-dev-time | header | optional | Epoch ms (13 digits) |
Request Body
{
"email": "user@example.com",
"otp": "123456",
"oti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"encrypted_data": null
}
Conditions:
- Either
encrypted_dataOR all three of (email,otp,oti) must be provided; error1003if neither - When
encrypted_datais set, decrypts to extract email/otp/oti using CryptoV2.2 otplength must match server-configured OTP length
Business Logic
- Verifies OTP against cache and revokes the OTI (one-time use)
- Creates user if not exists via
get_or_create_user_by_email() - If
course_idprovided: creates default “All Bookmarks” collection if missing - Generates JWT access token + refresh token
- If
course_idprovided: generates CloudFront signed cookies (TTL = access_token_expiry + 60 minutes)
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user_details": {
"id": "507f1f77bcf86cd799439011",
"email": "user@example.com",
"username": null,
"phone_number": null,
"is_phone_validated": false,
"name": {
"first_name": "John",
"last_name": "Doe"
},
"date_of_birth": null,
"gender": 3,
"bio": null,
"background_info": null,
"attempt_number": null,
"attempt_year": null,
"profile_picture": null,
"address": null,
"social_links": [],
"mcq_stats": null,
"subscription": null
},
"signed_cookies": {
"cookies": {
"CloudFront-Policy": "eyJTdGF0ZW1lbnQiOlt7...",
"CloudFront-Signature": "abc123...",
"CloudFront-Key-Pair-Id": "KXYZ123..."
},
"domain": "cdn.example.com"
}
},
"error": null,
"app_actions": null
}
Notes:
signed_cookiesisnullwhencourse_idis not providedmcq_statsandsubscriptioninsideuser_detailsarenullwhencourse_idis not provided- Auth responses are always plaintext (never encrypted) regardless of feature flag
Error Responses
| Error Code | Condition |
|---|---|
| 1003 | Missing email/otp/oti or encrypted_data |
| 2002 | OTP is incorrect |
| 2003 | OTP has expired |
| 2008 | OTI has expired |
POST /auth/login/apple/mobile
Summary: Apple Mobile Login
Handle Apple Sign In for native iOS/Android clients using identity token.
Auth: None required (public endpoint)
Parameters
| Name | In | Required | Description |
|---|---|---|---|
course_id | query | optional | Course ID - when provided, includes subscription and creates default bookmark collection |
x-dev-time | header | optional | Epoch ms (13 digits) |
Request Body
{
"token": "eyJraWQiOiJXNldjT0...",
"device_id": "device-uuid-12345",
"platform": "ios",
"application": "arivu",
"first_name": "John",
"last_name": "Doe"
}
| Field | Required | Description |
|---|---|---|
token | required | Apple identity token (ASAuthorizationAppleIDCredential.identityToken) |
device_id | required | Unique device ID |
platform | required | ios, ios_t, an, an_t |
application | required | wri, prx, arivu |
first_name | optional | Only provided on initial Apple Sign In |
last_name | optional | Only provided on initial Apple Sign In |
Business Logic
- Validates Apple identity token via
get_apple_user_mobile()to extractapple_sso_idand email - Creates or retrieves user by Apple SSO ID
- If
course_idprovided: creates default bookmark collection + generates CloudFront cookies - Returns JWT tokens
Success Response
Same shape as POST /auth/verify-email-otp response (TokenResponse with user_details and optional signed_cookies).
Error Responses
| Error Code | Condition |
|---|---|
| 2005 | Apple token verification failed (SSO_ERROR) |
POST /auth/login/google/mobile
Summary: Google Login Mobile
Handle Google authentication for mobile devices using ID token.
Auth: None required (public endpoint)
Parameters
| Name | In | Required | Description |
|---|---|---|---|
course_id | query | optional | Course ID |
x-dev-time | header | optional | Epoch ms (13 digits) |
Request Body
{
"token": "eyJhbGciOiJSUzI1NiIs...",
"device_id": "device-uuid-12345",
"platform": "an",
"application": "arivu"
}
All fields required.
Business Logic
- Validates Google ID token via
get_google_user_mobile()to extractgoogle_sso_id, email, picture, first_name, last_name - Creates or retrieves user by Google SSO ID
- If
course_idprovided: default bookmark collection + CloudFront cookies - Returns JWT tokens
Success Response
Same shape as POST /auth/verify-email-otp response.
Error Responses
| Error Code | Condition |
|---|---|
| 2005 | Google token verification failed (SSO_ERROR) |
POST /auth/refresh
Summary: Refresh Token
Generate a new access token using a valid refresh token.
Auth: None required (uses refresh token in body)
Parameters
| Name | In | Required | Description |
|---|---|---|---|
course_id | query | optional | Course ID - when provided, generates CloudFront cookies |
x-dev-time | header | optional | Epoch ms (13 digits) |
Request Body
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"grant_type": "refresh_token"
}
| Field | Required | Description |
|---|---|---|
refresh_token | required | Valid refresh token JWT |
grant_type | required | Must be exactly "refresh_token" |
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"user_details": { ... },
"signed_cookies": { ... }
},
"error": null,
"app_actions": null
}
Error Responses
| Error Code | Condition |
|---|---|
| 3000 | Token invalid or revoked |
| 3003 | Refresh token expired |
POST /auth/logout
Summary: Logout
Invalidate the current session’s refresh token.
Auth: Bearer Token required
Parameters
| Name | In | Required | Description |
|---|---|---|---|
x-dev-time | header | optional | Epoch ms (13 digits) |
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": {
"message": "Successfully logged out"
},
"error": null,
"app_actions": null
}
Note: Response data is encrypted when encryption feature flag is enabled.
GET /auth/me
Summary: Get Current User
Retrieve the details of the currently authenticated user.
Auth: Bearer Token required
Parameters
| Name | In | Required | Description |
|---|---|---|---|
course_id | query | optional | When provided: includes mcq_stats and subscription in response |
x-dev-time | header | optional | Epoch ms (13 digits) |
Business Logic
- Fetches user details from the authenticated JWT claims
- If
course_idprovided:- Fetches MCQ stats via
CustomTestService.get_mcq_stats(user_id, course_id) - Fetches active subscription via
SubscriptionService.get_active_for_user_and_course(user_id, course_id)
- Fetches MCQ stats via
Success Response
{
"status": "success",
"is_data_encrypted": 0,
"data": {
"id": "507f1f77bcf86cd799439011",
"email": "user@example.com",
"username": "johndoe",
"phone_number": {
"code": "+91",
"value": "9876543210"
},
"is_phone_validated": true,
"name": {
"first_name": "John",
"last_name": "Doe"
},
"date_of_birth": 946684800000,
"gender": 1,
"bio": "Preparing for UPSC 2026",
"background_info": "Engineering graduate",
"attempt_number": 2,
"attempt_year": 2026,
"profile_picture": {
"filename": "profile_507f1f77.jpg",
"width": 400,
"height": 400,
"aspect_ratio": "1:1"
},
"address": {
"street": "123 Main St",
"city": "Bangalore",
"state": "Karnataka",
"zip_code": "560001",
"country": "India"
},
"social_links": [
{
"platform": "linkedin",
"url": "https://linkedin.com/in/johndoe"
}
],
"mcq_stats": {
"total_count": 150,
"pyq_count": 100,
"dq_count": 30,
"eq_count": 20
},
"subscription": {
"id": "60d5ec49f1b2c72e4c8b4567",
"plan_id": "60d5ec49f1b2c72e4c8b1234",
"order_id": "60d5ec49f1b2c72e4c8b7890",
"duration_in_days": 365,
"starts_at": 1714400000000,
"expires_at": 1745936000000,
"status": 1,
"subscription_components": [
{
"content_type": "taxonomy",
"content_id": "all"
}
],
"plan_components": null
}
},
"error": null,
"app_actions": null
}
Notes:
mcq_statsisnullwhencourse_idis not providedsubscriptionisnullwhencourse_idis not provided or user has no active subscriptionsubscription.status:1=ACTIVE,2=EXPIRED,3=CANCELLEDdate_of_birthis stored as midnight UTC epoch msgender:NON_BINARY=0,MALE=1,FEMALE=2,OTHER=3- Response
datais encrypted when feature flag is enabled