Skip to main content

API Reference

Complete REST API documentation for the NS Exam Portal.

Overview

All endpoints require Firebase ID token authentication via Authorization: Bearer <token> header, except for public endpoints.

Authentication

JWT Token Requirements

  • Header: Authorization: Bearer <firebase_id_token>
  • Token Source: Firebase Authentication SDK
  • Expiry: 1 hour (automatic refresh in client)
  • Guest Access: Use attempt_token query parameter for guest operations

Guest Access Pattern

GET /api/attempts/:attemptId?attempt_token=<token>
Headers: Authorization: Bearer <guest_firebase_token>

Example Request

curl -X GET https://api.nssoftwaresolutions.in/api/tests \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Core Endpoints

Clients Management

List All Clients

GET /api/clients

Auth: Super Admin only

Response:

{
"clients": [
{
"id": "client-uuid",
"name": "Organization Name",
"address": "123 Main St",
"logo_url": "https://...",
"active_status": 1,
"created_at": "2024-01-15T10:00:00Z"
}
]
}

Create Client

POST /api/clients

Body:

{
"name": "New Organization",
"address": "Street Address",
"logo_url": "https://logo.url/image.png",
"plan_id": "starter"
}

Update Client

PUT /api/clients/:clientId

Tests Management

List Tests

GET /api/tests?client_id={id}&status=published

Query Parameters:

  • client_id (required for admins)
  • status: draft, published, archived
  • folder_id: filter by folder
  • share_code: public lookup

Get Test Details

GET /api/tests/{testId}

Create Test

POST /api/tests

Body:

{
"test_name": "Math Exam",
"timer": 60,
"shuffle": 1,
"allow_review": 1,
"negative_marking": 0,
"folder_id": "folder-uuid",
"allow_guests": true,
"show_results_after_submission": true
}

Update Test

PUT /api/tests/{testId}

Publish Test

POST /api/tests/{testId}/publish

Delete Test

DELETE /api/tests/{testId}

Clone Test

POST /api/rpc/clone-test

Auth: Client Admin only

Body:

{
"test_id": "original-test-uuid"
}

Response: 201 Created with cloned test (draft status, new share code)

Attempts

Start Attempt

POST /api/attempts

Body:

{
"test_id": "test-uuid",
"student_id": "student-uuid"
}

Response:

{
"id": "attempt-uuid",
"student_id": "student-uuid",
"test_id": "test-uuid",
"status": "in_progress",
"attempt_token": "secure-token-for-guests"
}

Get Attempt

GET /api/attempts/{attemptId}

Query Parameters: attempt_token (required for guest access)

List Attempts

GET /api/attempts?student_id={studentId}

Answers Management

Save/Update Answers (Auto-Save)

POST /api/attempt-answers

Auth: Student (own attempt) or Guest (with attempt_token)

Body (supports batch array):

[
{
"attempt_id": "attempt-uuid",
"question_id": "question-uuid",
"selected_option": "B",
"marked_for_review": false
}
]

Get Saved Answers

GET /api/attempt-answers/{attemptId}?attempt_token={token}

Submission & Grading

Submit Attempt for Grading

POST /api/rpc/submit-attempt

Body:

{
"attempt_id": "attempt-uuid",
"time_taken": 3600
}

Response:

{
"score": 85,
"total_marks": 100,
"time_taken": 3600,
"status": "submitted"
}

Submit Answer

POST /api/attempts/{attemptId}/answers

Body:

{
"question_id": "question-uuid",
"student_answer": "option_a"
}

Submit Attempt

POST /api/attempts/{attemptId}/submit

Response:

{
"status": "submitted",
"score": 85,
"total_marks": 100,
"submitted_at": "2024-01-15T11:30:00Z"
}

Download Report

GET /api/attempts/{attemptId}/report?attempt_token={token}

Response: XLSX file download (3-sheet workbook)

Questions

Get Questions

GET /api/questions?folder_id={id}&client_id={id}

Create Question

POST /api/questions

Body:

{
"question_text": "What is 2+2?",
"question_type": "mcq",
"option_a": "3",
"option_b": "4",
"option_c": "5",
"option_d": "6",
"correct_answer": "B",
"marks": 1,
"difficulty": "easy"
}

Import Questions

POST /api/questions/import

Body: Form data with CSV file

CSV Format:

question_text,question_type,option_a,option_b,option_c,option_d,correct_answer,marks,difficulty
"What is 2+2?",mcq,"3","4","5","6","B",1,easy

Rollback Import

DELETE /api/questions?import_batch_id={batchId}

Proctoring Events

Log Proctoring Event

POST /api/proctoring/events

Body:

{
"attempt_id": "attempt-uuid",
"test_id": "test-uuid",
"event_type": "TAB_SWITCH",
"evidence": "base64-encoded-image"
}

Event Types:

