-
-
Notifications
You must be signed in to change notification settings - Fork 23
API Reference
Last Updated: February 7, 2026
API Version: 1.0
Base URL: http://localhost:8000/api/v1
- API Overview & Standards
- Authentication
- SSO Authentication
- User Management
- Patient Management
- Medical Records
- Related Information
- Notifications
- Files & Attachments
- Sharing & Collaboration
- Search & Tags
- Reports & Export
- Integrations
- System & Utilities
- Admin Dashboard
- Protocol: HTTPS (recommended), HTTP (development only)
- Authentication: JWT Bearer Token
-
Content-Type:
application/json - Character Encoding: UTF-8
- Default: 20 items per page
- Maximum: 100 items per page
-
Query Parameters:
-
skip: Number of items to skip (default: 0) -
limit: Maximum items to return (default: 20, max: 100)
-
{
"status": "success",
"data": {},
"message": "Optional success message"
}{
"status": "error",
"error": "User-friendly error message",
"detail": "Technical details (development only)"
}-
200: Successful GET/PUT/PATCH request -
201: Resource created successfully -
204: Successful DELETE (no content) -
400: Bad request (validation error) -
401: Unauthorized (invalid/missing token) -
403: Forbidden (insufficient permissions) -
404: Resource not found -
409: Conflict (duplicate resource) -
422: Unprocessable entity -
429: Too many requests (rate limit) -
500: Internal server error
- Standard endpoints: 100 requests/minute
- Authentication endpoints: 10 requests/minute
- File upload endpoints: 50 requests/hour
- Search endpoints: 30 requests/minute
- System monitoring endpoints: 60 requests/minute per IP (
/system/log-level,/system/log-rotation-config)
Base path: /api/v1/auth
GET /auth/registration-status
- Purpose: Check if new user registration is enabled
- Authentication: No
- Success Response (200):
{
"registration_enabled": true,
"message": null
}POST /auth/register
- Purpose: Create a new user account
- Authentication: No
- Request Body:
{
"username": "johndoe",
"email": "john@example.com",
"password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe"
}- Success Response (201):
{
"id": 1,
"username": "johndoe",
"email": "john@example.com",
"full_name": "John Doe",
"role": "user",
"created_at": "2025-10-04T10:30:00Z"
}-
Error Responses:
-
400: Validation failed (weak password, invalid email) -
403: Registration disabled -
409: Username or email already exists
-
POST /auth/login
- Purpose: Authenticate and receive JWT token
- Authentication: No
-
Content-Type:
application/x-www-form-urlencoded - Request Body (form data):
username=johndoe&password=SecurePass123!
- Success Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"session_timeout_minutes": 60
}-
Note: The response does NOT include a user object. Use
GET /users/meto retrieve user details after login. Thesession_timeout_minutesreflects the user's session timeout preference (or system default if not set). -
Error Responses:
-
401: Invalid credentials -
429: Too many login attempts (rate limited)
-
POST /auth/change-password
- Purpose: Update current user's password
- Authentication: Yes
- Request Body:
{
"current_password": "OldPass123!",
"new_password": "NewPass456!"
}- Success Response (200):
{
"message": "Password changed successfully"
}-
Error Responses:
-
400: Password validation failed (too weak, same as old) -
401: Current password incorrect
-
Base path: /api/v1/auth/sso
Supported Providers: Google, GitHub, OIDC, Authentik, Authelia, Keycloak
GET /auth/sso/config
- Purpose: Check SSO availability and configuration
- Authentication: No
- Success Response (200):
{
"enabled": true,
"provider_type": "google",
"registration_enabled": true
}POST /auth/sso/initiate
- Purpose: Start SSO authentication flow
- Authentication: No
-
Query Parameters:
-
return_url(string, optional): URL to redirect after authentication
-
- Success Response (200):
{
"authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?...",
"state": "random_state_token"
}POST /auth/sso/callback
- Purpose: Complete SSO authentication
- Authentication: No
- Request Body:
{
"code": "authorization_code_from_provider",
"state": "state_token_from_initiate"
}- Success Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"session_timeout_minutes": 60,
"user": {
"id": 1,
"username": "john.doe",
"email": "john@example.com",
"full_name": "John Doe",
"role": "user",
"auth_method": "google_sso"
},
"is_new_user": false
}- Conflict Response (200):
{
"conflict": true,
"temp_token": "temporary_token_for_resolution",
"existing_email": "john@example.com",
"sso_provider": "google"
}POST /auth/sso/resolve-conflict
- Purpose: Handle SSO email conflicts with existing accounts
- Authentication: No
- Request Body:
{
"temp_token": "temporary_token",
"action": "link",
"preference": "auto_link"
}-
Actions:
-
link: Link SSO to existing account -
create_separate: Create new account with different email
-
-
Preferences:
-
auto_link: Automatically link in future -
create_separate: Always create separate accounts -
always_ask: Prompt user each time
-
POST /auth/sso/resolve-github-link
- Purpose: Link GitHub account by verifying password
- Authentication: No
- Request Body:
{
"temp_token": "temporary_token",
"username": "johndoe",
"password": "password"
}POST /auth/sso/test-connection
- Purpose: Test SSO provider connectivity (admin)
- Authentication: No (but intended for admin use)
- Success Response (200):
{
"success": true,
"message": "Successfully connected to provider"
}Base path: /api/v1/users
GET /users/me
- Purpose: Retrieve authenticated user's profile
- Authentication: Yes
- Success Response (200):
{
"id": 1,
"username": "johndoe",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"role": "user",
"auth_method": "local",
"created_at": "2025-01-01T00:00:00Z"
}PUT /users/me
- Purpose: Update user profile information
- Authentication: Yes
- Request Body:
{
"first_name": "John",
"last_name": "Doe",
"email": "newemail@example.com"
}- Success Response (200): Updated user object
DELETE /users/me
- Purpose: Permanently delete user account and ALL associated data
- Authentication: Yes
- Warning: Deletes user, patient record, and all medical data (medications, lab results, etc.)
- Success Response (200):
{
"message": "Account and all associated data deleted successfully",
"deleted_user_id": 1,
"deleted_patient_id": 1,
"deletion_summary": {
"medications": 10,
"lab_results": 5,
"allergies": 2,
"conditions": 3
}
}-
Error Responses:
-
400: Cannot delete last admin user -
404: User not found
-
GET /users/me/preferences
- Purpose: Retrieve user preferences/settings
- Authentication: Yes
- Success Response (200):
{
"user_id": 1,
"theme": "light",
"notifications_enabled": true,
"language": "en"
}PUT /users/me/preferences
- Purpose: Update user preferences
- Authentication: Yes
- Request Body:
{
"theme": "dark",
"notifications_enabled": false
}Base path: /api/v1/patients
GET /patients/me
- Purpose: Retrieve current user's patient record
- Authentication: Yes
- Success Response (200):
{
"id": 1,
"owner_user_id": 1,
"first_name": "John",
"last_name": "Doe",
"birth_date": "1990-01-15",
"gender": "male",
"address": "123 Main St, City, State 12345",
"phone_number": "+1234567890",
"created_at": "2025-01-01T00:00:00Z"
}-
Error Responses:
-
404: Patient record not found
-
POST /patients/me
- Purpose: Create patient record for current user
- Authentication: Yes
- Request Body:
{
"first_name": "John",
"last_name": "Doe",
"birth_date": "1990-01-15",
"gender": "male",
"address": "123 Main St, City, State 12345",
"blood_type": "O+",
"height": 70,
"weight": 165,
"physician_id": 5
}- Note: Height in inches, weight in pounds
- Success Response (201): Created patient object
-
Error Responses:
-
400: Patient record already exists -
400: Validation failed (invalid date, missing required fields)
-
PUT /patients/me
- Purpose: Update current user's patient record
- Authentication: Yes
- Request Body: All fields optional
{
"first_name": "Jonathan",
"address": "456 New Address"
}- Success Response (200): Updated patient object
DELETE /patients/me
- Purpose: Delete patient record and ALL medical data
- Authentication: Yes
- Warning: Cascades to medications, lab results, allergies, conditions, procedures, treatments, encounters, vitals, immunizations
- Success Response (200):
{
"message": "Patient record and all associated medical records deleted successfully"
}GET /patients/{patient_id}/medications
- Purpose: Get all medications for a specific patient
- Authentication: Yes (must have access to patient)
-
Path Parameters:
-
patient_id(integer): Patient ID
-
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 100, max: 100): Items per page -
active_only(boolean, default: false): Only active medications
-
- Success Response (200): Array of medication objects with practitioner and pharmacy details
POST /patients/{patient_id}/medications
- Purpose: Create new medication for a patient
- Authentication: Yes (must have write access to patient)
- Request Body: See Medications section below
GET /patients/{patient_id}/conditions
- Purpose: Get all medical conditions for a patient
- Authentication: Yes
GET /patients/{patient_id}/allergies
- Purpose: Get all allergies for a patient
- Authentication: Yes
GET /patients/{patient_id}/immunizations
- Purpose: Get all immunizations for a patient
- Authentication: Yes
GET /patients/{patient_id}/procedures
- Purpose: Get all procedures for a patient
- Authentication: Yes
GET /patients/{patient_id}/treatments
- Purpose: Get all treatments for a patient
- Authentication: Yes
GET /patients/{patient_id}/lab-results
- Purpose: Get all lab results for a patient
- Authentication: Yes
GET /patients/{patient_id}/encounters
- Purpose: Get all encounters (visits) for a patient
- Authentication: Yes
GET /patients/me/recent-activity
- Purpose: Get recent medical-related activities
- Authentication: Yes
-
Query Parameters:
-
limit(integer, default: 10, max: 100): Number of activities
-
- Success Response (200):
[
{
"id": 1,
"model_name": "Medication",
"action": "created",
"description": "Created Medication: Aspirin 100mg",
"timestamp": "2025-10-04T10:30:00Z"
}
]GET /patients/me/dashboard-stats
- Purpose: Get comprehensive medical record statistics
- Authentication: Yes
-
Query Parameters:
-
patient_id(integer, optional): Specific patient for Phase 1 patient switching
-
- Success Response (200):
{
"patient_id": 1,
"total_records": 45,
"active_medications": 3,
"total_lab_results": 10,
"total_procedures": 5,
"total_treatments": 2,
"total_conditions": 4,
"total_allergies": 3,
"total_immunizations": 8,
"total_encounters": 12,
"total_vitals": 20
}POST /patients/{patient_id}/photo
- Purpose: Upload patient profile photo
- Authentication: Yes (must have write access)
-
Content-Type:
multipart/form-data -
Request Body:
-
file: Image file (JPEG, PNG, GIF, BMP)
-
-
File Restrictions:
- Max size: 15MB
- Accepted types: image/jpeg, image/png, image/gif, image/bmp
- Success Response (201):
{
"id": 1,
"patient_id": 1,
"filename": "patient_1_photo.jpg",
"file_size": 245678,
"uploaded_at": "2025-10-04T10:30:00Z"
}GET /patients/{patient_id}/photo
- Purpose: Get patient photo file
- Authentication: Yes
- Success Response (200): Image file (image/jpeg)
-
Headers:
Cache-Control: max-age=3600
GET /patients/{patient_id}/photo/info
- Purpose: Get photo metadata without downloading file
- Authentication: Yes
- Success Response (200): Photo metadata object
DELETE /patients/{patient_id}/photo
- Purpose: Delete patient photo
- Authentication: Yes (must be owner)
- Success Response (204): No content
Base path: /api/v1/patient-management
Purpose: Netflix-style patient switching for managing multiple patient records
POST /patient-management/
- Purpose: Create a new patient record (for managing family members, dependents, etc.)
- Request Body:
{
"first_name": "Jane",
"last_name": "Doe",
"birth_date": "2010-05-20",
"gender": "female",
"blood_type": "A+",
"height": 60,
"weight": 85,
"address": "123 Main St",
"physician_id": 3,
"is_self_record": false
}- Success Response (201): Created patient object
GET /patient-management/
- Purpose: Get all accessible patients (owned + shared with user)
-
Query Parameters:
-
skip: Pagination offset -
limit: Max items -
include_shared: Include patients shared with user (default: true)
-
- Success Response (200):
{
"patients": [...],
"total": 5,
"owned_count": 3,
"shared_count": 2
}GET /patient-management/{patient_id}
- Purpose: Get specific patient details
- Success Response (200): Patient object
PUT /patient-management/{patient_id}
- Purpose: Update patient information
- Request Body: Same as create (all fields optional)
- Success Response (200): Updated patient object
DELETE /patient-management/{patient_id}
- Purpose: Delete a patient record
- Note: Cannot delete if it's the last remaining patient
-
Success Response (200):
{"message": "Patient deleted successfully"}
GET /patient-management/owned/list
- Purpose: Get only patients owned by current user
- Success Response (200): Array of owned patient objects
GET /patient-management/self-record
- Purpose: Get the user's own patient record (is_self_record=true)
- Success Response (200): Patient object or null
POST /patient-management/switch
- Purpose: Switch the currently active patient context
- Request Body:
{
"patient_id": 3
}- Success Response (200): New active patient object
- Use Case: Switch between managing different family members
GET /patient-management/active/current
- Purpose: Get the currently active patient
- Success Response (200): Active patient object or null
GET /patient-management/stats
- Purpose: Get statistics about accessible patients
- Success Response (200):
{
"total_patients": 5,
"owned_patients": 3,
"shared_patients": 2,
"self_record_exists": true
}Base path: /api/v1/medications
POST /medications/
- Authentication: Yes
- Request Body:
{
"patient_id": 1,
"medication_name": "Aspirin",
"dosage": "100mg",
"frequency": "Once daily",
"route": "oral",
"indication": "Pain relief",
"effective_period_start": "2025-01-01",
"effective_period_end": "2025-12-31",
"status": "active",
"practitioner_id": 5,
"pharmacy_id": 3
}-
Route values:
oral,injection,topical,intravenous,intramuscular,subcutaneous,inhalation,nasal,rectal,sublingual -
Status values:
active,stopped,on-hold,completed,cancelled - Success Response (201): Medication object with relations
GET /medications/
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0) -
limit(integer, default: 100, max: 100) -
name(string, optional): Filter by medication name -
tags(array, optional): Filter by tags -
tag_match_all(boolean, default: false): Match ALL tags vs ANY tag
-
- Success Response (200): Array of medications with practitioner, pharmacy, condition details
GET /medications/{medication_id}
- Authentication: Yes
- Success Response (200): Single medication with relations
PUT /medications/{medication_id}
- Authentication: Yes
- Request Body: Same as create, all fields optional
DELETE /medications/{medication_id}
- Authentication: Yes
- Success Response (204): No content
GET /medications/patient/{patient_id}?active_only=true
-
Query Parameters:
-
active_only(boolean): Filter only active medications
-
GET /medications/{medication_id}/treatments
- Purpose: Get all treatments that use a specific medication (medication profile / treatment history)
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"treatment_id": 5,
"medication_id": 12,
"specific_dosage": "20mg",
"specific_frequency": null,
"specific_duration": null,
"timing_instructions": null,
"relevance_note": "Primary medication",
"specific_prescriber_id": null,
"specific_pharmacy_id": null,
"specific_start_date": "2026-01-09",
"specific_end_date": null,
"treatment": {
"id": 5,
"treatment_name": "Cardiac Rehabilitation",
"treatment_type": "Rehabilitation",
"status": "active",
"mode": "advanced",
"start_date": "2026-01-01",
"end_date": null,
"condition": { "id": 2, "condition_name": "Hypertension" }
}
}
]- Includes treatment details with condition, plus any treatment-specific overrides for this medication
Base path: /api/v1/allergies
POST /allergies/
- Request Body:
{
"patient_id": 1,
"allergen": "Penicillin",
"severity": "severe",
"reaction": "Anaphylaxis",
"diagnosed_date": "2020-05-15",
"notes": "Confirmed by allergist"
}-
Severity values:
mild,moderate,severe,life-threatening
GET /allergies/
-
Query Parameters:
-
severity(string, optional): Filter by severity -
allergen(string, optional): Search allergen name -
tags(array, optional): Filter by tags
-
GET /allergies/patient/{patient_id}/active
- Purpose: Get only active allergies for a patient
GET /allergies/patient/{patient_id}/critical
- Purpose: Get severe and life-threatening allergies
GET /allergies/patient/{patient_id}/check/{allergen}
- Purpose: Check if patient has allergy to specific allergen
- Success Response (200):
{
"patient_id": 1,
"allergen": "Penicillin",
"has_allergy": true
}Base path: /api/v1/conditions
POST /conditions/
- Request Body:
{
"patient_id": 1,
"condition_name": "Hypertension",
"status": "active",
"diagnosed_date": "2023-03-10",
"practitioner_id": 5,
"severity": "moderate",
"notes": "Controlled with medication"
}-
Status values:
active,resolved,chronic -
Severity values:
mild,moderate,severe
GET /conditions/
-
Query Parameters:
-
status(string, optional): Filter by status -
tags(array, optional): Filter by tags -
tag_match_all(boolean, default: false): Match all tags (AND) vs any tag (OR)
-
GET /conditions/dropdown
- Purpose: Get conditions formatted for dropdown selection in forms
- Authentication: Yes
-
Query Parameters:
-
active_only(boolean, default: false): Only return active conditions
-
- Success Response (200):
[
{
"id": 1,
"condition_name": "Hypertension",
"status": "active"
}
]GET /conditions/{condition_id}
- Purpose: Get condition with related information (patient, practitioner, treatments)
- Authentication: Yes
- Success Response (200): Condition object with relations
PUT /conditions/{condition_id}
- Request Body: Same as create (all fields optional)
- Success Response (200): Updated condition object
DELETE /conditions/{condition_id}
-
Success Response (200):
{"message": "Condition deleted successfully"}
GET /conditions/patient/{patient_id}/active
- Purpose: Get all active conditions for a patient
- Authentication: Yes
- Success Response (200): Array of active condition objects
GET /conditions/patients/{patient_id}/conditions/
- Purpose: Get all conditions for a specific patient
- Authentication: Yes
-
Query Parameters:
skip,limit - Success Response (200): Array of condition objects
GET /conditions/condition-medications/{condition_id}
- Purpose: Get all medications linked to a condition
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"medication_id": 5,
"condition_id": 1,
"relevance_note": "Primary treatment",
"created_at": "2025-10-01T00:00:00Z",
"updated_at": "2025-10-01T00:00:00Z"
}
]POST /conditions/{condition_id}/medications
- Purpose: Create a relationship between a condition and medication
- Authentication: Yes
- Request Body:
{
"medication_id": 5,
"relevance_note": "Primary treatment for this condition"
}- Success Response (201): Created relationship object
-
Error Responses:
-
400: Relationship already exists or medication belongs to different patient -
404: Condition or medication not found
-
POST /conditions/{condition_id}/medications/bulk
- Purpose: Create multiple condition-medication relationships at once
- Authentication: Yes
- Request Body:
{
"medication_ids": [5, 8, 12],
"relevance_note": "Treatments for managing this condition"
}- Success Response (200): Array of created relationship objects
[
{
"id": 1,
"condition_id": 1,
"medication_id": 5,
"relevance_note": "Treatments for managing this condition",
"created_at": "2026-02-03T00:00:00Z",
"updated_at": "2026-02-03T00:00:00Z"
},
{
"id": 2,
"condition_id": 1,
"medication_id": 8,
"relevance_note": "Treatments for managing this condition",
"created_at": "2026-02-03T00:00:00Z",
"updated_at": "2026-02-03T00:00:00Z"
}
// Note: medication_id 12 was already linked, so it was silently skipped
]-
Notes:
- Medications that are already linked to the condition are silently skipped
- All medications must belong to the same patient as the condition
-
relevance_noteis optional and applies to all created relationships
-
Error Responses:
-
400: Medication belongs to different patient -
404: Condition or medication not found -
422: Validation error (empty array, duplicate IDs, invalid IDs)
-
PUT /conditions/{condition_id}/medications/{relationship_id}
- Purpose: Update the relevance note for a condition-medication relationship
- Authentication: Yes
- Request Body:
{
"relevance_note": "Updated note about the relationship"
}- Success Response (200): Updated relationship object
DELETE /conditions/{condition_id}/medications/{relationship_id}
- Purpose: Remove the link between a condition and medication
- Authentication: Yes
-
Success Response (200):
{"message": "Condition medication relationship deleted successfully"}
GET /conditions/medication/{medication_id}/conditions
- Purpose: Get all conditions linked to a specific medication (reverse lookup)
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"condition_id": 3,
"medication_id": 5,
"relevance_note": "Treatment for this condition",
"created_at": "2025-10-01T00:00:00Z",
"updated_at": "2025-10-01T00:00:00Z",
"condition": {
"id": 3,
"diagnosis": "Hypertension",
"status": "active",
"severity": "moderate"
}
}
]Base path: /api/v1/immunizations
POST /immunizations/
- Request Body:
{
"patient_id": 1,
"vaccine_name": "COVID-19 Vaccine",
"vaccine_type": "mRNA",
"dose_number": 1,
"administration_date": "2025-01-15",
"practitioner_id": 5,
"lot_number": "LOT12345",
"expiration_date": "2026-01-15",
"site": "Left arm",
"route": "Intramuscular",
"notes": "No adverse reactions"
}GET /immunizations/
GET /immunizations/patient/{patient_id}/upcoming
- Purpose: Get immunizations scheduled for the future
Base path: /api/v1/vitals
POST /vitals/
- Authentication: Yes
- Request Body:
{
"patient_id": 1,
"recorded_date": "2025-10-04T10:30:00Z",
"systolic_bp": 120,
"diastolic_bp": 80,
"heart_rate": 72,
"temperature": 98.6,
"weight": 165,
"height": 70,
"oxygen_saturation": 98,
"respiratory_rate": 16,
"blood_glucose": 95,
"glucose_context": "fasting",
"bmi": 24.5,
"pain_scale": 0,
"notes": "Routine checkup",
"location": "Doctor's office",
"device_used": "Digital thermometer",
"practitioner_id": 5
}- Note: Temperature stored in Fahrenheit, weight in pounds, height in inches
-
Validation:
- Systolic BP: 60-250 mmHg
- Diastolic BP: 30-150 mmHg
- Heart Rate: 30-250 bpm
- Temperature: 80-115°F
- Pain Scale: 0-10
GET /vitals/
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 10000, max: 10000): Items per page -
vital_type(string, optional): Filter by vital type (blood_pressure, heart_rate, temperature, weight, oxygen_saturation, blood_glucose) -
glucose_context(string, optional): Filter by glucose context (fasting, before_meal, after_meal, random) -
start_date(string, optional): Start date for date range filter (ISO format) -
end_date(string, optional): End date for date range filter (ISO format) -
days(integer, optional): Get readings from last N days
-
- Success Response (200): Array of vitals records
GET /vitals/{vitals_id}
- Purpose: Get vitals reading with related information
- Authentication: Yes
- Success Response (200): Vitals object with patient and practitioner details
PUT /vitals/{vitals_id}
- Authentication: Yes
- Request Body: Same as create (all fields optional)
- Success Response (200): Updated vitals object
DELETE /vitals/{vitals_id}
- Authentication: Yes
-
Success Response (200):
{"message": "Vitals deleted successfully"}
GET /vitals/stats
- Purpose: Get vitals statistics for the current user
- Authentication: Yes
-
Query Parameters:
-
patient_id(integer, optional): Patient ID for patient switching
-
- Success Response (200):
{
"total_readings": 50,
"latest_reading_date": "2025-10-04T10:30:00Z",
"blood_pressure_avg": {
"systolic": 120,
"diastolic": 80
},
"heart_rate_avg": 72,
"weight_trend": "stable"
}GET /vitals/patient/{patient_id}/paginated
- Purpose: Get paginated vitals readings with total count
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 10, max: 100): Items per page -
vital_type(string, optional): Filter by vital type -
glucose_context(string, optional): Filter by glucose context (fasting, before_meal, after_meal, random)
-
- Success Response (200):
{
"items": [...],
"total": 100,
"skip": 0,
"limit": 10
}GET /vitals/patient/{patient_id}
- Purpose: Get all vitals for a specific patient
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 10000, max: 10000): Items per page -
vital_type(string, optional): Filter by vital type -
glucose_context(string, optional): Filter by glucose context (fasting, before_meal, after_meal, random) -
days(integer, optional): Get readings from last N days
-
- Success Response (200): Array of vitals records
GET /vitals/patient/{patient_id}/latest
- Purpose: Get most recent vital signs for a patient
- Authentication: Yes
- Success Response (200): Single vitals object
- Error Response (404): No vitals readings found
GET /vitals/patient/{patient_id}/stats
- Purpose: Get vitals statistics for a specific patient
- Authentication: Yes
- Success Response (200): Statistics object with averages and trends
GET /vitals/patient/{patient_id}/date-range
- Purpose: Get vitals readings within a specific date range
- Authentication: Yes
-
Query Parameters:
-
start_date(datetime, required): Start date for the range -
end_date(datetime, required): End date for the range -
skip(integer, default: 0): Pagination offset -
limit(integer, default: 10000, max: 10000): Items per page -
vital_type(string, optional): Filter by vital type -
glucose_context(string, optional): Filter by glucose context (fasting, before_meal, after_meal, random)
-
- Success Response (200): Array of vitals records
POST /vitals/patient/{patient_id}/vitals/
- Purpose: Create a new vitals reading for a specific patient
- Authentication: Yes
- Request Body: Same as main create endpoint
- Note: Patient ID in URL must match patient_id in request body
- Success Response (201): Created vitals object
Base path: /api/v1/lab-results
POST /lab-results/
- Authentication: Yes
- Request Body:
{
"patient_id": 1,
"test_name": "Complete Blood Count",
"test_code": "CBC",
"test_category": "Hematology",
"test_type": "routine",
"facility": "LabCorp",
"ordered_date": "2025-10-01",
"completed_date": "2025-10-02",
"practitioner_id": 5,
"status": "final",
"labs_result": "All values within normal range",
"notes": "Routine checkup",
"tags": ["annual", "routine"]
}-
Status values:
pending,preliminary,final,corrected - Success Response (201): Lab result object
GET /lab-results/
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 100, max: 1000): Items per page -
tags(array, optional): Filter by tags -
tag_match_all(boolean, default: false): Match all tags (AND) vs any tag (OR)
-
- Success Response (200): Array of lab results with practitioner and patient details
GET /lab-results/{lab_result_id}
- Purpose: Get lab result with related data (patient, practitioner, files)
- Authentication: Yes
- Success Response (200): Lab result with relations
PUT /lab-results/{lab_result_id}
- Authentication: Yes
- Request Body: Same as create (all fields optional)
- Success Response (200): Updated lab result
DELETE /lab-results/{lab_result_id}
- Purpose: Delete lab result and associated files
- Authentication: Yes
- Success Response (200):
{
"message": "Lab result and associated files deleted successfully",
"files_deleted": 2,
"files_preserved": 1
}GET /lab-results/patient/{patient_id}
- Purpose: Get all lab results for a specific patient
- Authentication: Yes
-
Query Parameters:
skip,limit,tags,tag_match_all - Success Response (200): Array of lab results
GET /lab-results/patient/{patient_id}/code/{code}
- Purpose: Get lab results for a patient filtered by test code
- Authentication: Yes
- Success Response (200): Array of lab results
GET /lab-results/practitioner/{practitioner_id}
- Purpose: Get lab results ordered by a specific practitioner (filtered to accessible patients)
- Authentication: Yes
-
Query Parameters:
skip,limit - Success Response (200): Array of lab results
GET /lab-results/search/code/{code}
- Purpose: Search lab results by exact test code
- Authentication: Yes
-
Query Parameters:
skip,limit - Success Response (200): Array of matching lab results
GET /lab-results/search/code-pattern/{code_pattern}
- Purpose: Search lab results by partial test code match
- Authentication: Yes
-
Query Parameters:
skip,limit - Success Response (200): Array of matching lab results
GET /lab-results/{lab_result_id}/files
- Purpose: Get all files attached to a lab result
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"lab_result_id": 1,
"file_name": "cbc_results.pdf",
"file_path": "/uploads/lab_result_files/abc123.pdf",
"file_type": "application/pdf",
"file_size": 245678,
"description": "Original lab report",
"uploaded_at": "2025-10-04T10:30:00Z"
}
]POST /lab-results/{lab_result_id}/files
- Purpose: Upload a file to a lab result
- Authentication: Yes
-
Content-Type:
multipart/form-data -
Request Body:
-
file: File to upload -
description(optional): File description
-
- Max Size: 1GB
- Allowed Extensions: PDF, images, documents, medical imaging (DICOM, NIfTI), video, audio, archives
- Success Response (201): Created file object
DELETE /lab-results/{lab_result_id}/files/{file_id}
- Purpose: Delete a specific file from a lab result
- Authentication: Yes
-
Success Response (200):
{"message": "File deleted successfully"}
GET /lab-results/stats/patient/{patient_id}/count
- Purpose: Get count of lab results for a patient
- Authentication: Yes
- Success Response (200):
{
"patient_id": 1,
"lab_result_count": 25
}GET /lab-results/stats/practitioner/{practitioner_id}/count
- Purpose: Get count of lab results ordered by a practitioner
- Authentication: Yes
- Success Response (200):
{
"practitioner_id": 5,
"lab_result_count": 150
}GET /lab-results/stats/code/{code}/count
- Purpose: Get count of how many times a specific test code has been used
- Authentication: Yes
- Success Response (200):
{
"code": "CBC",
"usage_count": 50
}GET /lab-results/{lab_result_id}/conditions
- Purpose: Get all conditions linked to a lab result
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"lab_result_id": 1,
"condition_id": 3,
"relevance_note": "Monitoring for diabetes",
"created_at": "2025-10-01T00:00:00Z",
"condition": {
"id": 3,
"diagnosis": "Type 2 Diabetes",
"status": "active",
"severity": "moderate"
}
}
]POST /lab-results/{lab_result_id}/conditions
- Purpose: Create a relationship between a lab result and condition
- Authentication: Yes
- Request Body:
{
"condition_id": 3,
"relevance_note": "Monitoring glucose levels for diabetes management"
}- Success Response (201): Created relationship object
PUT /lab-results/{lab_result_id}/conditions/{relationship_id}
- Request Body:
{
"relevance_note": "Updated relevance note"
}- Success Response (200): Updated relationship object
DELETE /lab-results/{lab_result_id}/conditions/{relationship_id}
-
Success Response (200):
{"message": "Lab result condition relationship deleted successfully"}
POST /lab-results/{lab_result_id}/ocr-parse
- Purpose: Extract text from lab PDF using hybrid OCR approach
- Authentication: Yes
-
Content-Type:
multipart/form-data - Request Body: PDF file
- Max Size: 15MB
- Success Response (200):
{
"status": "success",
"extracted_text": "Extracted text content...",
"metadata": {
"method": "hybrid",
"confidence": 0.95,
"page_count": 3,
"char_count": 5000,
"filename": "lab_report.pdf",
"lab_name": "LabCorp",
"test_count": 15,
"test_date": "2025-10-01"
},
"error": null
}- Note: Returns raw text for client-side parsing. Does NOT save to database.
Base path: /api/v1/lab-test-components
POST /lab-test-components/
- Request Body:
{
"lab_result_id": 1,
"component_name": "White Blood Cell Count",
"value": "7.5",
"unit": "K/uL",
"reference_range": "4.5-11.0",
"status": "normal"
}-
Status values:
normal,abnormal,critical
Base path: /api/v1/encounters
POST /encounters/
- Request Body:
{
"patient_id": 1,
"encounter_type": "Office Visit",
"encounter_date": "2025-10-04",
"practitioner_id": 5,
"chief_complaint": "Annual checkup",
"diagnosis": "Healthy",
"treatment_plan": "Continue current medications",
"notes": "Patient doing well"
}Bidirectional many-to-many linking between encounters and lab results. Accessible from both the encounter side and the lab result side.
Base path: /api/v1/encounters/{encounter_id}/lab-results
GET /encounters/{encounter_id}/lab-results
- Purpose: List all lab results linked to an encounter
- Authentication: Yes
- Success Response (200): Array of relationship objects with lab result details
POST /encounters/{encounter_id}/lab-results
- Purpose: Link a lab result to an encounter
- Authentication: Yes
- Request Body:
{
"lab_result_id": 5,
"purpose": "ordered_during",
"relevance_note": "CBC ordered during routine checkup"
}-
Purpose values:
ordered_during,results_reviewed,follow_up_for,reference,other - Success Response (201): Created relationship object
POST /encounters/{encounter_id}/lab-results/bulk
- Purpose: Bulk link multiple lab results to an encounter
- Authentication: Yes
- Request Body: Array of relationship objects
PUT /encounters/{encounter_id}/lab-results/{relationship_id}
- Purpose: Update a relationship (purpose, relevance_note)
- Request Body:
{
"purpose": "results_reviewed",
"relevance_note": "Updated note"
}- Success Response (200): Updated relationship object
DELETE /encounters/{encounter_id}/lab-results/{relationship_id}
- Purpose: Remove a lab result link from an encounter
-
Success Response (200):
{"message": "Encounter lab result relationship deleted successfully"}
Base path: /api/v1/lab-results/{lab_result_id}/encounters
GET /lab-results/{lab_result_id}/encounters
- Purpose: List all encounters linked to a lab result
- Authentication: Yes
- Success Response (200): Array of relationship objects with encounter details
POST /lab-results/{lab_result_id}/encounters
- Purpose: Link an encounter to a lab result
- Authentication: Yes
- Request Body:
{
"lab_result_id": 3,
"purpose": "results_reviewed",
"relevance_note": "Results discussed during follow-up"
}- Success Response (201): Created relationship object
POST /lab-results/{lab_result_id}/encounters/bulk
- Purpose: Bulk link multiple encounters to a lab result
- Request Body: Array of relationship objects
PUT /lab-results/{lab_result_id}/encounters/{relationship_id}
- Purpose: Update a relationship (purpose, relevance_note)
- Success Response (200): Updated relationship object
DELETE /lab-results/{lab_result_id}/encounters/{relationship_id}
- Purpose: Remove an encounter link from a lab result
-
Success Response (200):
{"message": "Lab result encounter relationship deleted successfully"}
Base path: /api/v1/procedures
POST /procedures/
- Request Body:
{
"patient_id": 1,
"procedure_name": "Blood Draw",
"procedure_date": "2025-10-04",
"practitioner_id": 5,
"status": "completed",
"outcome": "successful",
"notes": "Routine lab work"
}Base path: /api/v1/treatments
Treatments support two modes:
-
Simple (
"simple"): Basic treatment tracking with schedule and dosage fields on the treatment itself. -
Advanced (
"advanced"): Medication-centric treatment plans where dosage, schedule, prescriber, and pharmacy can be overridden per linked medication.
POST /treatments/
- Request Body:
{
"patient_id": 1,
"treatment_name": "Physical Therapy",
"condition_id": 2,
"start_date": "2025-10-01",
"end_date": "2025-12-31",
"practitioner_id": 5,
"frequency": "3 times per week",
"status": "active",
"mode": "simple",
"notes": "For lower back pain"
}-
Mode values:
simple(default),advanced
GET /treatments/
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 100, max: 100): Items per page -
condition_id(integer, optional): Filter by condition -
status(string, optional): Filter by status -
tags(array, optional): Filter by tags -
tag_match_all(boolean, default: false): Match ALL tags vs ANY tag
-
- Success Response (200): Array of treatment objects
GET /treatments/{treatment_id}
- Authentication: Yes
- Success Response (200): Treatment object with patient, practitioner, and condition relations
PUT /treatments/{treatment_id}
- Authentication: Yes
- Request Body: Same as create (all fields optional)
DELETE /treatments/{treatment_id}
- Authentication: Yes
-
Success Response (200):
{"message": "Treatment deleted successfully"}
GET /treatments/patient/{patient_id}/active
- Purpose: Get only active treatments for a specific patient
- Success Response (200): Array of treatment objects
GET /treatments/ongoing
- Purpose: Get treatments with status active or in_progress
-
Query Parameters:
-
patient_id(integer, optional): Filter by patient ID (defaults to current user's patient)
-
- Success Response (200): Array of treatment objects
These endpoints manage the many-to-many relationship between treatments and medications. In advanced mode, each linked medication can have treatment-specific overrides for dosage, frequency, dates, prescriber, and pharmacy.
GET /treatments/{treatment_id}/medications
- Purpose: Get all medications linked to a treatment, with computed effective values
- Success Response (200):
[
{
"id": 1,
"treatment_id": 5,
"medication_id": 12,
"specific_dosage": "20mg",
"specific_frequency": null,
"specific_duration": "6 weeks",
"timing_instructions": "Take with food",
"relevance_note": "Primary medication for this plan",
"specific_prescriber_id": 3,
"specific_pharmacy_id": null,
"specific_start_date": "2026-01-09",
"specific_end_date": null,
"specific_prescriber": {
"id": 3,
"name": "Dr. Smith",
"specialty": "Cardiology"
},
"specific_pharmacy": null,
"medication": {
"id": 12,
"medication_name": "Lisinopril",
"dosage": "10mg",
"frequency": "Once daily",
"route": "Oral",
"status": "active",
"effective_period_start": "2025-06-01",
"effective_period_end": "2025-09-25",
"practitioner": {
"id": 2,
"name": "Dr. Jones",
"specialty": "Internal Medicine"
},
"pharmacy": { "id": 1, "name": "CVS", "brand": null }
},
"effective_dosage": "20mg",
"effective_frequency": "Once daily",
"effective_start_date": "2026-01-09",
"effective_end_date": null,
"effective_prescriber": {
"id": 3,
"name": "Dr. Smith",
"specialty": "Cardiology"
},
"effective_pharmacy": { "id": 1, "name": "CVS", "brand": null },
"created_at": "2026-02-07T10:00:00Z",
"updated_at": "2026-02-07T10:00:00Z"
}
]Effective value logic:
-
effective_*fields use the treatment-specific override (specific_*) if set, otherwise fall back to the base medication's value. -
Smart end date: If
specific_start_dateis set butspecific_end_dateis not, and the medication'seffective_period_endis before the overridden start date,effective_end_dateis set tonull(treated as "Ongoing") to avoid displaying a stale end date.
POST /treatments/{treatment_id}/medications
- Request Body:
{
"medication_id": 12,
"specific_dosage": "20mg",
"specific_frequency": "Twice daily",
"specific_duration": "6 weeks",
"timing_instructions": "Take with food",
"relevance_note": "Primary medication for this plan",
"specific_prescriber_id": 3,
"specific_pharmacy_id": 1,
"specific_start_date": "2026-01-09",
"specific_end_date": "2026-03-01"
}- All fields except
medication_idare optional -
Validation:
specific_end_datemust be on or afterspecific_start_dateif both are provided - Success Response (200): Created relationship object
POST /treatments/{treatment_id}/medications/bulk
- Purpose: Link multiple medications to a treatment at once
- Request Body:
{
"medication_ids": [12, 15, 18],
"relevance_note": "Medications for treatment plan"
}- Skips medications that are already linked (no error)
- Success Response (200): Array of created relationship objects
PUT /treatments/{treatment_id}/medications/{relationship_id}
-
Request Body: Same fields as create (all optional, except
medication_idwhich is not updatable) - Success Response (200): Updated relationship object
DELETE /treatments/{treatment_id}/medications/{relationship_id}
-
Success Response (200):
{"status": "success", "message": "Medication link removed"}
Treatments also support relationships with encounters, lab results, and equipment using the same CRUD pattern:
| Relationship | Base Path | Purpose |
|---|---|---|
| Encounters | /{treatment_id}/encounters |
Link visits/encounters to treatment |
| Lab Results | /{treatment_id}/lab-results |
Link lab results to treatment |
| Equipment | /{treatment_id}/equipment |
Link medical equipment to treatment |
Each supports: GET (list), POST (link), POST /bulk (bulk link), PUT /{id} (update), DELETE /{id} (remove).
Base path: /api/v1/symptoms
POST /symptoms/
- Purpose: Create a new symptom definition (reusable symptom type)
- Request Body:
{
"patient_id": 1,
"symptom_name": "Headache",
"body_part": "Head",
"status": "active",
"notes": "Recurring tension headaches"
}GET /symptoms/
-
Query Parameters:
-
skip: Pagination offset (default: 0) -
limit: Max items (default: 100, max: 100) -
status: Filter by status (active,inactive,resolved) -
search: Search by symptom name
-
- Success Response (200): Array of symptom objects
GET /symptoms/{symptom_id}
- Success Response (200): Symptom object with details
PUT /symptoms/{symptom_id}
- Request Body: Same as create (all fields optional)
DELETE /symptoms/{symptom_id}
- Note: Deletes symptom and all occurrences via cascade
-
Success Response (200):
{"message": "Symptom deleted successfully"}
GET /symptoms/stats
-
Query Parameters:
-
patient_id: Optional patient ID for patient switching
-
- Success Response (200):
{
"total_symptoms": 5,
"active_symptoms": 3,
"resolved_symptoms": 2,
"total_occurrences": 47,
"recent_occurrences": 12
}GET /symptoms/timeline
-
Query Parameters:
-
patient_id: Optional patient ID -
start_date: Start date (YYYY-MM-DD) -
end_date: End date (YYYY-MM-DD)
-
- Purpose: Get timeline data formatted for visualization
- Success Response (200): Array of timeline data points
POST /symptoms/{symptom_id}/occurrences
- Purpose: Log a new episode/occurrence of an existing symptom
- Request Body:
{
"occurred_at": "2025-10-15T14:30:00Z",
"severity": "moderate",
"duration_minutes": 120,
"notes": "Started after lunch, took ibuprofen"
}GET /symptoms/{symptom_id}/occurrences
-
Query Parameters:
skip,limit - Success Response (200): Array of occurrence objects
GET /symptoms/{symptom_id}/occurrences/{occurrence_id}
PUT /symptoms/{symptom_id}/occurrences/{occurrence_id}
DELETE /symptoms/{symptom_id}/occurrences/{occurrence_id}
POST /symptoms/{symptom_id}/link-condition
- Purpose: Associate symptom with a diagnosed condition
- Request Body:
{
"condition_id": 3,
"symptom_id": 1,
"notes": "Primary symptom of this condition"
}GET /symptoms/{symptom_id}/conditions
- Success Response (200): Array of condition relationships
DELETE /symptoms/{symptom_id}/unlink-condition/{condition_id}
POST /symptoms/{symptom_id}/link-medication
- Purpose: Associate symptom with medication (side effect or treatment)
- Request Body:
{
"medication_id": 5,
"symptom_id": 1,
"relationship_type": "side_effect",
"notes": "Occurs 2 hours after taking medication"
}GET /symptoms/{symptom_id}/medications
DELETE /symptoms/{symptom_id}/unlink-medication/{medication_id}
POST /symptoms/{symptom_id}/link-treatment
- Request Body:
{
"treatment_id": 2,
"symptom_id": 1,
"notes": "Treatment helps reduce symptom severity"
}GET /symptoms/{symptom_id}/treatments
DELETE /symptoms/{symptom_id}/unlink-treatment/{treatment_id}
Base path: /api/v1/injuries
Purpose: Track physical injuries like sprains, fractures, burns, etc. with links to related medications, conditions, treatments, and procedures.
Base path: /api/v1/injury-types
GET /injury-types/
- Purpose: Get all injury types for dropdown selection
- Authentication: Yes
- Success Response (200): Array of injury type objects
[
{
"id": 1,
"name": "Sprain",
"description": "Ligament injury",
"is_system": true,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]GET /injury-types/dropdown
- Purpose: Get injury types formatted for dropdown (minimal response)
- Authentication: Yes
- Success Response (200): Array of dropdown options
POST /injury-types/
- Purpose: Create a user-defined injury type
- Authentication: Yes
- Request Body:
{
"name": "Custom Injury Type",
"description": "Description of the injury type"
}- Note: System types can only be created via database migration
-
Error Responses:
-
400: Name already exists
-
GET /injury-types/{injury_type_id}
- Purpose: Get a specific injury type by ID
- Success Response (200): Injury type object
DELETE /injury-types/{injury_type_id}
- Purpose: Delete a user-created injury type
- Note: Cannot delete system types or types referenced by injuries
-
Error Responses:
-
400: Cannot delete system type or type in use -
404: Injury type not found
-
POST /injuries/
- Purpose: Create a new injury record
- Authentication: Yes
- Request Body:
{
"patient_id": 1,
"injury_name": "Left Ankle Sprain",
"injury_type_id": 1,
"body_part": "Ankle",
"laterality": "left",
"date_of_injury": "2025-10-01",
"mechanism": "Twisted while running",
"severity": "moderate",
"status": "active",
"treatment_received": "Ice, compression, elevation",
"recovery_notes": "Improving with physical therapy",
"practitioner_id": 5,
"notes": "Patient advised to avoid running for 2 weeks",
"tags": ["sports", "acute"]
}-
Laterality values:
left,right,bilateral,not_applicable -
Severity values:
mild,moderate,severe,life-threatening -
Status values:
active,healing,resolved,chronic - Success Response (201): Injury with relations
GET /injuries/
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 100, max: 100): Items per page -
status(string, optional): Filter by status -
injury_type_id(integer, optional): Filter by injury type -
tags(array, optional): Filter by tags -
tag_match_all(boolean, default: false): Match ALL tags vs ANY
-
- Success Response (200): Array of injuries with relations
GET /injuries/{injury_id}
- Purpose: Get injury by ID with related information
- Success Response (200): Injury with relations
PUT /injuries/{injury_id}
- Request Body: Same fields as create (all optional)
- Success Response (200): Updated injury with relations
DELETE /injuries/{injury_id}
-
Success Response (200):
{"message": "Injury deleted successfully"}
GET /injuries/patient/{patient_id}/active
- Purpose: Get all active injuries for a patient
- Success Response (200): Array of active injuries with relations
Get Linked Medications
GET /injuries/{injury_id}/medications
- Purpose: Get all medications linked to an injury
- Success Response (200):
[
{
"id": 1,
"injury_id": 1,
"medication_id": 5,
"relevance_note": "Pain management",
"created_at": "2025-10-01T00:00:00Z",
"medication": {
"id": 5,
"medication_name": "Ibuprofen",
"dosage": "400mg",
"status": "active"
}
}
]Link Medication to Injury
POST /injuries/{injury_id}/medications
- Request Body:
{
"medication_id": 5,
"relevance_note": "Pain management"
}-
Error Responses:
-
400: Already linked or different patient -
404: Medication not found
-
Unlink Medication from Injury
DELETE /injuries/{injury_id}/medications/{medication_id}
-
Success Response (200):
{"message": "Medication unlinked from injury"}
Get Linked Conditions
GET /injuries/{injury_id}/conditions
Link Condition to Injury
POST /injuries/{injury_id}/conditions
- Request Body:
{
"condition_id": 3,
"relevance_note": "Underlying condition"
}Unlink Condition from Injury
DELETE /injuries/{injury_id}/conditions/{condition_id}
Get Linked Treatments
GET /injuries/{injury_id}/treatments
Link Treatment to Injury
POST /injuries/{injury_id}/treatments
- Request Body:
{
"treatment_id": 2,
"relevance_note": "Physical therapy for recovery"
}Unlink Treatment from Injury
DELETE /injuries/{injury_id}/treatments/{treatment_id}
Get Linked Procedures
GET /injuries/{injury_id}/procedures
Link Procedure to Injury
POST /injuries/{injury_id}/procedures
- Request Body:
{
"procedure_id": 4,
"relevance_note": "Surgery to repair damage"
}Unlink Procedure from Injury
DELETE /injuries/{injury_id}/procedures/{procedure_id}
Base path: /api/v1/standardized-tests
Purpose: Search and autocomplete for standardized lab tests using LOINC codes
GET /standardized-tests/search
- Purpose: Search for standardized tests
-
Query Parameters:
-
query: Search term (test name, LOINC code, etc.) -
category: Filter by category -
skip: Pagination offset -
limit: Max items
-
- Success Response (200):
{
"tests": [
{
"id": 1,
"loinc_code": "2345-7",
"test_name": "Glucose [Mass/volume] in Serum or Plasma",
"short_name": "Glucose",
"default_unit": "mg/dL",
"category": "Chemistry",
"common_names": ["Blood sugar", "Blood glucose"],
"is_common": true
}
],
"total": 1
}GET /standardized-tests/autocomplete
- Purpose: Get autocomplete suggestions for test names
-
Query Parameters:
-
q: Query string (min 2 characters) -
limit: Max suggestions (default: 10)
-
- Success Response (200):
[
{
"value": "Glucose [Mass/volume] in Serum or Plasma",
"label": "Glucose - Blood sugar test",
"loinc_code": "2345-7",
"default_unit": "mg/dL",
"category": "Chemistry"
}
]GET /standardized-tests/common
- Purpose: Get frequently ordered tests
- Success Response (200): Array of common test objects
GET /standardized-tests/by-category/{category}
- Purpose: Get all tests in a specific category
- Success Response (200): Array of test objects
GET /standardized-tests/by-loinc/{loinc_code}
- Purpose: Get test details by LOINC code
- Success Response (200): Test object
GET /standardized-tests/by-name/{test_name}
- Purpose: Get test details by exact name match
- Success Response (200): Test object
GET /standardized-tests/count
- Purpose: Get total number of standardized tests in database
- Success Response (200):
{
"total_tests": 45672,
"common_tests": 150,
"categories": 12
}POST /standardized-tests/batch-match
- Purpose: Match multiple test names to LOINC codes
- Request Body:
{
"test_names": ["Glucose", "Hemoglobin", "Cholesterol"]
}- Success Response (200):
{
"matches": [
{
"input": "Glucose",
"matched": true,
"loinc_code": "2345-7",
"test_name": "Glucose [Mass/volume] in Serum or Plasma"
}
],
"total_requested": 3,
"total_matched": 2
}Base path: /api/v1/insurances
POST /insurances/
- Request Body:
{
"patient_id": 1,
"provider_name": "Blue Cross Blue Shield",
"policy_number": "ABC123456",
"group_number": "GRP789",
"insurance_type": "primary",
"effective_date": "2025-01-01",
"expiration_date": "2025-12-31",
"notes": "PPO plan"
}Base path: /api/v1/emergency-contacts
POST /emergency-contacts/
- Request Body:
{
"patient_id": 1,
"name": "Jane Doe",
"relationship": "Spouse",
"phone_number": "+1234567890",
"email": "jane@example.com",
"address": "123 Main St",
"is_primary": true
}Base path: /api/v1/family-members
POST /family-members/
- Authentication: Yes
- Request Body:
{
"patient_id": 1,
"name": "John Doe Sr.",
"relationship": "Father",
"birth_date": "1960-05-20",
"is_deceased": false,
"medical_history": "Diabetes, Hypertension"
}- Success Response (201): Family member object
GET /family-members/
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 100, max: 100): Items per page -
relationship(string, optional): Filter by relationship type
-
- Success Response (200): Array of family member objects with conditions
GET /family-members/dropdown
- Purpose: Get family members formatted for dropdown selection in forms
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"name": "John Doe Sr.",
"relationship": "Father"
}
]GET /family-members/{family_member_id}
- Purpose: Get family member with conditions
- Authentication: Yes
- Success Response (200): Family member object with conditions
PUT /family-members/{family_member_id}
- Authentication: Yes
- Request Body: Same as create (all fields optional)
- Success Response (200): Updated family member object
DELETE /family-members/{family_member_id}
- Authentication: Yes
-
Success Response (200):
{"message": "Family Member deleted successfully"}
GET /family-members/search/
- Purpose: Search family members by name
- Authentication: Yes
-
Query Parameters:
-
name(string, required, min 2 chars): Name search term
-
- Success Response (200): Array of matching family member objects
GET /family-members/{family_member_id}/conditions
- Purpose: Get all medical conditions for a specific family member
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"family_member_id": 1,
"condition_name": "Diabetes Type 2",
"diagnosis_age": 55,
"notes": "Controlled with medication",
"created_at": "2025-10-01T00:00:00Z"
}
]POST /family-members/{family_member_id}/conditions
- Purpose: Add a medical condition to a family member's history
- Authentication: Yes
- Request Body:
{
"condition_name": "Hypertension",
"diagnosis_age": 45,
"notes": "Started medication at 50"
}- Success Response (201): Created condition object
PUT /family-members/{family_member_id}/conditions/{condition_id}
- Authentication: Yes
- Request Body: Same fields as create (all optional)
- Success Response (200): Updated condition object
DELETE /family-members/{family_member_id}/conditions/{condition_id}
- Authentication: Yes
-
Success Response (200):
{"message": "Family Condition deleted successfully"}
Base path: /api/v1/pharmacies
POST /pharmacies/
- Request Body:
{
"name": "CVS Pharmacy - Main Street",
"brand": "CVS",
"street_address": "456 Oak St",
"city": "Anytown",
"state": "NC",
"zip_code": "27514",
"country": "United States",
"store_number": "12345",
"phone_number": "+1234567890",
"fax_number": "+1234567891",
"email": "pharmacy@example.com",
"website": "https://www.cvs.com",
"hours": "Mon-Fri 9AM-9PM",
"drive_through": true,
"twenty_four_hour": false,
"specialty_services": "Vaccinations, Compounding, Medication Therapy Management"
}-
Notes:
zip_codeaccepts international postal codes (US ZIP, Canadian, UK, etc.)
GET /pharmacies/
-
Query Parameters:
-
skip(integer, optional, default: 0): Number of records to skip (for pagination) -
limit(integer, optional, default: 100): Maximum number of records to return
-
Base path: /api/v1/practitioners
POST /practitioners/
- Request Body:
{
"name": "Dr. Sarah Smith",
"specialty": "Cardiology",
"practice_id": 1,
"phone_number": "+1234567890",
"email": "dr.smith@clinic.com",
"website": "https://drsmith.com",
"rating": 4.5
}-
Notes:
practice_idlinks the practitioner to a Practice entity (optional). The legacypracticestring field is still accepted for backward compatibility.
GET /practitioners/
-
Query Parameters:
-
skip(integer, optional, default: 0): Number of records to skip -
limit(integer, optional, default: 100, max: 100): Maximum records to return -
specialty(string, optional): Filter by specialty -
practice_id(integer, optional): Filter by practice
-
-
Response includes
practice_name(resolved from the linked Practice entity) and timestamps (created_at,updated_at).
GET /practitioners/{practitioner_id}
-
Response: Full practitioner details with
practice_namefrom linked Practice.
PUT /practitioners/{practitioner_id}
- Request Body: Same fields as create, all optional.
DELETE /practitioners/{practitioner_id}
GET /practitioners/search/by-name
-
Query Parameters:
-
name(string, required, min: 2): Search term
-
GET /practitioners/specialties
-
Response:
{ "specialties": ["Cardiology", "Dermatology", ...] }
Base path: /api/v1/practices
Purpose: Manage medical practices or clinics that practitioners belong to.
POST /practices/
- Authentication: Yes
- Request Body:
{
"name": "City General Hospital",
"phone_number": "+1234567890",
"fax_number": "+1234567891",
"website": "https://www.citygeneral.com",
"patient_portal_url": "https://portal.citygeneral.com",
"notes": "Main campus location",
"locations": [
{
"label": "Main Office",
"address": "123 Medical Dr",
"city": "Chapel Hill",
"state": "NC",
"zip": "27514",
"phone": "+1234567890"
}
]
}-
Success Response (200): Created practice object with
id,created_at,updated_at - Notes: Practice name must be unique (case-insensitive). Name must be 2-150 characters.
GET /practices/
- Authentication: Yes
-
Query Parameters:
-
skip(integer, optional, default: 0): Number of records to skip -
limit(integer, optional, default: 100, max: 100): Maximum records to return -
search(string, optional, min: 1): Search by practice name
-
GET /practices/summary
- Authentication: Yes
- Purpose: Lightweight list for dropdowns and selectors
- Success Response (200):
[
{ "id": 1, "name": "City General Hospital" },
{ "id": 2, "name": "Downtown Medical Group" }
]GET /practices/search/by-name
- Authentication: Yes
-
Query Parameters:
-
name(string, required, min: 1): Search term
-
GET /practices/{practice_id}
- Authentication: Yes
-
Response: Practice details with
practitioner_countindicating how many practitioners are linked.
{
"id": 1,
"name": "City General Hospital",
"phone_number": "+1234567890",
"fax_number": "+1234567891",
"website": "https://www.citygeneral.com",
"patient_portal_url": "https://portal.citygeneral.com",
"notes": "Main campus location",
"locations": [...],
"created_at": "2026-02-14T12:00:00Z",
"updated_at": "2026-02-14T12:00:00Z",
"practitioner_count": 5
}PUT /practices/{practice_id}
- Authentication: Yes
- Request Body: Same fields as create, all optional.
DELETE /practices/{practice_id}
- Authentication: Yes
-
Notes: Deleting a practice sets
practice_idto NULL on all linked practitioners (ON DELETE SET NULL).
Base path: /api/v1/notifications
Purpose: Manage notification channels, preferences, and view notification history.
GET /notifications/event-types
- Purpose: Get list of available notification event types
- Authentication: Yes
- Success Response (200):
{
"event_types": [
{
"value": "medication_reminder",
"label": "Medication Reminder",
"description": "Reminders for taking medications",
"category": "reminders",
"is_implemented": true
},
{
"value": "lab_result_available",
"label": "Lab Result Available",
"description": "Notification when lab results are ready",
"category": "results",
"is_implemented": true
}
]
}GET /notifications/channels
- Purpose: List all notification channels for current user
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"name": "My Email",
"channel_type": "email",
"is_enabled": true,
"is_verified": true,
"last_test_at": "2025-10-01T10:00:00Z",
"last_test_status": "success",
"last_used_at": "2025-10-04T08:00:00Z",
"total_notifications_sent": 25,
"config_valid": true,
"config_error": null,
"created_at": "2025-09-01T00:00:00Z",
"updated_at": "2025-10-01T00:00:00Z"
}
]POST /notifications/channels
- Purpose: Create a new notification channel
- Authentication: Yes
- Request Body:
{
"name": "My Telegram",
"channel_type": "telegram",
"config": {
"chat_id": "123456789",
"bot_token": "your_bot_token"
},
"is_enabled": true
}-
Channel types:
email,telegram,discord,slack,webhook,pushover - Success Response (201): Created channel object
GET /notifications/channels/{channel_id}
- Purpose: Get channel with masked configuration
- Authentication: Yes
- Success Response (200):
{
"id": 1,
"name": "My Telegram",
"channel_type": "telegram",
"is_enabled": true,
"is_verified": true,
"last_test_at": "2025-10-01T10:00:00Z",
"last_test_status": "success",
"config_masked": {
"chat_id": "123***789",
"bot_token": "***masked***"
}
}PUT /notifications/channels/{channel_id}
- Purpose: Update notification channel
- Authentication: Yes
- Request Body:
{
"name": "Updated Name",
"config": {
"chat_id": "new_chat_id"
},
"is_enabled": false
}- Success Response (200): Updated channel object
DELETE /notifications/channels/{channel_id}
- Purpose: Delete a notification channel
- Authentication: Yes
- Success Response (204): No content
POST /notifications/channels/{channel_id}/test
- Purpose: Send a test notification to verify channel configuration
- Authentication: Yes
- Request Body (optional):
{
"message": "Custom test message"
}- Success Response (200):
{
"success": true,
"message": "Test notification sent successfully",
"channel_name": "My Telegram",
"sent_at": "2025-10-04T10:30:00Z"
}- Failure Response (200):
{
"success": false,
"message": "Channel configuration is invalid: missing bot_token",
"channel_name": "My Telegram",
"sent_at": null
}GET /notifications/preferences
- Purpose: List all notification preferences for current user
- Authentication: Yes
- Success Response (200):
[
{
"id": 1,
"channel_id": 1,
"channel_name": "My Email",
"event_type": "medication_reminder",
"is_enabled": true,
"remind_before_minutes": 30,
"created_at": "2025-09-01T00:00:00Z",
"updated_at": "2025-10-01T00:00:00Z"
}
]GET /notifications/preferences/matrix
- Purpose: Get full preference matrix (events x channels)
- Authentication: Yes
- Success Response (200):
{
"channels": [
{"id": 1, "name": "My Email", "channel_type": "email", ...},
{"id": 2, "name": "My Telegram", "channel_type": "telegram", ...}
],
"events": ["medication_reminder", "lab_result_available", "appointment_reminder"],
"preferences": {
"medication_reminder": {"1": true, "2": false},
"lab_result_available": {"1": true, "2": true},
"appointment_reminder": {"1": false, "2": true}
}
}POST /notifications/preferences
- Purpose: Set or update a notification preference
- Authentication: Yes
- Request Body:
{
"channel_id": 1,
"event_type": "medication_reminder",
"is_enabled": true,
"remind_before_minutes": 30
}- Success Response (200): Updated preference object
GET /notifications/history
- Purpose: Get notification history for current user
- Authentication: Yes
-
Query Parameters:
-
page(integer, default: 1): Page number -
page_size(integer, default: 20, max: 100): Items per page -
status_filter(string, optional): Filter by status -
event_type(string, optional): Filter by event type
-
- Success Response (200):
{
"items": [
{
"id": 1,
"event_type": "medication_reminder",
"title": "Medication Reminder",
"message_preview": "Time to take your Aspirin...",
"channel_name": "My Email",
"channel_type": "email",
"status": "sent",
"attempt_count": 1,
"error_message": null,
"created_at": "2025-10-04T08:00:00Z",
"sent_at": "2025-10-04T08:00:05Z"
}
],
"total": 50,
"page": 1,
"page_size": 20
}Base path: /api/v1/entity-files
POST /entity-files/
-
Content-Type:
multipart/form-data -
Request Body:
-
file: File to upload -
entity_type: Type (medication, condition, etc.) -
entity_id: ID of related record -
description: File description
-
- Max Size: 15MB
- Allowed Types: PDF, JPEG, PNG, GIF
GET /entity-files/{entity_type}/{entity_id}
GET /entity-files/{file_id}/download
DELETE /entity-files/{file_id}
Base path: /api/v1/patient-sharing
POST /patient-sharing/
- Purpose: Share a patient record with another user via email invitation
- Authentication: Yes
- Request Body:
{
"patient_id": 1,
"recipient_email": "doctor@example.com",
"access_level": "view",
"expiration_date": "2025-12-31",
"message": "Sharing medical records for consultation"
}-
Access levels:
view,edit,full - Success Response (201): Share invitation object
POST /patient-sharing/bulk-invite
- Purpose: Send sharing invitations to multiple recipients at once
- Authentication: Yes
- Request Body:
{
"patient_id": 1,
"recipient_emails": ["doctor1@example.com", "doctor2@example.com"],
"access_level": "view",
"expiration_date": "2025-12-31",
"message": "Sharing medical records for consultation"
}- Success Response (200):
{
"successful_invites": 2,
"failed_invites": 0,
"results": [...]
}DELETE /patient-sharing/{share_id}
- Purpose: Revoke a user's access to a shared patient record
- Authentication: Yes
-
Success Response (200):
{"message": "Access revoked successfully"}
DELETE /patient-sharing/remove-my-access/{patient_id}
- Purpose: Remove your own access to a patient record shared with you
- Authentication: Yes
-
Success Response (200):
{"message": "Access removed successfully"}
GET /patient-sharing/shared-with-me
- Purpose: Get all patient records that others have shared with you
- Authentication: Yes
- Success Response (200): Array of shared patient access objects
GET /patient-sharing/shared-by-me
- Purpose: Get all patient records you have shared with others
- Authentication: Yes
- Success Response (200): Array of patient sharing objects with recipient info
GET /patient-sharing/stats/user
- Purpose: Get statistics about patient sharing for current user
- Authentication: Yes
- Success Response (200):
{
"total_shared_with_me": 3,
"total_shared_by_me": 5,
"active_shares": 4,
"pending_invitations": 2
}POST /patient-sharing/cleanup-expired
- Purpose: Remove expired sharing invitations (admin/scheduled task)
- Authentication: Yes
- Success Response (200):
{
"expired_shares_removed": 5,
"cleanup_date": "2025-10-04T10:30:00Z"
}Base path: /api/v1/invitations
GET /invitations/pending
- Purpose: Get all pending invitations for the current user
- Authentication: Yes
- Success Response (200): Array of pending invitation objects
GET /invitations/sent
- Purpose: Get all invitations sent by the current user
- Authentication: Yes
- Success Response (200): Array of sent invitation objects
POST /invitations/{invitation_id}/respond
- Purpose: Accept or reject an invitation
- Authentication: Yes
- Request Body:
{
"action": "accept"
}-
Actions:
accept,reject - Success Response (200): Updated invitation object
POST /invitations/{invitation_id}/revoke
- Purpose: Revoke an invitation you sent (before it's accepted)
- Authentication: Yes
-
Success Response (200):
{"message": "Invitation revoked successfully"}
POST /invitations/cleanup
- Purpose: Remove expired invitations (admin/scheduled task)
- Authentication: Yes
- Success Response (200):
{
"message": "Expired 10 old invitations",
"expired_count": 10
}GET /invitations/summary
- Purpose: Get summary counts of invitations by status
- Authentication: Yes
- Success Response (200):
{
"pending_received": 2,
"pending_sent": 3,
"accepted": 10,
"rejected": 1,
"expired": 5
}Base path: /api/v1/family-history-sharing
Purpose: Share family medical history with relatives for better health tracking
GET /family-history-sharing/mine
- Purpose: Get all family history accessible to current user (owned + shared)
- Success Response (200):
{
"owned_family_members": [...],
"shared_family_members": [...],
"total_owned": 5,
"total_shared": 3
}GET /family-history-sharing/{family_member_id}/shares
- Purpose: Get list of users who have access to this family member's history
- Success Response (200): Array of share objects with user info
POST /family-history-sharing/{family_member_id}/shares
- Purpose: Share a family member's history with another user
- Request Body:
{
"shared_with_user_id": 5,
"permission_level": "view",
"notes": "Sharing father's medical history"
}-
Permission levels:
view,edit - Success Response (201): Created share object
DELETE /family-history-sharing/{family_member_id}/shares/{user_id}
- Purpose: Remove a user's access to family member history
-
Success Response (200):
{"message": "Access revoked successfully"}
DELETE /family-history-sharing/shared-with-me/{family_member_id}/remove-access
- Purpose: Remove own access to shared family history
-
Success Response (200):
{"message": "Access removed successfully"}
POST /family-history-sharing/bulk-invite
- Purpose: Invite multiple family members at once
- Request Body:
{
"family_member_ids": [1, 2, 3],
"recipient_emails": ["sibling1@example.com", "sibling2@example.com"],
"permission_level": "view",
"message": "Sharing our family medical history"
}- Success Response (200):
{
"successful_invites": 5,
"failed_invites": 1,
"results": [...]
}GET /family-history-sharing/{family_member_id}/details
- Purpose: Get detailed family member information with medical history
- Success Response (200): Family member object with full medical history
GET /family-history-sharing/shared-with-me
- Purpose: Get all family history shared with current user by others
- Success Response (200): Array of shared family member objects
GET /family-history-sharing/my-own
- Purpose: Get only family members owned by current user
- Success Response (200): Array of owned family member objects
GET /family-history-sharing/shared-by-me
- Purpose: Get family history records that current user has shared with others
- Success Response (200): Array of family members with share information
Base path: /api/v1/search
GET /search/
-
Query Parameters:
-
q(string, required): Search query -
type(string, optional): Filter by record type -
patient_id(integer, optional): Filter by patient
-
- Success Response (200):
{
"results": [
{
"type": "medication",
"id": 1,
"name": "Aspirin 100mg",
"patient_id": 1
}
],
"total": 1
}Base path: /api/v1/tags
Note: Tags are stored directly on entities (medications, lab results, conditions, etc.) as arrays. The tags API provides cross-entity tag management, search, and autocomplete functionality.
GET /tags/popular
- Purpose: Get most popular tags across multiple entity types
- Authentication: Yes
-
Query Parameters:
-
entity_types(array, default: all types): Entity types to search (lab_result, medication, condition, procedure, immunization, treatment, encounter, allergy, practice) -
limit(integer, default: 20, max: 50): Maximum number of tags
-
- Success Response (200):
[
{
"tag": "cardiology",
"count": 25,
"entity_types": ["medication", "condition"]
},
{
"tag": "routine",
"count": 18,
"entity_types": ["lab_result", "immunization"]
}
]GET /tags/search
- Purpose: Search across entity types by tags
- Authentication: Yes
-
Query Parameters:
-
tags(array, required): Tags to search for -
entity_types(array, default: all types): Entity types to search -
limit_per_entity(integer, default: 10, max: 20): Max results per entity type
-
- Success Response (200):
{
"medication": [
{ "id": 1, "medication_name": "Aspirin", "tags": ["cardiology", "daily"] }
],
"condition": [
{ "id": 3, "diagnosis": "Hypertension", "tags": ["cardiology", "chronic"] }
],
"lab_result": []
}GET /tags/autocomplete
- Purpose: Get tag suggestions for autocomplete as user types
- Authentication: Yes
-
Query Parameters:
-
q(string, required, min: 1, max: 50): Query string -
limit(integer, default: 10, max: 20): Maximum suggestions
-
- Success Response (200):
["cardiology", "cardiac", "cardio-checkup"]GET /tags/suggestions
- Purpose: Get tag suggestions based on what users have actually created
- Authentication: Yes
-
Query Parameters:
-
entity_type(string, optional): Suggest tags for specific entity type -
limit(integer, default: 20, max: 50): Maximum suggestions
-
- Success Response (200):
["routine", "annual", "follow-up", "urgent", "cardiology"]PUT /tags/rename
- Purpose: Rename a tag across all entities
- Authentication: Yes
-
Query Parameters:
-
old_tag(string, required): Current tag name to rename -
new_tag(string, required): New tag name
-
- Success Response (200):
{
"message": "Successfully renamed 'cardio' to 'cardiology'",
"records_updated": 15
}DELETE /tags/delete
- Purpose: Delete a tag from all entities
- Authentication: Yes
-
Query Parameters:
-
tag(string, required): Tag name to delete
-
- Success Response (200):
{
"message": "Successfully deleted tag 'old-tag'",
"records_updated": 8
}PUT /tags/replace
- Purpose: Replace one tag with another across all entities
- Authentication: Yes
-
Query Parameters:
-
old_tag(string, required): Tag to replace -
new_tag(string, required): Replacement tag
-
- Success Response (200):
{
"message": "Successfully replaced 'cardio' with 'cardiology'",
"records_updated": 15
}POST /tags/create
- Purpose: Create a new tag in the user tags registry
- Authentication: Yes
- Request Body:
{
"tag": "new-category"
}- Success Response (200):
{
"message": "Successfully created tag 'new-category'",
"tag": "new-category"
}Base path: /api/v1/custom-reports
POST /custom-reports/generate
- Request Body:
{
"patient_id": 1,
"report_type": "medical_summary",
"date_range": {
"start": "2025-01-01",
"end": "2025-12-31"
},
"include_sections": ["medications", "lab_results", "conditions"]
}GET /custom-reports/
GET /custom-reports/{report_id}/download
Base path: /api/v1/export
GET /export/{record_type}
-
Path Parameters:
-
record_type:medications,lab-results,allergies, etc.
-
-
Query Parameters:
-
patient_id(integer, required): Patient to export -
format(string, optional):csvorpdf(default: csv)
-
- Success Response: File download
Base path: /api/v1/paperless
POST /paperless/upload
- Request Body:
{
"document_id": 123,
"tags": ["medical", "lab-results"],
"correspondent": "LabCorp"
}POST /paperless/sync
GET /paperless/documents
Base path: /api/v1/system
GET /system/health
- Authentication: No
- Purpose: Basic system health check
- Success Response (200):
{
"status": "healthy",
"timestamp": "2025-10-19T12:00:00Z",
"logging_system": {
"current_level": "INFO",
"level_valid": true,
"categories_configured": 2
}
}GET /system/version
- Authentication: No
- Purpose: Get application version information
- Success Response (200):
{
"app_name": "MediKeep",
"version": "0.40.0",
"timestamp": "2025-10-19T12:00:00Z"
}GET /system/log-level
- Authentication: No
- Rate Limit: 60 requests/minute per IP
- Purpose: Get current logging configuration for frontend integration
- Success Response (200):
{
"current_level": "INFO",
"available_levels": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
"default_level": "INFO",
"categories": ["app", "security"],
"file_mapping": {
"app": "logs/app.log - Patient access, API calls, frontend errors, performance events",
"security": "logs/security.log - Authentication failures, security threats, suspicious activity"
},
"configuration": {
"log_level_numeric": 20,
"simplified_structure": true,
"file_count": 2,
"max_file_size_mb": 50,
"backup_count": 10
},
"timestamp": "2025-10-19T12:00:00Z",
"rate_limit_info": {
"requests_remaining": 59,
"requests_limit": 60,
"window_seconds": 60,
"reset_time": "2025-10-19T12:01:00Z"
}
}- Error Response (429 - Rate Limit Exceeded):
{
"detail": "Rate limit exceeded. Maximum 60 requests per minute."
}-
Headers (on rate limit):
-
X-RateLimit-Limit: 60 -
X-RateLimit-Remaining: 0 -
X-RateLimit-Reset: Unix timestamp -
Retry-After: Seconds until reset
-
GET /system/log-rotation-config
- Authentication: No
- Rate Limit: 60 requests/minute per IP
- Purpose: Get current log rotation configuration and status
- Success Response (200):
{
"rotation_method": "python",
"logrotate_available": false,
"configuration": {
"method": "auto",
"size": "5M",
"time": "daily",
"backup_count": 30,
"compression": true,
"retention_days": 180
},
"log_directory": "E:\\path\\to\\logs",
"log_files": {
"app": {
"path": "E:\\path\\to\\logs\\app.log",
"size_bytes": 1048576,
"size_mb": 1.0,
"exists": true
},
"security": {
"path": "E:\\path\\to\\logs\\security.log",
"size_bytes": 524288,
"size_mb": 0.5,
"exists": true
}
},
"features": {
"size_based_rotation": true,
"time_based_rotation": false,
"compression": false,
"hybrid_rotation": false
},
"notes": {
"python_rotation": "Size-based only, fallback for development/Windows",
"logrotate_rotation": "Full features including time-based and compression"
},
"timestamp": "2025-10-19T12:00:00Z"
}-
Use Cases:
- Monitor log file sizes and rotation status
- Verify which rotation method is active (logrotate vs Python)
- Check if log rotation features are working correctly
- Troubleshoot log management issues
POST /system/backup
- Authentication: Yes (Admin only)
POST /system/restore
- Authentication: Yes (Admin only)
GET /system/releases
- Authentication: No (public information)
- Purpose: Get application release notes from GitHub
- Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| limit | integer | 10 | Maximum number of releases to return (capped at 20) |
- Success Response (200):
{
"releases": [
{
"tag_name": "v0.58.0",
"name": "v0.58.0",
"body": "## Changes\n- Feature X\n- Bug fix Y",
"published_at": "2026-03-15T00:00:00Z",
"html_url": "https://github.com/afairgiant/MediKeep/releases/tag/v0.58.0"
}
],
"current_version": "0.58.0",
"timestamp": "2026-03-15T12:00:00Z"
}- Error Handling: Returns empty releases list on GitHub API failure (graceful degradation).
Base path: /api/v1/utils
GET /utils/timezone-info
- Purpose: Get facility timezone information for proper date/time handling
- Authentication: No
- Success Response (200):
{
"timezone": "America/New_York",
"utc_offset": "-05:00",
"current_time": "2025-10-19T14:30:00-05:00",
"is_dst": false
}Base path: /api/v1/frontend-logs
Purpose: Centralized logging for frontend events, errors, and user actions.
POST /frontend-logs/log
- Purpose: Log frontend events and errors from the React frontend
- Authentication: Optional (user_id can be provided in request)
- Request Body:
{
"level": "error",
"message": "JavaScript error occurred",
"category": "error",
"timestamp": "2025-10-19T14:30:00Z",
"url": "/dashboard",
"user_agent": "Mozilla/5.0...",
"stack_trace": "Error: Something went wrong\n at Component.render...",
"user_id": 1,
"session_id": "abc123",
"component": "PatientList",
"action": "load_data",
"details": {
"patient_id": 123,
"attempt": 3
}
}-
Log levels:
error,warn,info,debug -
Categories:
error,user_action,performance,security - Success Response (200):
{
"status": "logged",
"timestamp": "2025-10-19T14:30:00Z"
}POST /frontend-logs/error
- Purpose: Log frontend errors with detailed context (for React error boundaries)
- Authentication: Optional
- Request Body:
{
"error_message": "Cannot read property 'id' of undefined",
"error_type": "TypeError",
"stack_trace": "TypeError: Cannot read property 'id'...",
"component_name": "MedicationList",
"props": {
"patientId": 123
},
"user_id": 1,
"url": "/medications",
"timestamp": "2025-10-19T14:30:00Z",
"user_agent": "Mozilla/5.0...",
"browser_info": {
"name": "Chrome",
"version": "118.0"
}
}- Success Response (200):
{
"status": "error_logged",
"timestamp": "2025-10-19T14:30:00Z"
}POST /frontend-logs/user-action
- Purpose: Log user actions for analytics and audit purposes
- Authentication: Yes
- Request Body:
{
"action": "medication_created",
"component": "MedicationForm",
"details": {
"medication_name": "Aspirin",
"dosage": "100mg"
},
"user_id": 1,
"timestamp": "2025-10-19T14:30:00Z",
"url": "/medications/new"
}- Success Response (200):
{
"status": "action_logged",
"timestamp": "2025-10-19T14:30:00Z"
}GET /frontend-logs/health
- Purpose: Health check endpoint for frontend logging service
- Authentication: No
- Success Response (200):
{
"status": "healthy",
"service": "frontend_logging",
"timestamp": "2025-10-19T14:30:00Z"
}Base path: /api/v1/admin
Purpose: Admin-only endpoints for system management, backups, restores, and maintenance operations. All endpoints in this section require admin role authentication.
GET /admin/dashboard/stats
- Purpose: Get comprehensive dashboard statistics
- Authentication: Yes (Admin only)
- Success Response (200):
{
"total_users": 150,
"total_patients": 145,
"total_practitioners": 25,
"total_medications": 450,
"total_lab_results": 1200,
"total_vitals": 800,
"total_conditions": 350,
"total_allergies": 180,
"total_immunizations": 220,
"total_procedures": 150,
"total_treatments": 100,
"total_encounters": 500,
"recent_registrations": 12,
"active_medications": 280,
"pending_lab_results": 15
}GET /admin/dashboard/recent-activity
- Purpose: Get recent activity across all models
- Authentication: Yes (Admin only)
-
Query Parameters:
-
limit(integer, default: 20): Number of items -
action_filter(string, optional): Filter by action (created,updated,deleted) -
entity_filter(string, optional): Filter by entity type (medication,patient, etc.)
-
- Success Response (200):
[
{
"id": 123,
"model_name": "Medication",
"action": "created",
"description": "Created Medication: Aspirin 100mg",
"timestamp": "2025-10-04T10:30:00Z",
"user_info": "johndoe"
}
]GET /admin/dashboard/system-health
- Purpose: Get comprehensive system health information
- Authentication: Yes (Admin only)
- Success Response (200):
{
"database_status": "healthy",
"total_records": 5000,
"last_backup": "2025-10-03T02:00:00Z",
"system_uptime": "5 days, 3 hours",
"database_connection_test": true,
"memory_usage": "Normal",
"disk_usage": "Database: 45.5 MB"
}GET /admin/dashboard/system-metrics
- Purpose: Get detailed system performance metrics
- Authentication: Yes (Admin only)
- Success Response (200):
{
"timestamp": "2025-10-04T10:30:00Z",
"services": {
"api": { "status": "operational", "response_time_ms": 15.2 },
"authentication": { "status": "operational" },
"frontend_logging": { "status": "operational" },
"admin_interface": { "status": "operational" }
},
"database": {
"connection_pool_size": "Available",
"active_connections": 1,
"query_performance": "fast"
},
"application": {
"memory_usage": "normal",
"cpu_usage": "low",
"response_time": "< 100ms",
"system_load": "normal"
},
"storage": {
"database_size": "45.5 MB",
"upload_directory_size": "120.3 MB",
"available_space": "Available"
},
"security": {
"ssl_enabled": true,
"authentication_method": "JWT",
"environment": "production",
"authentication_status": "operational",
"authorization_status": "operational",
"session_status": "operational"
}
}GET /admin/dashboard/health-check
- Purpose: Quick health check for monitoring services
- Authentication: No
- Success Response (200):
{
"status": "healthy",
"timestamp": "2025-10-04T10:30:00Z",
"service": "medical_records_api",
"version": "2.0",
"uptime": "432000s",
"startup_time": "2025-09-29T07:30:00Z"
}GET /admin/dashboard/storage-health
- Purpose: Check storage system health
- Authentication: Yes (Admin only)
- Success Response (200):
{
"status": "healthy",
"directories": {
"uploads": {
"path": "uploads/lab_result_files",
"exists": true,
"write_permission": true,
"size_mb": 120.3,
"file_count": 450
},
"backups": {
"path": "backups",
"exists": true,
"write_permission": true,
"size_mb": 250.0,
"file_count": 10
},
"logs": {
"path": "logs",
"exists": true,
"write_permission": true,
"size_mb": 15.5,
"file_count": 5
}
},
"disk_space": {
"total_gb": 500.0,
"used_gb": 150.5,
"free_gb": 349.5,
"usage_percent": 30.1
}
}GET /admin/dashboard/analytics-data
- Purpose: Get analytics data for dashboard charts
- Authentication: Yes (Admin only)
-
Query Parameters:
-
days(integer, default: 7): Number of days to analyze
-
- Success Response (200):
{
"weekly_activity": {
"labels": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
"data": [45, 52, 48, 55, 60, 30, 25],
"total": 315
},
"model_activity": {
"medication": 45,
"lab_result": 60,
"vitals": 80
},
"hourly_activity": {
"labels": ["00:00", "01:00", ...],
"data": [5, 2, 1, 0, 0, 3, ...]
}
}GET /admin/dashboard/test-access
- Purpose: Verify admin access and token validity
- Authentication: Yes (Admin only)
- Success Response (200):
{
"message": "Admin access verified",
"user": "admin_user",
"role": "admin",
"timestamp": "2025-10-04T10:30:00Z"
}Base path: /api/v1/admin/models
Purpose: Django-style generic CRUD operations for all models
GET /admin/models/
- Purpose: Get list of all available models for management
- Authentication: Yes (Admin only)
- Success Response (200):
["user", "patient", "medication", "lab_result", "condition", "allergy", ...]GET /admin/models/{model_name}/metadata
- Purpose: Get metadata for a specific model (fields, types, relationships)
- Success Response (200):
{
"name": "medication",
"table_name": "medications",
"fields": [
{ "name": "id", "type": "integer", "nullable": false, "primary_key": true },
{
"name": "medication_name",
"type": "string",
"nullable": false,
"max_length": 255
},
{
"name": "status",
"type": "string",
"choices": ["active", "stopped", "on-hold"]
}
],
"relationships": { "patient": "Patient", "practitioner": "Practitioner" },
"display_name": "Medication",
"verbose_name_plural": "Medications"
}GET /admin/models/{model_name}/
- Purpose: Get paginated list of records
-
Query Parameters:
-
page(integer, default: 1): Page number -
per_page(integer, default: 25, max: 100): Items per page -
search(string, optional): Search term
-
- Success Response (200):
{
"items": [...],
"total": 450,
"page": 1,
"per_page": 25,
"total_pages": 18
}GET /admin/models/{model_name}/{record_id}
- Purpose: Get a specific record by ID
- Success Response (200): Record object with all fields
POST /admin/models/{model_name}/
- Purpose: Create a new record
- Request Body: Model-specific fields
- Success Response (201): Created record object
PUT /admin/models/{model_name}/{record_id}
- Purpose: Update a record
- Note: Password fields cannot be updated through this endpoint
- Request Body: Fields to update
- Success Response (200): Updated record object
DELETE /admin/models/{model_name}/{record_id}
- Purpose: Delete a record
- Note: Protects against deleting last user or last admin
- Success Response (200):
{
"message": "medication record 123 deleted successfully",
"deleted_id": 123
}POST /admin/models/users/{user_id}/reset-password
- Purpose: Admin reset of any user's password
- Request Body:
{
"new_password": "NewSecurePass123!"
}- Validation: Min 6 chars, must contain letter and number
-
Success Response (200):
{"message": "Password reset successfully"}
Base path: /api/v1/admin/backup
POST /admin/backup/create-database
- Purpose: Create a database-only backup
- Request Body:
{
"description": "Weekly backup"
}- Success Response (200):
{
"id": 1,
"backup_type": "database",
"filename": "backup_db_2025-10-04_103000.sql",
"size_bytes": 5242880,
"status": "completed",
"created_at": "2025-10-04T10:30:00Z",
"description": "Weekly backup"
}POST /admin/backup/create-files
- Purpose: Create a files-only backup (uploads directory)
- Request Body: Same as database backup
- Success Response (200): Backup response object
POST /admin/backup/create-full
- Purpose: Create a full system backup (database + files)
- Request Body: Same as database backup
- Success Response (200): Backup response object
GET /admin/backup/
- Purpose: List all backup records
- Success Response (200):
{
"backups": [
{
"id": 1,
"backup_type": "full",
"filename": "backup_full_2025-10-04.zip",
"size_bytes": 15728640,
"status": "completed",
"file_exists": true,
"file_path": "/backups/backup_full_2025-10-04.zip",
"created_at": "2025-10-04T02:00:00Z"
}
],
"total": 5
}GET /admin/backup/{backup_id}/download
- Purpose: Download a backup file
- Success Response: File download (application/octet-stream)
-
Error Responses:
-
404: Backup or file not found
-
POST /admin/backup/{backup_id}/verify
- Purpose: Verify backup integrity
- Success Response (200):
{
"valid": true,
"backup_id": 1,
"verification_details": {...}
}DELETE /admin/backup/{backup_id}
- Purpose: Delete a backup record and file
- Success Response (200): Deletion confirmation
POST /admin/backup/cleanup
- Purpose: Clean up old backups based on retention policy
- Success Response (200): Cleanup results
POST /admin/backup/cleanup-orphaned
- Purpose: Clean up orphaned backup files
- Success Response (200): Cleanup results
POST /admin/backup/cleanup-all
- Purpose: Clean up old backups, orphaned files, and old trash
- Success Response (200): Combined cleanup results
GET /admin/backup/settings/retention
- Purpose: Get current retention settings
- Success Response (200):
{
"backup_retention_days": 30,
"trash_retention_days": 7,
"backup_min_count": 3,
"backup_max_count": 10,
"allow_user_registration": true
}POST /admin/backup/settings/retention
- Purpose: Update retention and admin settings
- Request Body:
{
"backup_retention_days": 45,
"trash_retention_days": 14,
"backup_min_count": 5,
"backup_max_count": 20,
"allow_user_registration": false
}- Success Response (200): Updated settings
GET /admin/backup/retention/stats
- Purpose: Get backup retention statistics
- Success Response (200): Statistics and cleanup preview
Base path: /api/v1/admin/restore
POST /admin/restore/upload
- Purpose: Upload an external backup file for restore
-
Content-Type:
multipart/form-data -
Supported Files:
.sql(database),.zip(files or full) - Success Response (200):
{
"success": true,
"message": "Backup file 'backup.zip' uploaded successfully",
"backup_id": 5,
"backup_type": "full",
"backup_size": 15728640,
"backup_description": "Uploaded backup"
}POST /admin/restore/preview/{backup_id}
- Purpose: Preview what will be affected by restore
- Success Response (200):
{
"backup_id": 5,
"backup_type": "full",
"backup_created": "2025-10-04T02:00:00Z",
"backup_size": 15728640,
"backup_description": "Full backup",
"warnings": ["This will replace all current data"],
"affected_data": {
"users": 150,
"patients": 145,
"medications": 450
}
}GET /admin/restore/confirmation-token/{backup_id}
- Purpose: Generate confirmation token for restore
- Success Response (200):
{
"backup_id": 5,
"confirmation_token": "abc123...",
"expires_at": "End of day (UTC)",
"warning": "This token allows irreversible restore operations. Use with caution."
}POST /admin/restore/execute/{backup_id}
- Purpose: Execute restore with confirmation token
- Request Body:
{
"confirmation_token": "abc123..."
}- Success Response (200):
{
"success": true,
"message": "Restore completed successfully",
"backup_id": 5,
"backup_type": "full",
"safety_backup_id": 6,
"restore_completed": "2025-10-04T10:30:00Z",
"warnings": null
}Base path: /api/v1/admin/bulk
POST /admin/bulk/delete
- Purpose: Delete multiple records at once
- Request Body:
{
"model_name": "medication",
"record_ids": [1, 2, 3, 4, 5]
}- Success Response (200):
{
"success": true,
"affected_records": 5,
"failed_records": [],
"message": "Successfully deleted 5 medication records"
}POST /admin/bulk/update
- Purpose: Update multiple records with same data
- Request Body:
{
"model_name": "medication",
"record_ids": [1, 2, 3],
"update_data": {
"status": "stopped"
}
}- Success Response (200):
{
"success": true,
"affected_records": 3,
"failed_records": [],
"message": "Successfully updated 3 medication records"
}Base path: /api/v1/admin/trash
GET /admin/trash/
- Purpose: List all files currently in trash
- Success Response (200): Array of trash file objects
POST /admin/trash/cleanup
- Purpose: Clean up old files based on retention policy
- Success Response (200):
{
"deleted_count": 15,
"freed_bytes": 52428800
}POST /admin/trash/restore
- Purpose: Restore a file from trash
-
Query Parameters:
-
trash_path(string, required): Path to file in trash -
restore_path(string, optional): Target restore path
-
- Success Response (200):
{
"status": "success",
"message": "File restored to original location"
}DELETE /admin/trash/permanently-delete
- Purpose: Permanently delete a file (cannot be recovered)
-
Query Parameters:
-
trash_path(string, required): Path to file in trash
-
- Success Response (200):
{
"status": "success",
"message": "File permanently deleted"
}Base path: /api/v1/admin/maintenance
GET /admin/maintenance/test-library/info
- Purpose: Get information about the current test library
- Success Response (200):
{
"version": "1.2.0",
"test_count": 4500,
"categories": {
"chemistry": 1200,
"hematology": 800,
"urinalysis": 500
}
}POST /admin/maintenance/test-library/reload
- Purpose: Reload the test library from disk
- Use Case: After updating test library JSON file
- Success Response (200):
{
"success": true,
"version": "1.2.1",
"test_count": 4520,
"message": "Test library reloaded successfully"
}POST /admin/maintenance/test-library/sync
- Purpose: Sync lab test components with the test library
- Request Body:
{
"force_all": false
}-
Note: If
force_allis true, re-processes all components. If false, only processes components without canonical names. - Success Response (200):
{
"success": true,
"components_processed": 1200,
"canonical_names_updated": 350,
"categories_updated": 120,
"message": "Successfully synced 1200 components"
}| Code | Meaning | Common Causes |
|---|---|---|
| AUTH_001 | Invalid credentials | Wrong username/password |
| AUTH_002 | Token expired | Session timeout |
| AUTH_003 | Insufficient permissions | Accessing restricted resource |
| VAL_001 | Validation failed | Invalid input data |
| RES_001 | Resource not found | Invalid ID or deleted resource |
| RES_002 | Resource conflict | Duplicate entry |
| RATE_001 | Rate limit exceeded | Too many requests |
Date Format: ISO 8601 (YYYY-MM-DD)
DateTime Format: ISO 8601 with timezone (YYYY-MM-DDTHH:MM:SSZ)
Phone Format: E.164 (+1234567890)
Email Format: RFC 5322
- Always use HTTPS in production
- Store tokens securely (never in localStorage for sensitive apps)
- Implement token refresh mechanism
- Handle 401 responses by redirecting to login
- Validate all user inputs on client side
- Never log sensitive patient data
- Implement CSRF protection for state-changing operations
- Current version: v1
- Breaking changes require new version
- Deprecated endpoints supported for 6 months
- Version specified in URL path (
/api/v1/)
For Support:
- GitHub Issues: github.com/afairgiant/MediKeep/issues
- API Documentation: http://localhost:8005/docs (Swagger UI)
- Developer Guide: docs/developer-guide/
Resources