Version: 1.0
Base URL: /api
Last Updated: 2026-01-01
- Authentication
- User Endpoints
- Check-In Endpoints
- Problem Endpoints
- Dashboard Endpoints
- Cron Endpoints
- Error Handling
- Data Types
Authentication uses NextAuth.js with two providers:
- Magic Link (email-based passwordless)
- Google OAuth 2.0
All authenticated endpoints require a valid session cookie.
| Endpoint | Method | Description |
|---|---|---|
/api/auth/signin |
GET | Render sign-in page |
/api/auth/signin/:provider |
POST | Initiate sign-in |
/api/auth/signout |
POST | Sign out user |
/api/auth/session |
GET | Get current session |
/api/auth/callback/:provider |
GET | OAuth callback |
Sets up a new user's pledge after authentication.
POST /api/user/onboard
Content-Type: application/jsonRequest Body:
{
"pledgeDays": 90,
"reminderTime": "22:00",
"timezone": "Asia/Kolkata",
"phone": "+919876543210"
}| Field | Type | Required | Description |
|---|---|---|---|
pledgeDays |
integer | ✅ | Pledge duration (30, 60, 90, etc.) |
reminderTime |
string | ✅ | Daily reminder time (HH:mm) |
timezone |
string | ✅ | User timezone (IANA format) |
phone |
string | ❌ | WhatsApp number with country code |
Response (201 Created):
{
"success": true,
"data": {
"id": "uuid",
"email": "user@example.com",
"pledgeDays": 90,
"startDate": "2026-01-01",
"reminderTime": "22:00",
"timezone": "Asia/Kolkata",
"currentStreak": 0,
"maxStreak": 0,
"gems": 0
}
}GET /api/user/profileResponse (200 OK):
{
"success": true,
"data": {
"id": "uuid",
"email": "user@example.com",
"name": "John Doe",
"phone": "+919876543210",
"pledgeDays": 90,
"startDate": "2026-01-01",
"reminderTime": "22:00",
"timezone": "Asia/Kolkata",
"currentStreak": 16,
"maxStreak": 16,
"daysCompleted": 16,
"gems": 210
}
}PATCH /api/user/settings
Content-Type: application/jsonRequest Body:
{
"reminderTime": "21:00",
"phone": "+919876543210",
"dailyProblemLimit": 5
}Response (200 OK):
{
"success": true,
"message": "Settings updated"
}Primary check-in action.
POST /api/checkinRequest Body: None required
Response (200 OK):
{
"success": true,
"data": {
"date": "2026-01-01",
"completed": true,
"markedAt": "2026-01-01T18:30:00Z",
"streak": {
"current": 17,
"max": 17,
"gemsAwarded": 10,
"milestoneReached": null
}
}
}Response (409 Conflict - Already checked in):
{
"success": false,
"error": {
"code": "ALREADY_CHECKED_IN",
"message": "Today has already been marked complete"
}
}Response (403 Forbidden - Deadline passed):
{
"success": false,
"error": {
"code": "DEADLINE_PASSED",
"message": "Check-in deadline for today has passed"
}
}GET /api/checkin/todayResponse (200 OK):
{
"success": true,
"data": {
"date": "2026-01-01",
"completed": false,
"deadlineAt": "2026-01-01T22:00:00+05:30",
"timeRemaining": "3h 30m"
}
}POST /api/problems
Content-Type: application/jsonRequest Body:
{
"topic": "DYNAMIC_PROGRAMMING",
"name": "Longest Common Subsequence",
"difficulty": "MEDIUM",
"externalUrl": "https://leetcode.com/problems/longest-common-subsequence/"
}| Field | Type | Required | Description |
|---|---|---|---|
topic |
Topic (enum) | ✅ | Problem topic category |
name |
string | ✅ | Problem name (max 255 chars) |
difficulty |
Difficulty (enum) | ✅ | EASY, MEDIUM, or HARD |
externalUrl |
string | ❌ | LeetCode/external link |
Response (201 Created):
{
"success": true,
"data": {
"id": "uuid",
"topic": "DYNAMIC_PROGRAMMING",
"name": "Longest Common Subsequence",
"difficulty": "MEDIUM",
"createdAt": "2026-01-01T18:30:00Z"
}
}Response (400 Bad Request - Limit exceeded):
{
"success": false,
"error": {
"code": "PROBLEM_LIMIT_EXCEEDED",
"message": "Maximum 2 problems per day allowed"
}
}GET /api/problems/todayResponse (200 OK):
{
"success": true,
"data": {
"problems": [
{
"id": "uuid",
"topic": "DYNAMIC_PROGRAMMING",
"name": "Longest Common Subsequence",
"difficulty": "MEDIUM",
"tags": ["DP", "LCS"],
"notes": "Classic DP problem",
"externalUrl": "https://leetcode.com/...",
"createdAt": "2026-01-01T18:30:00Z"
}
],
"remaining": 1
}
}DELETE /api/problems/:idResponse (200 OK):
{
"success": true,
"message": "Problem deleted"
}Primary dashboard aggregation endpoint.
GET /api/dashboardResponse (200 OK):
{
"success": true,
"data": {
"user": {
"name": "John Doe",
"email": "user@example.com"
},
"pledge": {
"totalDays": 90,
"daysCompleted": 16,
"daysRemaining": 74,
"startDate": "2025-12-16",
"endDate": "2026-03-15"
},
"streak": {
"current": 16,
"max": 16
},
"gems": 210,
"today": {
"completed": false,
"deadlineAt": "2026-01-01T22:00:00+05:30",
"problemsLogged": 0
}
}
}GET /api/matrix?startDate=2025-12-01&endDate=2026-01-01Query Parameters:
| Param | Type | Required | Description |
|---|---|---|---|
startDate |
date | ❌ | Start of date range (default: pledge start) |
endDate |
date | ❌ | End of date range (default: today) |
Response (200 OK):
{
"success": true,
"data": {
"days": [
{
"date": "2025-12-16",
"completed": true,
"isMilestone": false,
"problems": [{ "topic": "ARRAYS", "difficulty": "EASY" }]
},
{
"date": "2025-12-17",
"completed": true,
"isMilestone": true,
"milestoneType": "7_DAY_STREAK",
"problems": []
},
{
"date": "2025-12-18",
"completed": false,
"isMilestone": false,
"problems": []
}
],
"topics": {
"ARRAYS": 5,
"DYNAMIC_PROGRAMMING": 3,
"GRAPHS": 2
}
}
}Caution
Cron endpoints are protected by a secret key. Include Authorization: Bearer <CRON_SECRET> header.
Triggered by scheduler at each user's reminder time.
POST /api/cron/reminder
Authorization: Bearer <CRON_SECRET>Response (200 OK):
{
"success": true,
"data": {
"remindersSent": 42,
"errors": 0
}
}Triggered daily after midnight to mark missed days and reset streaks.
POST /api/cron/process-day
Authorization: Bearer <CRON_SECRET>Response (200 OK):
{
"success": true,
"data": {
"processed": 150,
"missedDays": 12,
"streaksReset": 12,
"alertsSent": 12
}
}All errors follow this structure:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message",
"details": {}
}
}| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED |
401 | Not authenticated |
FORBIDDEN |
403 | Not authorized for action |
NOT_FOUND |
404 | Resource not found |
VALIDATION_ERROR |
400 | Invalid request body |
ALREADY_CHECKED_IN |
409 | Day already marked |
DEADLINE_PASSED |
403 | Check-in too late |
PROBLEM_LIMIT_EXCEEDED |
400 | Max 2 problems/day |
USER_NOT_ONBOARDED |
403 | Pledge not set up |
INTERNAL_ERROR |
500 | Server error |
Topic:
type Topic =
| "BASICS"
| "SORTING"
| "ARRAYS"
| "BINARY_SEARCH"
| "STRINGS"
| "LINKED_LISTS"
| "RECURSION"
| "BIT_MANIPULATION"
| "STACKS_QUEUES"
| "SLIDING_WINDOW"
| "HEAPS"
| "GREEDY"
| "BINARY_TREES"
| "BST"
| "GRAPHS"
| "DYNAMIC_PROGRAMMING"
| "TRIES"
| "OTHER";Difficulty:
type Difficulty = "EASY" | "MEDIUM" | "HARD";User:
interface User {
id: string;
email: string;
name?: string;
phone?: string;
pledgeDays: number;
startDate: string; // ISO date
reminderTime: string; // HH:mm
timezone: string;
currentStreak: number;
maxStreak: number;
daysCompleted: number;
gems: number;
}DailyLog:
interface DailyLog {
id: string;
date: string; // ISO date
completed: boolean;
markedAt?: string; // ISO datetime
problems: ProblemLog[];
}ProblemLog:
interface ProblemLog {
id: string;
topic: Topic;
name: string;
difficulty: Difficulty;
externalUrl?: string;
createdAt: string; // ISO datetime
}| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-01 | — | Initial API specification |
End of Document