Event TypeSeverityScoreEvidence Required
TAB_SWITCHLOW1No
WINDOW_BLURLOW1No
FULLSCREEN_EXITMEDIUM2No
NO_FACEMEDIUM3Yes
MULTIPLE_FACESHIGH5Yes
CAMERA_DISCONNECTEDHIGH5Yes
CAMERA_PERMISSION_DENIEDHIGH5No

List Proctoring Events

GET /api/proctoring/events?attempt_id={id}&page=1&limit=20

Subscriptions Management

List Client Subscriptions

GET /api/superadmin/subscriptions

Auth: Super Admin only

Update Client Subscription

PUT /api/superadmin/subscriptions/{clientId}

Body:

{
"plan_id": "growth",
"expiry_date": "2027-06-24"
}

List Subscription Requests

GET /api/subscription-requests

Approve Subscription Request

PUT /api/subscription-requests/{requestId}/approve

Packages (Pay Per Test)

Request Package

POST /api/packages/request

List Package Requests

GET /api/packages/requests

Auth: Super Admin only

Approve Package Request

PUT /api/packages/requests/{requestId}/approve

List Available Packages

GET /api/packages/available

Analytics

Get Dashboard Statistics

GET /api/stats

Response: Role-specific metrics (Super Admin vs Client Admin)

Get Admin Dashboard

GET /api/stats/dashboard

Get Test Analytics

GET /api/analytics/tests/{testId}

Response:

{
"total_attempts": 150,
"completed": 148,
"average_score": 72.5,
"difficulty_analysis": {...}
}

Get Student Analytics

GET /api/analytics/students/{studentId}

Audit Logging

List Audit Logs

GET /api/superadmin/audit-logs?page=1&limit=50

Auth: Super Admin only

Query Parameters:

  • page: Pagination page number
  • limit: Results per page
  • user_id: Filter by user
  • action: Filter by action type
  • entity_type: Filter by entity type
  • start_date / end_date: Date range filter

Settings & Configuration

Get Platform Settings

GET /api/settings

Update Platform Settings

PUT /api/settings

Auth: Super Admin only

Body:

{
"maintenance_mode": false,
"announcement_banner": "Platform under maintenance",
"registration_enabled": true
}

User Management

Create User

POST /api/create-user

Auth: Client Admin or Super Admin

Body:

{
"email": "student@example.com",
"password": "temporary-password",
"name": "Student Name",
"role": "student",
"client_id": "client-uuid"
}

Note: Creates Firebase Auth account + profile + role in Turso

Profiles

Get Profiles

GET /api/profiles?client_id={id}

Update Profile

PUT /api/profiles/{profileId}

Permissions Matrix

EndpointSuper AdminClient AdminStudentGuest
GET /api/clients
POST /api/clients
PUT /api/clients/:id
GET /api/tests✅ Tenant✅ Tenant✅ Tenant✅ Share Code
POST /api/tests✅ Tenant
PUT /api/tests/:id✅ Tenant
POST /api/rpc/clone-test✅ Tenant
POST /api/attempts✅ Tenant✅ Own✅ Token
GET /api/attempts✅ Tenant✅ Own✅ Token
POST /api/attempt-answers✅ Own✅ Token
GET /api/attempt-answers✅ Own✅ Token
POST /api/rpc/submit-attempt✅ Own✅ Token
GET /api/attempts/:id/report✅ Tenant✅ Own✅ Token
POST /api/proctoring/events✅ Own✅ Token
GET /api/superadmin/*
GET /api/settings
PUT /api/settings
POST /api/questions✅ Tenant
POST /api/questions/import✅ Tenant
GET /api/stats✅ Tenant
GET /api/create-user✅ Tenant

Rate Limiting

Global Rate Limiter

  • Limit: 1000 requests per 15 minutes
  • Response: 429 Too Many Requests

Strict Rate Limiter (Sensitive Endpoints)

  • Limit: 100 requests per minute
  • Endpoints:
    • /api/create-user
    • /api/attempts
    • /api/attempt-answers
    • /api/proctoring/events
    • /api/rpc/submit-attempt

Common Response Patterns

Success Response

{
"data": {
/* resource or array */
},
"message": "Operation successful"
}

Standard Error Response

{
"error": "error_code",
"message": "Human readable error message",
"status": 400
}

Pagination Response

GET /api/tests?page=1&limit=20

Response:

{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 250,
"pages": 13
}
}

Common Error Codes

CodeStatusDescription
UNAUTHORIZED401Invalid or missing token
FORBIDDEN403Insufficient permissions
NOT_FOUND404Resource not found
VALIDATION_ERROR400Invalid input data
CONFLICT409Resource already exists
RATE_LIMITED429Too many requests
SERVER_ERROR500Internal server error

Health Checks

Backend Health

GET /health

Auth: None (public)

Response:

{
"status": "ok"
}

API Info

GET /

Auth: None (public)

Response:

{
"name": "NS Exam Portal Backend API",
"version": "1.0.0",
"status": "healthy"
}

Response Headers

Response headers often contain additional information:

X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1705320000
X-Request-Id: req-12345

Next Steps