Skip to content

Services API Endpoints

Service Catalog

List Services

http
GET /v1/services?published=true&is_public=true&category=consultation

Query Parameters:

  • published (boolean) - Filter by published status
  • is_public (boolean) - Filter patient-visible services
  • category (string) - Filter by category (consultation, therapy, examination, procedure)
  • specialty_id (int) - Filter by specialty

Response:

json
{
  "services": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Mobility Evaluation",
      "slug": "mobility-evaluation",
      "description": "Comprehensive mobility assessment...",
      "category": "consultation",
      "specialty": {
        "id": 1,
        "name": "Physiotherapy"
      },
      "duration_minutes": 30,
      "buffer_minutes": 5,
      "base_price": 200.00,
      "currency": "RON",
      "is_addon": false,
      "is_public": true,
      "published": true,
      "cover_url": "https://...",
      "video_url": "https://..."
    }
  ]
}

Get Service Details

http
GET /v1/services/{id}

Response:

json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Mobility Evaluation",
  "slug": "mobility-evaluation",
  "description": "...",
  "category": "consultation",
  "specialty_id": 1,
  "duration_minutes": 30,
  "buffer_minutes": 5,
  "base_price": 200.00,
  "currency": "RON",
  "is_addon": false,
  "is_public": true,
  "published": true,
  "cover_url": "https://...",
  "video_url": "https://...",
  "forms": [
    {
      "form_template_id": 10,
      "form_type": "survey",
      "sort_order": 1,
      "template": {
        "id": 10,
        "name": "Mobility Survey"
      }
    }
  ],
  "specialists": [
    {
      "specialist_id": 1,
      "name": "John Doe",
      "priority": 1,
      "custom_price": null,
      "is_active": true
    }
  ],
  "attachments": [
    {
      "id": 42,
      "file_url": "https://...",
      "file_name": "consent-form.pdf",
      "file_type": "application/pdf",
      "file_size": 245678
    }
  ]
}

Create Service (Admin)

http
POST /v1/services

Request:

json
{
  "name": "Mobility Evaluation",
  "slug": "mobility-evaluation",
  "description": "Comprehensive mobility assessment",
  "category": "consultation",
  "specialty_id": 1,
  "duration_minutes": 30,
  "buffer_minutes": 5,
  "base_price": 200.00,
  "currency": "RON",
  "is_addon": false,
  "is_public": true,
  "published": true,
  "cover_url": "https://...",
  "video_url": "https://..."
}

Response: Service object (201 Created)

Update Service (Admin)

http
PUT /v1/services/{id}

Request: Same as create (partial updates allowed)

Response: Updated service object

Delete Service (Admin)

http
DELETE /v1/services/{id}

Response: 204 No Content


Service Specialists

List Specialists for Service

http
GET /v1/services/{id}/specialists

Response:

json
{
  "specialists": [
    {
      "specialist_id": 1,
      "specialist": {
        "id": 1,
        "name": "John Doe",
        "specialty": "Physiotherapy",
        "scheduling_active": true
      },
      "is_active": true,
      "custom_price": null,
      "created_at": "2026-02-01T10:00:00Z"
    }
  ]
}

Assign Specialist to Service (Admin)

http
POST /v1/services/{id}/specialists

Request:

json
{
  "specialist_id": 1,
  "is_active": true,
  "custom_price": 180.00  // Optional override
}

Response: 201 Created

Remove Specialist from Service (Admin)

http
DELETE /v1/services/{id}/specialists/{specialist_id}

Response: 204 No Content


Service Forms

Attach Form Template to Service (Admin)

http
POST /v1/services/{id}/forms

Request:

json
{
  "form_template_id": 10,
  "form_type": "survey",
  "sort_order": 1
}

Response: 201 Created

Remove Form from Service (Admin)

http
DELETE /v1/services/{id}/forms/{form_template_id}

Response: 204 No Content


Service Attachments

Upload Attachment to Service (Admin)

