API Reference — Every Endpoint Explained
Authentication Mechanism
Type: Stateless JWT (JSON Web Tokens)
Flow: Signup/Signin → server returns JWT → client stores in localStorage → sends as Authorization: Bearer <token> on every request.
Token generation: jwt.sign(payload, JWT_SECRET, { expiresIn: '24h' }) via auth/jwtToken.js
Token verification: jwt.verify(token, JWT_SECRET) via authMiddleware.js → decoded payload attached to req.user
No refresh tokens. When the 24h token expires, the user must re-login.
Middleware Chain (every protected request)
1. express.json() → Parse JSON body
2. cors() → Allow FRONTEND_URL origin, all methods
3. triggerMedicationReminder() → Async, non-blocking reminder check
4. checkForMissedMedications() → Decode JWT, mark overdue meds as Missed
5. authenticate() → Verify JWT, attach req.user (per-route)
6. [route handler] → Business logicAuth Endpoints (Public)
POST /auth/signup
What it does: Creates a new doctor or patient account.
Request body:
// Patient
{ "role": "patient", "name": "John", "email": "john@example.com", "password": "pass123", "age": 30, "gender": "Male" }
// Doctor
{ "role": "doctor", "name": "Dr. Smith", "email": "smith@example.com", "password": "pass123", "specialization": "Cardiology" }Response (200):
{ "msg": "done signup", "token": "<jwt>", "role": "patient" }Errors:
| Status | Reason |
|---|---|
| 400 | Duplicate email (Prisma P2002) or missing fields |
Internally: bcrypt.hash(password, 10) → prisma.Patient.create() or prisma.Doctor.create() → tokenGenerate()
POST /auth/signin
What it does: Authenticates an existing user.
Request body:
{ "email": "john@example.com", "password": "pass123", "role": "patient" }Response (200):
{ "message": "Sign-in successful", "token": "<jwt>", "role": "patient" }Errors:
| Status | Reason |
|---|---|
| 401 | Wrong email or wrong password (same message for both) |
| 500 | JWT_SECRET missing |
Internally: prisma.Patient.findUnique({ email }) → bcrypt.compare() → tokenGenerate()
POST /auth/set-password
What it does: Patient sets initial password after being invited by a doctor.
Request body:
{ "token": "<invite-jwt>", "password": "newPassword123" }Response (200):
{ "message": "Password set successfully", "status": "success" }Errors:
| Status | Reason |
|---|---|
| 400 | Token invalid, expired, wrong purpose, or missing patientId |
| 404 | Patient not found |
Internally: tokenVerify() → check purpose === 'password-setup' → bcrypt.hash() → Patient.update({ password, status: 'active' })
POST /auth/forgot-password
What it does: Sends a password reset email.
Request body:
{ "email": "john@example.com", "role": "patient" }Response (200): Always returns same message (prevents email enumeration):
{ "message": "If an account with that email exists, a password reset link has been sent." }Internally: findUnique({ email }) → tokenGenerate({ purpose: 'password-reset' }, '1h') → sendPasswordResetEmail()
POST /auth/reset-password
What it does: Resets password using the emailed token.
Request body:
{ "token": "<reset-jwt>", "password": "newPassword456" }Response (200):
{ "message": "Password reset successfully", "status": "success" }Errors:
| Status | Reason |
|---|---|
| 400 | Token invalid, expired, wrong purpose |
Doctor Endpoints (Authenticated, role: doctor)
GET /doctor/retrievePatients
What it does: Lists all patients assigned to the authenticated doctor.
Auth: Bearer token, role must be doctor (inferred from token).
Response (200):
[
{ "id": 1, "name": "John", "email": "john@example.com", "age": 30, "gender": "Male", "status": "active" },
...
]Internally: Patient.findMany({ where: { doctors: { some: { id: doctorId } } } })
GET /doctor/doctors
What it does: Lists all patients with their assigned doctors (misleading endpoint name).
Response (200):
[
{
"id": 1, "name": "John", ...,
"doctors": [{ "id": "uuid", "name": "Dr. Smith", "specialization": "Cardiology", "email": "..." }]
}
]Internally: Patient.findMany({ include: { doctors: { select: { id, name, specialization, email } } } })
POST /doctor/add-patient
What it does: Creates a new patient (pending status), links to doctor, sends invite email.
Request body:
{ "name": "Jane Doe", "email": "jane@example.com", "age": 25, "gender": "Female" }Response (201):
{ "message": "Patient added successfully", "patient": {...}, "emailSent": true }Errors:
| Status | Reason |
|---|---|
| 403 | Caller is not a doctor |
| 400 | Email already exists |
POST /doctor/assign-patient
What it does: Links an existing patient to the authenticated doctor.
Request body:
{ "patientId": 5 } // integer ID
// OR
{ "patientId": "jane@example.com" } // email lookup
// OR
{ "patientId": "Jane Doe (jane@example.com)" } // "name (email)" formatResponse (200):
{ "message": "Patient assigned successfully", "patient": {...} }Internally: Parses identifier (int vs email vs "name (email)" regex), then Patient.update({ doctors: { connect: { id: doctorId } } })
POST /doctor/remove-patient
What it does: Unlinks a patient from the doctor (doesn't delete the patient).
Request body:
{ "patientId": 5 }Response (200):
{ "message": "Patient removed successfully", "patientsCount": 4 }Errors:
| Status | Reason |
|---|---|
| 403 | Not a doctor |
| 404 | Patient not assigned to this doctor |
Internally: Verifies doctor-patient relationship → Doctor.update({ patients: { disconnect: { id } } })
POST /doctor/prescription
What it does: Creates a prescription with multiple medicines.
Request body:
{
"patientId": 5,
"condition": "Hypertension",
"medicines": [
{
"id": "med-123",
"name": "Amlodipine",
"dosage": "5mg",
"timing": { "morning": true, "afternoon": false, "evening": true },
"instructions": "After food",
"duration": "30 days"
}
]
}Response (201):
{
"message": "Prescription created successfully",
"prescription": {
"id": "uuid", "date": "...", "condition": "Hypertension",
"medicines": [...],
"patient": { "name": "John", "email": "..." },
"doctor": { "name": "Dr. Smith" }
}
}Internally: Validates all medicine fields → Prescription.create({ data: { ..., medicines: { create: [...] } }, include: { medicines, patient, doctor } })
GET /doctor/prescriptions/:patientId
What it does: Lists prescriptions for a patient (doctor must be assigned to that patient).
Response (200): Array of prescriptions with medicines and doctor info.
Errors:
| Status | Reason |
|---|---|
| 403 | Not a doctor, or patient not assigned to this doctor |
| 404 | Patient not found |
DELETE /doctor/prescription/:prescriptionId
What it does: Deletes a prescription and all associated records (medicines + adherence).
Response (200):
{ "message": "Prescription and associated records deleted successfully" }Internally: $transaction → delete MedicineAdherence → delete PrescribedMedicine → delete Prescription
GET /doctor/patient-medications/today/:patientId
What it does: Doctor views a specific patient's today medication schedule with adherence status.
Response (200): Array of medication entries with adherenceStatus, scheduledTime, etc.
Patient Endpoints (Authenticated, role: patient)
GET /patient/prescriptions/:patientId
What it does: Patient views their own prescriptions.
Response (200): Array of prescriptions with medicines and doctor name.
GET /patient/medications/today/:patientId
What it does: Gets today's medication schedule with status for each time period.
Response (200):
[
{
"medicineId": "uuid",
"medicineName": "Amlodipine",
"dosage": "5mg",
"instructions": "After food",
"scheduledTime": "morning",
"adherenceStatus": "Pending",
"prescriptionId": "uuid"
}
]POST /patient/medications/update-status
What it does: Patient marks a medication as Taken or Missed.
Request body:
{
"id": "existing-record-id-or-null",
"status": "Taken",
"patientId": 5,
"medication": "Amlodipine",
"prescriptionId": "uuid",
"medicineId": "uuid",
"scheduledTime": "morning",
"isNewMedication": false
}Response (200): The upserted MedicineAdherence record.
Internally: Checks for existing record (patientId + scheduledDate + scheduledTime + medicineId) → UPDATE if exists, CREATE if new.
GET /patient/medications/history/:patientId?days=7
What it does: Returns medication adherence history for the past N days.
Query params: days (default: 7)
Response (200): Array of MedicineAdherence records ordered by createdAt desc.
GET /patient/medications/adherence-stats/:patientId?days=30
What it does: Returns medication adherence statistics.
Response (200):
{
"summary": {
"totalMedications": 5,
"takenCount": 120,
"missedCount": 15,
"pendingCount": 3,
"adherenceRate": 88.89
},
"dailyStats": [
{ "date": "2025-08-12", "total": 6, "taken": 5, "missed": 1, "pending": 0, "adherenceRate": 83.33 }
]
}Appointment Endpoints (Authenticated)
POST /appointments
What it does: Patient books an appointment slot.
Auth: Patient only.
Request body:
{ "doctorId": "uuid", "appointmentDate": "2025-08-15T10:00:00Z", "purpose": "Follow-up checkup" }Response (201): The created appointment object.
Errors:
| Status | Reason |
|---|---|
| 403 | Not a patient, or not assigned to this doctor |
| 404 | Doctor not found |
| 409 | Slot already booked (fast-path check OR P2002 unique violation) |
GET /appointments/available-slots?doctorId=uuid&date=2025-08-15
What it does: Returns available 1-hour slots for a doctor on a given date.
Response (200):
{
"doctorId": "uuid",
"date": "2025-08-15",
"availableSlots": [
{ "time": "2025-08-15T09:00:00.000Z", "formattedTime": "9:00" },
{ "time": "2025-08-15T11:00:00.000Z", "formattedTime": "11:00" }
]
}GET /doctor/appointments?status=scheduled&date=2025-08-15
What it does: Doctor's appointment list with optional filters.
Response (200): Array of appointments with patient details (name, email, age, gender).
GET /patient/appointments?status=scheduled
What it does: Patient's appointment list with optional status filter.
Response (200): Array of appointments with doctor details (name, email, specialization).
POST /appointments/update-status
What it does: Updates an appointment's status.
Request body:
{ "appointmentId": "uuid", "status": "completed" }Valid statuses: scheduled, completed, cancelled, missed
Permission rules:
- Doctors: can set any valid status (for their own appointments)
- Patients: can only set
cancelled
Chat Endpoints (Authenticated)
POST /chats
What it does: Creates or returns existing chat between doctor and patient.
Request body: { "patientId": 5, "doctorId": "uuid" }
Response (200/201): Chat object with isExisting: true or isNew: true.
GET /doctor/chats / GET /patient/chats
What it does: Lists all chats for the authenticated user, with last message preview.
Response (200): Array of chats with counterparty details and most recent message.
GET /chats/:chatId
What it does: Gets a specific chat (verifies caller is a participant).
POST /chats/:chatId/messages
What it does: Sends a message in a chat.
Request body: { "content": "Hello, how are you feeling today?" }
Response (201): The created message object.
GET /chats/:chatId/messages
What it does: Gets all messages in a chat + marks opponent's messages as read.
Response (200): Array of messages ordered by createdAt asc.
Utility Endpoints
GET /api/medicines?search=amlo
Auth: None (public endpoint).
What it does: Searches the 32MB medicine CSV dataset.
Response (200): Array of medicine objects:
[
{
"id": "12345",
"name": "Amlodipine 5mg Tablet",
"price": "25",
"manufacturer": "Cipla Ltd",
"type": "allopathy",
"packSize": "strip of 10 tablets",
"composition1": "Amlodipine (5mg)",
"composition2": null
}
]POST /admin/send-medication-reminders (No auth!)
What it does: Manually triggers medication reminders.
Request body: { "timeOfDay": "morning" } (defaults to "morning")
Response (200): { "success": true, "message": "morning medication reminders sent successfully" }
⚠ Security issue: This endpoint has no authentication. Anyone can trigger mass email sends.