04-api-reference.md

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 logic

Auth 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:

StatusReason
400Duplicate 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:

StatusReason
401Wrong email or wrong password (same message for both)
500JWT_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:

StatusReason
400Token invalid, expired, wrong purpose, or missing patientId
404Patient 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:

StatusReason
400Token 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:

StatusReason
403Caller is not a doctor
400Email 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)" format

Response (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:

StatusReason
403Not a doctor
404Patient 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:

StatusReason
403Not a doctor, or patient not assigned to this doctor
404Patient 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:

StatusReason
403Not a patient, or not assigned to this doctor
404Doctor not found
409Slot 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.