http
POST /v1/services/{id}/attachments
Content-Type: multipart/form-data

Request:

file: [binary]

Response:

json
{
  "id": 42,
  "service_id": "550e8400-e29b-41d4-a716-446655440000",
  "file_url": "services/550e8400.../consent-form.pdf",
  "file_name": "consent-form.pdf",
  "file_type": "application/pdf",
  "file_size": 245678,
  "created_at": "2026-02-01T10:00:00Z"
}

Delete Attachment (Admin)

http
DELETE /v1/services/{id}/attachments/{attachment_id}

Response: 204 No Content


Service Plans

List Service Plans

http
GET /v1/service-plans?published=true

Query Parameters:

  • published (boolean) - Filter by published status
  • billing_model (string) - Filter by billing model (session_based, time_based, hybrid)
  • service_id (uuid) - Filter by linked service
  • telerehab_access (boolean) - Filter plans with telerehab access
  • library_access (boolean) - Filter plans with library access

Response:

json
{
  "service_plans": [
    {
      "id": 1,
      "name": "Kinetotherapy 10 Sessions",
      "service": {
        "id": "...",
        "name": "Kinetotherapy",
        "duration_minutes": 30,
        "base_price": 100.00
      },
      "billing_model": "session_based",
      "sessions_total": 10,
      "validity_days": 90,
      "access_months": null,
      "telerehab_access": false,
      "library_access": false,
      "total_price": 1000.00,
      "currency": "RON",
      "is_published": true,
      "bundled_products_count": 0,
      "created_at": "2026-02-01T10:00:00Z"
    },
    {
      "id": 2,
      "name": "Cervical Recovery Kit",
      "service": null,
      "billing_model": "time_based",
      "sessions_total": null,
      "validity_days": null,
      "access_months": null,
      "telerehab_access": true,
      "library_access": true,
      "total_price": 89.00,
      "currency": "RON",
      "is_published": true,
      "bundled_products_count": 1,
      "created_at": "2026-02-10T10:00:00Z"
    }
  ]
}

Get Service Plan Details

http
GET /v1/service-plans/{id}

Response: Service plan object with full service details

Create Service Plan (Admin)

http
POST /v1/service-plans

Request (session_based):

json
{
  "name": "Kinetotherapy 10 Sessions",
  "service_id": "550e8400-e29b-41d4-a716-446655440000",
  "billing_model": "session_based",
  "sessions_total": 10,
  "validity_days": 90,
  "total_price": 1000.00,
  "is_published": true
}

Request (time_based with product bundle):

json
{
  "name": "Cervical Recovery Kit",
  "billing_model": "time_based",
  "access_months": null,
  "telerehab_access": true,
  "library_access": true,
  "total_price": 89.00,
  "is_published": true
}

Request (hybrid):

json
{
  "name": "Recovery Package",
  "service_id": "550e8400-e29b-41d4-a716-446655440000",
  "billing_model": "hybrid",
  "sessions_total": 10,
  "validity_days": 120,
  "access_months": 1,
  "telerehab_access": true,
  "total_price": 1500.00,
  "is_published": true
}

Response: Service plan object (201 Created)

Update Service Plan (Admin)

http
PUT /v1/service-plans/{id}

Request: Same as create

Response: Updated service plan object

Delete Service Plan (Admin)

http
DELETE /v1/service-plans/{id}

Response: 204 No Content


Patient Service Plans

List Patient's Enrolled Programs

http
GET /v1/patient-service-plans?patient_id=123&status=active

Query Parameters:

  • patient_id (int) - Filter by patient
  • status (string) - Filter by status (active, completed, expired, cancelled)

Response:

