-
-
Notifications
You must be signed in to change notification settings - Fork 29
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,
"reminder_enabled": true,
"reminder_times": ["08:00", "20:00"]
}-
Route values:
oral,injection,topical,intravenous,intramuscular,subcutaneous,inhalation,nasal,rectal,sublingual -
Status values:
active,stopped,on-hold,completed,cancelled -
Reminder fields:
reminder_enableddefaults tofalse.reminder_timesare facility-local"HH:MM"strings (max 12, no duplicates, sorted on save). Reminders fire only while the medication isactiveand the current date falls withineffective_period_start/effective_period_end(inclusive on both ends). Delivery uses the patient owner's enabled notification channels for themedication_reminder_dueevent. - 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
-
POST /medications/{medication_id}/reminders/test
- Authentication: Yes
-
Purpose: Fire a one-shot test reminder for this medication to verify the patient owner's notification channel setup. Test events are flagged
is_testand carry no scheduled time, so they are excluded from the scheduler's idempotency dedup. -
Authorization: Caller must have access to the medication (same check as
GET /medications/{id}). The notification is delivered to the patient owner's enabled channels, not the caller's. - Success Response (204): No content
-
Error Responses:
-
400— Reminders are not enabled for this medication -
404— Medication not found or not accessible to the caller -
422— No notification channel is enabled for themedication_reminder_dueevent type for the patient owner
-
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",
"standardized_vaccine_who_code": "XM1NL1"
}-
Optional fields:
-
standardized_vaccine_who_code(string): WHO PCMT code of the standardized vaccine the user picked from the autocomplete. The backend resolves this tostandardized_vaccine_id. Unknown codes are silently ignored (the record saves with NULL FK).
-
PUT /immunizations/{immunization_id}
- Purpose: Update an existing immunization record. Accepts the same fields as Create (all optional on update).
-
Optional fields:
-
standardized_vaccine_who_code(string or null): WHO PCMT code of the standardized vaccine the user picked from the autocomplete. The backend resolves this tostandardized_vaccine_id. Unknown codes are silently ignored (the record saves with NULL FK). Pass explicitnullto clear an existing link.
-
GET /immunizations/
GET /immunizations/patient/{patient_id}/upcoming
- Purpose: Get immunizations scheduled for the future
GET /immunizations/patient/{patient_id}/history
- Purpose: Returns immunizations enriched with disease components, suitable for the History view in the UI. Combined vaccines (e.g., DTaP) are expanded via the StandardizedVaccine library so a single record can be surfaced under each disease it covers.
- Authentication: Yes
-
Query Parameters:
-
start_date(string, optional, ISO date): include records withdate_administered >= start_date -
end_date(string, optional, ISO date): include records withdate_administered <= end_date
-
- Success Response (200):
{
"items": [
{
"id": 12,
"patient_id": 42,
"vaccine_name": "DTaP",
"vaccine_trade_name": "Daptacel",
"date_administered": "2024-03-15",
"dose_number": 4,
"lot_number": "ABC123",
"ndc_number": null,
"manufacturer": null,
"site": null,
"route": null,
"expiration_date": null,
"location": null,
"notes": null,
"practitioner_id": null,
"tags": [],
"standardized_vaccine_id": 10,
"components": ["Diphtheria", "Tetanus", "Pertussis"],
"is_combined": true,
"is_library_matched": true
}
],
"diseases_index": {
"Diphtheria": [12],
"Tetanus": [12],
"Pertussis": [12]
},
"unmatched_count": 0
}-
Response fields specific to this endpoint:
-
components: array of disease component names this immunization covers (empty for unmatched records) -
is_combined: true if the linked vaccine is a combination vaccine -
is_library_matched: true if the record was successfully matched to a library entry (either viastandardized_vaccine_idFK or case-insensitive name match againstStandardizedVaccine.vaccine_nameorcommon_names) -
diseases_index: mapping of disease name → list of immunization IDs covering that disease (only populated for matched records) -
unmatched_count: number of records that could not be resolved to a library entry
-
-
Status codes:
-
200— success (including empty result) -
403— patient access denied -
404— patient not found
-
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": "completed",
"labs_result": "normal",
"notes": "Routine checkup",
"tags": ["annual", "routine"]
}-
Status values:
ordered,in-progress,completed,cancelled -
Labs result values:
normal,abnormal,critical,high,low,borderline,inconclusive -
Test category values:
blood work,imaging,pathology,microbiology,chemistry,hematology,hepatology,immunology,genetics,cardiology,pulmonology,hearing,stomatology,other -
Test type values:
routine,urgent,stat,emergency,follow-up,screening - 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
Current builds use the shared Entity Files subsystem for lab result attachments. See Section 9 — Files & Attachments for the full reference; the lab-result-specific paths are listed here for convenience.
Base path: /api/v1/entity-files
GET /entity-files/lab-result/{lab_result_id}/files
- Purpose: Get all files attached to a lab result
- Authentication: Yes
- Success Response (200): Array of entity file objects
POST /entity-files/lab-result/{lab_result_id}/files
- Purpose: Upload a file to a lab result
- Authentication: Yes
-
Content-Type:
multipart/form-data -
Request Body:
-
file(required): File to upload -
description(optional): File description -
category(optional): Category label -
storage_backend(optional): Storage backend (local,paperless,papra)
-
- Max Size: 1GB
- Allowed Extensions: PDF, images, documents, medical imaging (DICOM, NIfTI), video, audio, archives
- Success Response (200): Created entity file object
GET /entity-files/files/{file_id}/view
GET /entity-files/files/{file_id}/download
DELETE /entity-files/files/{file_id}
- Purpose: Delete a specific file from a lab result
- Authentication: Yes
- Success Response (200): File operation result
Legacy endpoints: Earlier builds exposed
GET|POST /lab-results/{lab_result_id}/files,DELETE /lab-results/{lab_result_id}/files/{file_id}, and a/lab-result-files/...router. These still respond for backward compatibility but new integrations should prefer theentity-filesendpoints above.
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
GET /lab-test-components/lab-result/{lab_result_id}/components
- Purpose: List all test components for a lab result
- Authentication: Yes
-
Query Parameters:
-
skip(integer, default: 0): Pagination offset -
limit(integer, default: 100, max: 1000): Items per page -
category(string, optional): Filter by category -
status(string, optional): Filter by status
-
- Success Response (200): Array of component objects
POST /lab-test-components/lab-result/{lab_result_id}/components
- Purpose: Add a single test component to a lab result
- Authentication: Yes
- Request Body:
{
"lab_result_id": 1,
"test_name": "White Blood Cell Count",
"abbreviation": "WBC",
"canonical_test_name": "White Blood Cell Count",
"value": 7.5,
"unit": "x10*9/L",
"ref_range_text": "4.5 - 11.0",
"status": "normal",
"category": "hematology",
"result_type": "quantitative",
"display_order": 1,
"notes": "Optional notes"
}-
Result type values:
quantitative,qualitative -
Status values:
normal,abnormal,critical,high,low,borderline,inconclusive -
Note: For quantitative components,
valueandunitare required. For qualitative components, usequalitative_valueinstead. - Success Response (201): Created component object
POST /lab-test-components/lab-result/{lab_result_id}/components/bulk
- Purpose: Create multiple test components for a lab result in one request
- Authentication: Yes
-
Request Body:
{"lab_result_id": <id>, "components": [<component>, ...]} -
Success Response (201):
{"created_count": N, "components": [...], "errors": [...]}
GET /lab-test-components/components/{component_id}
- Authentication: Yes
- Success Response (200): Component object with associated lab result summary
PUT /lab-test-components/components/{component_id}
- Purpose: Update an existing test component (canonical mapping, ranges, status, etc.)
- Authentication: Yes
- Request Body: Same shape as create (all fields optional)
- Success Response (200): Updated component object
DELETE /lab-test-components/components/{component_id}
- Authentication: Yes
- Success Response (204): No content
GET /lab-test-components/lab-result/{lab_result_id}/statistics
- Purpose: Summary counts (normal/abnormal/critical, categories, etc.)
- Authentication: Yes
- Success Response (200): Statistics object
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/standardized-vaccines
Purpose: Search and autocomplete for standardized vaccines, backed by WHO PreQualVaccineType codes plus curated additions for common Western vaccines (Tdap booster, Shingles/RZV, MMRV, Twinrix, etc.). Powers the vaccine selector on the Immunization form. Free-text vaccine_name on immunization records is still accepted for entries not in this catalog.
GET /standardized-vaccines/search
- Purpose: Full-text + fuzzy search across vaccine_name, short_name, WHO code, and common brand names
-
Query Parameters:
-
query: Search term -
category: Filter by category (Viral,Bacterial,Combined,Toxoid,Parasitic,Other) -
limit: Max items (default 200, max 1000)
-
- Success Response (200):
{
"vaccines": [
{
"id": 42,
"who_code": "MeaslesMumpsandRubella",
"vaccine_name": "Measles, Mumps and Rubella",
"short_name": "MMR",
"category": "Combined",
"common_names": ["MMR", "M-M-R II", "Priorix"],
"is_combined": true,
"components": ["Measles", "Mumps", "Rubella"],
"default_manufacturer": null,
"is_common": true
}
],
"total": 1
}GET /standardized-vaccines/autocomplete
- Purpose: Autocomplete suggestions formatted for the frontend dropdown
-
Query Parameters:
-
query: Autocomplete query -
category: Optional category filter -
limit: Max suggestions (default 50, max 200)
-
- Success Response (200):
[
{
"value": "Measles, Mumps and Rubella (MMR)",
"label": "Measles, Mumps and Rubella",
"who_code": "MeaslesMumpsandRubella",
"short_name": "MMR",
"category": "Combined",
"is_combined": true,
"components": ["Measles", "Mumps", "Rubella"]
}
]GET /standardized-vaccines/common
-
Purpose: Frequently administered vaccines, ordered by
display_order - Success Response (200): Array of vaccine objects
GET /standardized-vaccines/by-category/{category}
- Purpose: All vaccines in a category
- Success Response (200): Array of vaccine objects
GET /standardized-vaccines/by-who-code/{who_code}
- Purpose: Look up by WHO PCMT PreQualVaccineType code
- Success Response (200): Vaccine object
GET /standardized-vaccines/by-name/{vaccine_name}
- Purpose: Case-insensitive exact-name lookup
- Success Response (200): Vaccine object
GET /standardized-vaccines/count
- Purpose: Total count of standardized vaccines in the catalog
- Success Response (200):
{
"category": null,
"count": 66
}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_id": 3,
"practice_id": 1,
"phone_number": "+1234567890",
"email": "dr.smith@clinic.com",
"website": "https://drsmith.com",
"rating": 4.5
}-
Notes:
specialty_idis required and must reference an existingmedical_specialtiesrow (see Medical Specialties section below for how to list or create them).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_id(integer, optional): Filter by specialty FK -
practice_id(integer, optional): Filter by practice
-
-
Response: Each practitioner includes
specialty(the resolved name from the FK),specialty_name(alias ofspecialty),practice_name, and timestamps.
GET /practitioners/{practitioner_id}
-
Response: Full practitioner details with
specialty/specialty_name(resolved from the FK) andpractice_name.
PUT /practitioners/{practitioner_id}
-
Request Body: Same fields as create, all optional.
specialty_idcan be changed to point at a differentMedicalSpecialty.
DELETE /practitioners/{practitioner_id}
GET /practitioners/search/by-name
-
Query Parameters:
-
name(string, required, min: 2): Search term
-
Base path: /api/v1/medical-specialties
Purpose: Lookup table of medical specialties referenced by the practitioners.specialty_id FK. Any authenticated user can list or quick-create specialties; full admin CRUD (update, deactivate, delete) is available via /api/v1/admin/models/medical_specialty/.
GET /medical-specialties/
- Auth: Any authenticated user.
- Response (200): Array of active specialty summaries for populating dropdowns.
[
{ "id": 3, "name": "Cardiology", "description": "Heart & cardiovascular system", "is_active": true },
{ "id": 5, "name": "Dermatology", "description": "Skin, hair & nails", "is_active": true }
]POST /medical-specialties/
- Auth: Any authenticated user. Rate-limited to 20 creates per hour per user.
- Request Body:
{
"name": "Pulmonology",
"description": "Lung & respiratory care"
}-
Response:
-
201 Created— new specialty inserted; body is the new row. -
200 OK— case-insensitive match onnamealready existed; body is the existing row. -
429 Too Many Requests— per-user rate limit exceeded;Retry-Afterheader set.
-
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_due",
"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_due",
"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_due", "lab_result_available", "appointment_reminder"],
"preferences": {
"medication_reminder_due": {"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_due",
"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_due",
"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
The shared Entity Files subsystem handles attachments across most medical entity types (lab results, medications, conditions, procedures, encounters, treatments, immunizations, vitals, allergies, injuries, etc.). Files can be stored locally or linked to external document backends (Paperless-ngx, Papra).
GET /entity-files/{entity_type}/{entity_id}/files
- Purpose: List files attached to an entity
- Authentication: Yes
- Success Response (200): Array of entity file objects
POST /entity-files/{entity_type}/{entity_id}/files
-
Content-Type:
multipart/form-data -
Request Body:
-
file(required): File to upload -
description(optional): File description -
category(optional): Category label -
storage_backend(optional): Storage backend (local,paperless,papra)
-
- Max Size: 1GB
- Allowed Extensions: PDF, images, documents, medical imaging (DICOM, NIfTI), video, audio, archives
- Success Response (200): Created entity file object
GET /entity-files/files/{file_id}
- Authentication: Yes
- Success Response (200): Entity file object
GET /entity-files/files/{file_id}/view
- Purpose: Stream file inline (e.g., PDF preview, image display)
- Authentication: Yes
GET /entity-files/files/{file_id}/download
-
Purpose: Download file with
Content-Disposition: attachment - Authentication: Yes
PUT /entity-files/files/{file_id}/metadata
- Authentication: Yes
-
Request Body: Updatable metadata fields (e.g.,
description,category) - Success Response (200): Updated entity file object
DELETE /entity-files/files/{file_id}
- Authentication: Yes
- Success Response (200): File operation result
POST /entity-files/files/batch-counts
- Purpose: Fetch attachment counts for many entities in one call (used by list views)
- Authentication: Yes
-
Request Body: Array of
{entity_type, entity_id}pairs -
Success Response (200): Array of
{entity_type, entity_id, file_count}objects
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