Treatment Plans API Endpoints
Treatment Plan CRUD
List Treatment Plans
GET /v1/treatment-plans?type=telerehab&status=active&scope=org&specialist_id=5&sort=-created_at&page=1&limit=25Query Parameters:
type(string) — Filter by type (telerehab, in_clinic)status(string) — Filter by status (draft, pending_approval, active, paused, completed, cancelled, expired)scope(string) — Filter by scope:global(org_id IS NULL),org(org_id = current),custom(created_for_patient_id IS NOT NULL),all(default)visibility(string) — Filter by visibility:internal,libraryspecialist_id(int) — Filter by prescribing specialistspecialty_id(int) — Filter by specialtycondition_tags(string[]) — Filter by condition tags (e.g.,?condition_tags=shoulder_pain,post_acl)published(boolean) — Filter by published statusq(string) — Search by name/description- Standard pagination:
page,limit,sort
Response:
{
"treatment_plans": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Post-ACL Rehab Weeks 1-4",
"description": "Progressive rehabilitation program for ACL reconstruction recovery",
"type": "telerehab",
"scope": "org",
"visibility": "library",
"condition_tags": ["post_acl", "knee_rehab"],
"created_from_id": null,
"created_for_patient_id": null,
"frequency_per_week": 3,
"duration_weeks": 4,
"total_sessions": 12,
"specialist": {
"id": 5,
"name": "Dr. Maria Kent"
},
"specialty": {
"id": 1,
"name": "Physiotherapy"
},
"requires_approval": true,
"post_session_form_template_id": 42,
"service_plan_id": null,
"status": "active",
"version": 2,
"published": true,
"sessions_count": 3,
"created_at": "2026-02-01T10:00:00Z",
"updated_at": "2026-02-14T15:30:00Z"
}
],
"total": 8,
"page": 1,
"limit": 25,
"total_pages": 1
}Get Treatment Plan Details
GET /v1/treatment-plans/{id}Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Post-ACL Rehab Weeks 1-4",
"description": "Progressive rehabilitation program...",
"type": "telerehab",
"frequency_per_week": 3,
"duration_weeks": 4,
"total_sessions": 12,
"specialist": {
"id": 5,
"name": "Dr. Maria Kent"
},
"specialty": {
"id": 1,
"name": "Physiotherapy"
},
"requires_approval": true,
"post_session_form_template": {
"id": 42,
"title": "Pain & Mobility Questionnaire"
},
"service_plan": null,
"status": "active",
"version": 2,
"published": true,
"sessions": [
{
"id": 1,
"session_number": 1,
"name": "Mobility Focus",
"description": "Gentle mobility exercises to restore range of motion",
"estimated_duration_minutes": 25,
"exercises": [
{
"id": 10,
"exercise_id": "660e8400-...",
"sort_order": 0,
"exercise": {
"id": "660e8400-...",
"name": "Quad Set",
"video_thumbnail_url": "https://...",
"difficulty": "beginner"
},
"mode": "reps",
"sets": 3,
"reps": 10,
"hold_seconds": null,
"duration_seconds": null,
"rest_between_sets_seconds": 30,
"rest_after_exercise_seconds": 60,
"notes": "Focus on controlled contraction"
},
{
"id": 11,
"exercise_id": "770e8400-...",
"sort_order": 1,
"exercise": {
"id": "770e8400-...",
"name": "Prone Hang",
"video_thumbnail_url": "https://...",
"difficulty": "beginner"
},
"mode": "duration",
"sets": 2,
"reps": null,
"hold_seconds": 60,
"duration_seconds": 60,
"rest_between_sets_seconds": 45,
"rest_after_exercise_seconds": 60,
"notes": "Let gravity extend the knee"
}
]
},
{
"id": 2,
"session_number": 2,
"name": "Strengthening Focus",
"description": "Progressive strengthening exercises",
"estimated_duration_minutes": 30,
"exercises": []
}
],
"created_by_user_id": 1,
"created_at": "2026-02-01T10:00:00Z",
"updated_at": "2026-02-14T15:30:00Z"
}Create Treatment Plan
POST /v1/treatment-plansRequest:
{
"name": "Post-ACL Rehab Weeks 1-4",
"description": "Progressive rehabilitation program for ACL reconstruction recovery",
"type": "telerehab",
"frequency_per_week": 3,
"duration_weeks": 4,
"specialist_id": 5,
"specialty_id": 1,
"requires_approval": true,
"post_session_form_template_id": 42,
"service_plan_id": null
}Backend:
- Creates plan in
draftstatus - Sets
version = 1,published = false - Validates specialist and specialty exist in org
Response: Treatment plan object (201 Created)
Update Treatment Plan
PUT /v1/treatment-plans/{id}Request: Same as create (partial updates allowed)
Validation:
- Can only update plans in
draftstatus - To modify a published plan: the update creates a new draft version on the existing plan
Response: Updated treatment plan object
Soft Delete Treatment Plan
DELETE /v1/treatment-plans/{id}Backend:
- Sets
deleted_at = NOW() - Active patient enrollments are NOT cancelled (they continue on their assigned version)
Response: 204 No Content
Treatment Plan Publishing & Versioning
Publish Treatment Plan
POST /v1/treatment-plans/{id}/publishBackend:
- Validates plan has at least 1 session with at least 1 exercise
- Creates a
treatment_plan_versionsrecord with full JSONB snapshot - Increments
treatment_plans.version - Sets
treatment_plans.published = true - If
requires_approval = false: setsstatus = 'active' - If
requires_approval = true: setsstatus = 'pending_approval'
Response:
{
"id": "550e8400-...",
"version": 2,
"status": "active",
"published": true,
"published_at": "2026-02-14T15:30:00Z"
}List Version History
GET /v1/treatment-plans/{id}/versionsResponse:
{
"versions": [
{
"id": 2,
"version": 2,
"name": "Post-ACL Rehab Weeks 1-4",
"type": "telerehab",
"frequency_per_week": 3,
"duration_weeks": 4,
"sessions_count": 3,
"created_by_user_id": 1,
"created_at": "2026-02-14T15:30:00Z"
},
{
"id": 1,
"version": 1,
"name": "Post-ACL Rehab Weeks 1-4",
"type": "telerehab",
"frequency_per_week": 3,
"duration_weeks": 2,
"sessions_count": 2,
"created_by_user_id": 1,
"created_at": "2026-02-01T10:00:00Z"
}
]
}Get Specific Version
GET /v1/treatment-plans/{id}/versions/{version}Response: Full version object with sessions_snapshot JSONB
Rollback to Version
POST /v1/treatment-plans/{id}/rollback/{version}Backend:
- Restores sessions from the version snapshot
- Creates a NEW version (increments version counter)
- Does not delete the current version
Response: Updated treatment plan object with new version number
Clone Treatment Plan
POST /v1/treatment-plans/{id}/cloneRequest:
{
"name": "Jane Doe - Post-ACL Rehab Weeks 1-4",
"created_for_patient_id": 123
}Backend:
- Copies plan, sessions, and session exercises into the current org
- Sets
created_from_idto the source plan's ID (tracks lineage) - If
created_for_patient_idprovided: creates as custom (specialist-for-patient) - Resets to
draftstatus,version = 1,visibility = 'internal' - Works across scopes: clone global → org, clone org → custom, clone org → org
Permissions: admin, specialist
Response: New treatment plan object (201 Created)
Promote Treatment Plan
POST /v1/treatment-plans/{id}/promoteRequest:
{
"visibility": "library"
}Backend:
- Clears
created_for_patient_id(makes plan reusable, no longer patient-specific) - Optionally sets
visibilityto'library'(patient-browseable) or keeps'internal' - Plan stays in the same org
- Only works on custom plans (created_for_patient_id IS NOT NULL)
Permissions: admin only
Response: Updated treatment plan object
Treatment Plan Library (Patient-Facing)
Browse Library
GET /v1/treatment-plans/library?condition_tags=shoulder_pain&type=telerehab&sort=-created_at&page=1&limit=25Query Parameters:
condition_tags(string[]) — Filter by condition/pain areatype(string) — Filter by type (telerehab, in_clinic)specialty_id(int) — Filter by specialtyq(string) — Search by name/description- Standard pagination
Backend:
- Returns plans where
published = TRUE AND visibility = 'library' - Includes both global plans (
organization_id IS NULL) and org plans (organization_id = current_org) - RLS handles scope automatically
Permissions: patient with active service plan that has library_access = TRUE
Response:
{
"treatment_plans": [
{
"id": "550e8400-...",
"name": "Post-ACL Rehab Weeks 1-4",
"description": "Progressive rehabilitation program...",
"type": "telerehab",
"scope": "global",
"condition_tags": ["post_acl", "knee_rehab"],
"frequency_per_week": 3,
"duration_weeks": 4,
"total_sessions": 12,
"specialty": {
"id": 1,
"name": "Physiotherapy"
},
"sessions_count": 3,
"difficulty_level": "beginner"
}
],
"total": 24,
"page": 1,
"limit": 25,
"total_pages": 1
}Self-Assign Plan
POST /v1/treatment-plans/{id}/self-assignRequest:
{
"start_date": "2026-02-17"
}Backend:
- Verifies patient has active
patient_service_planwithlibrary_access = TRUE - Verifies plan is
published = TRUE AND visibility = 'library' - Creates
patient_treatment_planwith:self_assigned = TRUEassigned_by_specialist_id = NULLstatus = 'active'(no approval needed for self-assigned)- Latest published version frozen
- Links to patient's active
patient_service_plan_id - Fires
treatment_plan.assignedautomation trigger
Permissions: patient (own only, with library access)
Response: Patient treatment plan object (201 Created)
Treatment Plan Sessions
List Sessions
GET /v1/treatment-plans/{id}/sessionsResponse:
{
"sessions": [
{
"id": 1,
"session_number": 1,
"name": "Mobility Focus",
"description": "Gentle mobility exercises",
"estimated_duration_minutes": 25,
"exercises_count": 4
}
]
}Add Session
POST /v1/treatment-plans/{id}/sessionsRequest:
{
"session_number": 3,
"name": "Balance & Control",
"description": "Balance and proprioception exercises",
"estimated_duration_minutes": 20
}Validation: Plan must be in draft status
Response: Session object (201 Created)
Update Session
PUT /v1/treatment-plans/{planId}/sessions/{sessionId}Response: Updated session object
Delete Session
DELETE /v1/treatment-plans/{planId}/sessions/{sessionId}Backend: Cascades to session exercises. Renumbers subsequent sessions.
Response: 204 No Content
Reorder Sessions
PUT /v1/treatment-plans/{planId}/sessions/reorderRequest:
{
"session_ids": [3, 1, 2]
}Response: Updated sessions list with new session_number values
Session Exercises
List Exercises in Session
GET /v1/treatment-plan-sessions/{sessionId}/exercisesResponse:
{
"exercises": [
{
"id": 10,
"exercise_id": "660e8400-...",
"sort_order": 0,
"exercise": {
"id": "660e8400-...",
"name": "Quad Set",
"video_url": "https://...",
"video_thumbnail_url": "https://...",
"difficulty": "beginner",
"estimated_duration_seconds": 45
},
"mode": "reps",
"sets": 3,
"reps": 10,
"hold_seconds": null,
"duration_seconds": null,
"rest_between_sets_seconds": 30,
"rest_after_exercise_seconds": 60,
"notes": "Focus on controlled contraction"
}
]
}Add Exercise to Session
POST /v1/treatment-plan-sessions/{sessionId}/exercisesRequest:
{
"exercise_id": "660e8400-...",
"sort_order": 0,
"mode": "reps",
"sets": 3,
"reps": 10,
"rest_between_sets_seconds": 30,
"rest_after_exercise_seconds": 60,
"notes": "Focus on controlled contraction"
}Validation:
- Exercise must exist and be published (or draft for admin)
- Plan must be in
draftstatus - Validates mode-specific fields (reps requires
reps, duration requireshold_seconds/duration_seconds)
Response: Session exercise object (201 Created)
Update Exercise Config
PUT /v1/treatment-plan-sessions/{sessionId}/exercises/{exerciseId}Request: Same as add (partial updates allowed)
Response: Updated session exercise object
Remove Exercise from Session
DELETE /v1/treatment-plan-sessions/{sessionId}/exercises/{exerciseId}Response: 204 No Content
Reorder Exercises
PUT /v1/treatment-plan-sessions/{sessionId}/exercises/reorderRequest:
{
"exercise_ids": [11, 10, 12]
}Response: Updated exercises list with new sort_order values
Patient Treatment Plans (Enrollment)
List Patient Enrollments
GET /v1/patient-treatment-plans?patient_id=123&status=activeQuery Parameters:
patient_id(int) — Filter by patienttreatment_plan_id(uuid) — Filter by planstatus(string) — Filter by statustype(string) — Filter by plan type (telerehab, in_clinic)- Standard pagination
Response:
{
"patient_treatment_plans": [
{
"id": 42,
"patient": {
"id": 123,
"name": "Jane Doe"
},
"treatment_plan": {
"id": "550e8400-...",
"name": "Post-ACL Rehab Weeks 1-4",
"type": "telerehab"
},
"treatment_plan_version": 2,
"assigned_by_specialist": {
"id": 5,
"name": "Dr. Maria Kent"
},
"start_date": "2026-02-10",
"end_date": "2026-03-10",
"frequency_per_week": 3,
"sessions_total": 12,
"sessions_completed": 5,
"sessions_skipped": 1,
"completion_percentage": 41.67,
"status": "active",
"approved_at": "2026-02-10T10:00:00Z",
"created_at": "2026-02-09T14:00:00Z"
}
]
}Assign Plan to Patient
POST /v1/patient-treatment-plansRequest:
{
"patient_id": 123,
"treatment_plan_id": "550e8400-...",
"start_date": "2026-02-10",
"assigned_by_specialist_id": 5,
"patient_service_plan_id": null
}Backend:
- Verifies patient has active
patient_service_planwithtelerehab_access = TRUE(for telerehab plans) - Assigns the latest published version of the plan
- Freezes
sessions_total,frequency_per_weekfrom plan version - Calculates
end_date = start_date + (duration_weeks * 7) - Sets
self_assigned = FALSE - If
treatment_plan.requires_approval = true: setsstatus = 'pending_approval' - If
treatment_plan.requires_approval = false: setsstatus = 'active' - Fires
treatment_plan.assignedautomation trigger
Response: Patient treatment plan object (201 Created)
Get Enrollment Details
GET /v1/patient-treatment-plans/{id}Response: Full enrollment object with plan details, progress, and session history
Approve Patient Plan
POST /v1/patient-treatment-plans/{id}/approveBackend:
- Validates
status = 'pending_approval' - Sets
status = 'active',approved_at = NOW(),approved_by_user_id = current user - Fires
treatment_plan.activatedautomation trigger
Response: Updated enrollment object
Change Enrollment Status
PUT /v1/patient-treatment-plans/{id}/statusRequest:
{
"status": "paused",
"reason": "Patient traveling for 2 weeks"
}Allowed transitions: See lifecycle.md
Response: Updated enrollment object
List Patient's Plans
GET /v1/patients/{patientId}/treatment-plans?status=activeConvenience endpoint for viewing all plans for a specific patient.
Response: Same as list patient enrollments, filtered by patient
Get Today's Session
GET /v1/patients/{patientId}/treatment-plans/todayBackend:
- Finds active
patient_treatment_plansfor the patient - Determines which session template to present next based on:
- Sessions already completed
- Current week number
- Plan rotation pattern (sessions cycle: session 1, 2, 3, 1, 2, 3, ...)
- Returns the session template with exercises and full details
Response:
{
"patient_treatment_plan_id": 42,
"treatment_plan": {
"id": "550e8400-...",
"name": "Post-ACL Rehab Weeks 1-4"
},
"next_session": {
"session_number": 2,
"name": "Strengthening Focus",
"estimated_duration_minutes": 30,
"exercises": [
{
"exercise_id": "770e8400-...",
"exercise": {
"name": "Straight Leg Raise",
"video_url": "https://...",
"video_thumbnail_url": "https://...",
"instructions": [...]
},
"mode": "reps",
"sets": 3,
"reps": 10,
"rest_between_sets_seconds": 30,
"rest_after_exercise_seconds": 60,
"notes": "Keep knee fully extended"
}
]
},
"progress": {
"sessions_completed": 5,
"sessions_total": 12,
"current_week": 2,
"total_weeks": 4,
"completion_percentage": 41.67
}
}Patient Session Execution
Start Session
POST /v1/patient-treatment-plans/{id}/sessions/{sessionNumber}/startRequest:
{
"pain_level_before": 6
}Backend:
- Creates
patient_session_completionsrecord withstatus = 'in_progress' - Sets
started_at = NOW() - Sets
exercises_totalfrom session template - Pre-creates
patient_exercise_logsentries for each exercise (with prescribed params)
Response:
{
"session_completion_id": 789,
"session_number": 2,
"status": "in_progress",
"started_at": "2026-02-16T09:00:00Z",
"exercises_total": 3,
"exercises": [
{
"exercise_log_id": 100,
"exercise_id": "770e8400-...",
"sort_order": 0,
"exercise": {
"name": "Straight Leg Raise",
"video_url": "https://...",
"instructions": [...]
},
"prescribed_mode": "reps",
"prescribed_sets": 3,
"prescribed_reps": 10,
"rest_between_sets_seconds": 30,
"rest_after_exercise_seconds": 60,
"notes": "Keep knee fully extended"
}
]
}Update Exercise Log (During Session)
PUT /v1/patient-exercise-logs/{id}Request:
{
"actual_sets": 3,
"actual_reps": 10,
"video_watched": true,
"video_watch_seconds": 38,
"video_watch_percentage": 90.48,
"started_at": "2026-02-16T09:02:00Z",
"completed_at": "2026-02-16T09:05:30Z"
}Response: Updated exercise log object
Skip Exercise
PUT /v1/patient-exercise-logs/{id}Request:
{
"skipped": true,
"skip_reason": "Too much pain in left knee"
}Response: Updated exercise log with skipped = true
Complete Session
POST /v1/patient-session-completions/{id}/completeRequest:
{
"pain_level_after": 4,
"perceived_difficulty": 3,
"notes": "Felt good overall, mild discomfort in last exercise"
}Backend:
- Sets
status = 'completed',completed_at = NOW() - Calculates
duration_secondsfrom started_at to now - Counts
exercises_completedandexercises_skippedfrom exercise logs - Increments
patient_treatment_plans.sessions_completed - If
sessions_completed >= sessions_total: transitions enrollment tocompleted - Creates post-session form instance from
post_session_form_template_id(if configured) - Fires
treatment_plan.session_completedautomation trigger - If plan completed: fires
treatment_plan.completedautomation trigger
Response:
{
"id": 789,
"session_number": 2,
"status": "completed",
"started_at": "2026-02-16T09:00:00Z",
"completed_at": "2026-02-16T09:28:00Z",
"duration_seconds": 1680,
"exercises_total": 3,
"exercises_completed": 3,
"exercises_skipped": 0,
"pain_level_before": 6,
"pain_level_after": 4,
"perceived_difficulty": 3,
"post_session_form_id": 456,
"plan_progress": {
"sessions_completed": 6,
"sessions_total": 12,
"completion_percentage": 50.0
}
}Skip Session
POST /v1/patient-session-completions/{id}/skipRequest:
{
"reason": "Not feeling well today"
}Backend:
- Sets
status = 'skipped' - Increments
patient_treatment_plans.sessions_skipped - Does NOT increment
sessions_completed
Response: Updated session completion object
Get Session Completion Details
GET /v1/patient-session-completions/{id}Response: Full session completion with exercise logs
Exercise Logs
List Exercise Logs for Session
GET /v1/patient-session-completions/{id}/exercise-logsResponse:
{
"exercise_logs": [
{
"id": 100,
"exercise_id": "770e8400-...",
"exercise": {
"name": "Straight Leg Raise",
"video_thumbnail_url": "https://..."
},
"sort_order": 0,
"started_at": "2026-02-16T09:02:00Z",
"completed_at": "2026-02-16T09:05:30Z",
"prescribed_mode": "reps",
"prescribed_sets": 3,
"prescribed_reps": 10,
"actual_sets": 3,
"actual_reps": 10,
"video_watched": true,
"video_watch_seconds": 38,
"video_watch_percentage": 90.48,
"pose_data_captured": true,
"pose_accuracy_score": 85.50,
"skipped": false
}
]
}Progress & Analytics
Get Plan Progress
GET /v1/patient-treatment-plans/{id}/progressResponse:
{
"patient_treatment_plan_id": 42,
"sessions_total": 12,
"sessions_completed": 6,
"sessions_skipped": 1,
"sessions_remaining": 5,
"completion_percentage": 50.0,
"current_week": 2,
"total_weeks": 4,
"start_date": "2026-02-10",
"end_date": "2026-03-10",
"days_remaining": 22,
"streak": {
"current": 3,
"best": 5
},
"pain_trend": [
{"session": 1, "before": 7, "after": 5},
{"session": 2, "before": 6, "after": 4},
{"session": 3, "before": 6, "after": 4},
{"session": 4, "before": 5, "after": 3},
{"session": 5, "before": 5, "after": 3},
{"session": 6, "before": 4, "after": 3}
],
"average_session_duration_seconds": 1750,
"average_completion_rate": 95.0,
"average_video_watch_percentage": 88.5
}Get Detailed Analytics (Specialist/Admin)
GET /v1/patient-treatment-plans/{id}/analyticsResponse:
{
"patient_treatment_plan_id": 42,
"sessions": [
{
"session_number": 1,
"completions": [
{
"completed_at": "2026-02-10T09:30:00Z",
"duration_seconds": 1680,
"exercises_completed": 4,
"exercises_skipped": 0,
"pain_level_before": 7,
"pain_level_after": 5,
"perceived_difficulty": 4
}
]
}
],
"exercise_performance": [
{
"exercise_id": "770e8400-...",
"exercise_name": "Straight Leg Raise",
"times_performed": 6,
"times_skipped": 0,
"average_completion_rate": 100.0,
"average_video_watch_percentage": 92.3,
"average_pose_accuracy": 85.5,
"trend": "improving"
}
],
"adherence": {
"weeks": [
{"week": 1, "expected": 3, "completed": 3, "rate": 100.0},
{"week": 2, "expected": 3, "completed": 2, "rate": 66.7}
]
}
}