json
{
  "patient_service_plans": [
    {
      "id": 42,
      "patient_id": 123,
      "service_plan": {
        "id": 1,
        "name": "Kinetotherapy 10 Sessions",
        "billing_model": "session_based",
        "telerehab_access": false,
        "library_access": false,
        "service": {
          "name": "Kinetotherapy"
        }
      },
      "sessions_total": 10,
      "sessions_completed": 3,
      "sessions_cancelled": 0,
      "sessions_remaining": 7,
      "access_starts_at": null,
      "access_expires_at": null,
      "status": "active",
      "enrolled_at": "2026-02-01T10:00:00Z",
      "expires_at": "2026-05-01T10:00:00Z"
    }
  ]
}

Get Program Progress

http
GET /v1/patient-service-plans/{id}

Response:

json
{
  "id": 42,
  "patient_id": 123,
  "patient": {
    "id": 123,
    "name": "Jane Doe"
  },
  "service_plan": {
    "id": 1,
    "name": "Kinetotherapy 10 Sessions",
    "service": {
      "id": "...",
      "name": "Kinetotherapy",
      "duration_minutes": 30
    },
    "total_price": 1000.00
  },
  "sessions_total": 10,
  "sessions_completed": 3,
  "sessions_cancelled": 0,
  "sessions_remaining": 7,
  "status": "active",
  "enrolled_at": "2026-02-01T10:00:00Z",
  "expires_at": "2026-05-01T10:00:00Z",
  "created_at": "2026-02-01T10:00:00Z",
  "updated_at": "2026-02-14T15:30:00Z"
}

Get Program's Appointment History

http
GET /v1/patient-service-plans/{id}/appointments

Response:

json
{
  "appointments": [
    {
      "id": 456,
      "plan_session_number": 1,
      "started_at": "2026-02-05T10:00:00Z",
      "status": "done",
      "specialist": {
        "name": "John Doe"
      }
    },
    {
      "id": 457,
      "plan_session_number": 2,
      "started_at": "2026-02-08T10:00:00Z",
      "status": "done",
      "specialist": {
        "name": "John Doe"
      }
    },
    {
      "id": 458,
      "plan_session_number": 3,
      "started_at": "2026-02-12T10:00:00Z",
      "status": "upcoming",
      "specialist": {
        "name": "John Doe"
      }
    }
  ]
}

Enroll Patient in Program

http
POST /v1/patient-service-plans

Request:

json
{
  "patient_id": 123,
  "service_plan_id": 1
}

Backend:

  • Copies sessions_total from service_plan (frozen at enrollment; NULL for pure time_based)
  • For session_based: calculates expires_at = NOW() + service_plan.validity_days
  • For time_based/hybrid: sets access_starts_at = NOW(), access_expires_at = NOW() + access_months (NULL access_months = lifetime)
  • Sets status = 'active', sessions_completed = 0
  • If plan has bundled products (service_plan_products): auto-creates patient_product_orders for each bundled product with status = 'pending'

Response:

json
{
  "id": 42,
  "patient_id": 123,
  "service_plan_id": 1,
  "sessions_total": null,
  "sessions_completed": 0,
  "access_starts_at": "2026-02-17T10:00:00Z",
  "access_expires_at": null,
  "status": "active",
  "enrolled_at": "2026-02-17T10:00:00Z",
  "product_orders": [
    {
      "id": 1,
      "product": {
        "id": "...",
        "name": "Cervical Pillow"
      },
      "quantity": 1,
      "status": "pending"
    }
  ]
}

Cancel Program

http
PATCH /v1/patient-service-plans/{id}/cancel

Request:

json
{
  "reason": "Patient requested cancellation"
}

Response: Updated patient service plan with status = 'cancelled'


Products

List Products

http
GET /v1/products?is_active=true

Response:

json
{
  "products": [
    {
      "id": "...",
      "name": "Elastic Band",
      "slug": "elastic-band",
      "description": "Resistance band for exercises",
      "price": 40.00,
      "currency": "RON",
      "is_active": true,
      "created_at": "2026-02-01T10:00:00Z"
    }
  ]
}

Create Product (Admin)

http
POST /v1/products

Request:

json
{
  "name": "Elastic Band",
  "slug": "elastic-band",
  "description": "Resistance band for exercises",
  "price": 40.00,
  "currency": "RON",
  "is_active": true
}

Response: Product object (201 Created)

Update Product (Admin)

http
PUT /v1/products/{id}

Request: Same as create

Response: Updated product object

Delete Product (Admin)

http
DELETE /v1/products/{id}

Response: 204 No Content


Service Plan Bundled Products

List Bundled Products

http
GET /v1/service-plans/{id}/products

Response:

json
{
  "bundled_products": [
    {
      "id": 1,
      "product": {
        "id": "550e8400-...",
        "name": "Cervical Pillow",
        "price": 45.00,
        "currency": "RON"
      },
      "quantity": 1,
      "created_at": "2026-02-01T10:00:00Z"
    }
  ]
}

Add Product to Bundle (Admin)

http
POST /v1/service-plans/{id}/products

Request:

json
{
  "product_id": "550e8400-...",
  "quantity": 1
}

Response: Bundled product object (201 Created)

Update Bundled Product Quantity (Admin)

http
PUT /v1/service-plans/{id}/products/{product_id}

Request:

json
{
  "quantity": 2
}

Response: Updated bundled product object

Remove Product from Bundle (Admin)

http
DELETE /v1/service-plans/{id}/products/{product_id}

Response: 204 No Content


Patient Product Orders

List Product Orders

http
GET /v1/patient-product-orders?patient_id=123&status=pending

Query Parameters:

  • patient_id (int) - Filter by patient
  • status (string) - Filter by status (pending, confirmed, shipped, delivered, cancelled)
  • source (string) - Filter by source: bundled (has patient_service_plan_id) or standalone (no plan link)

Response:

json
{
  "product_orders": [
    {
      "id": 1,
      "patient": {
        "id": 123,
        "name": "Jane Doe"
      },
      "product": {
        "id": "550e8400-...",
        "name": "Cervical Pillow",
        "price": 45.00
      },
      "patient_service_plan_id": 42,
      "quantity": 1,
      "unit_price": null,
      "status": "pending",
      "ordered_at": "2026-02-17T10:00:00Z",
      "shipped_at": null,
      "delivered_at": null,
      "tracking_number": null
    }
  ]
}

Create Standalone Product Order (Specialist, Admin)

http
POST /v1/patient-product-orders

Request:

json
{
  "patient_id": 123,
  "product_id": "550e8400-...",
  "quantity": 1,
  "notes": "Recommended during consultation"
}

Backend:

  • Sets patient_service_plan_id = NULL (standalone)
  • Freezes unit_price from products.price at time of order

Response: Product order object (201 Created)

Update Order Status (Specialist, Admin)

http
PUT /v1/patient-product-orders/{id}/status

Request:

json
{
  "status": "shipped",
  "tracking_number": "RO123456789"
}

Valid transitions:

  • pendingconfirmed
  • confirmedshipped (requires tracking_number)
  • shippeddelivered
  • pendingcancelled
  • confirmedcancelled

Backend: Sets the corresponding timestamp (confirmed_at, shipped_at, delivered_at)

Response: Updated product order object

Cancel Product Order (Specialist, Admin)

http
POST /v1/patient-product-orders/{id}/cancel

Request:

json
{
  "reason": "Patient no longer needs the product"
}

Response: Updated product order with status = 'cancelled'


Direct Service Registration (No Slot)

For services that don't require slot selection (e.g., "Second Opinion" - patient uploads docs, specialist reviews later):

http
POST /v1/services/{id}/register

Request:

json
{
  "contact_name": "John Doe",
  "contact_email": "[email protected]",
  "contact_phone": "+40123456789"
}

Backend:

  • Creates appointment with service_id set, calendar_id = NULL, started_at = NULL
  • Status: booked
  • Customer service later assigns specialist and time, then onboards patient

Response:

json
{
  "appointment_id": 789,
  "service": {
    "name": "Second Opinion"
  },
  "status": "booked",
  "message": "Thank you! We will contact you shortly."
}