Skip to content

Segments API

Endpoints

Segment CRUD

List Segments

http
GET /v1/segments

Response:

json
{
  "segments": [
    {
      "id": 1,
      "organization_id": 10,
      "name": "High Pain Patients",
      "description": "Patients reporting big pain in intake survey",
      "rules": [
        {
          "source": "form",
          "template_id": 5,
          "custom_field_id": 11,
          "op": "eq",
          "value": "Big pain"
        }
      ],
      "match_mode": "all",
      "version": 1,
      "created_at": "2025-01-15T10:00:00Z",
      "updated_at": "2025-01-15T10:00:00Z"
    }
  ]
}

Get Segment

http
GET /v1/segments/{id}

Response:

json
{
  "id": 1,
  "organization_id": 10,
  "name": "High Pain Patients",
  "description": "Patients reporting big pain in intake survey",
  "rules": [...],
  "match_mode": "all",
  "version": 1,
  "member_count": 42,
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-15T10:00:00Z"
}

Create Segment

http
POST /v1/segments

Request:

json
{
  "name": "Bucharest High Pain Seniors",
  "description": "Complex target group for outreach campaign",
  "match_mode": "all",
  "rules": [
    {
      "source": "profile",
      "custom_field_id": 10,
      "op": "eq",
      "value": "Bucharest"
    },
    {
      "source": "profile",
      "custom_field_id": 12,
      "op": "gte",
      "value": 50
    },
    {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11,
      "op": "eq",
      "value": "Big pain"
    },
    {
      "source": "appointments",
      "metric": "count",
      "op": "gte",
      "value": 2,
      "filters": {
        "status": "done"
      }
    }
  ]
}

Response:

json
{
  "id": 2,
  "organization_id": 10,
  "name": "Bucharest High Pain Seniors",
  "description": "Complex target group for outreach campaign",
  "rules": [...],
  "match_mode": "all",
  "version": 1,
  "created_at": "2025-01-15T11:00:00Z",
  "updated_at": "2025-01-15T11:00:00Z"
}

Validation:

  • name required, 1-255 characters
  • match_mode must be "all" or "any"
  • rules must be a non-empty array
  • Each rule must have valid source, op, and source-specific fields
  • Profile rules: custom_field_id must exist in custom_fields with entity_type='patient'
  • Form rules: template_id must exist, custom_field_id must exist in custom_fields
  • Appointment rules: metric must be "count" or "last_date"

Update Segment

http
PUT /v1/segments/{id}

Request:

json
{
  "name": "Updated Name",
  "description": "Updated description",
  "match_mode": "any",
  "rules": [...]
}

Behavior:

  • Current rules are archived to segment_versions before update
  • version is incremented
  • Full rebuild is enqueued asynchronously (Tier 3 evaluation)
  • Existing segment_members remain valid until rebuild completes

Response:

json
{
  "id": 2,
  "version": 2,
  "updated_at": "2025-01-15T12:00:00Z",
  ...
}

Delete Segment

http
DELETE /v1/segments/{id}

Response: 204 No Content

Cascade: Deletes all segment_members and segment_versions for this segment.


Segment Members

Get Members

http
GET /v1/segments/{id}/members

Query Parameters:

  • page (int, default: 1)
  • per_page (int, default: 50, max: 200)
  • fresh (bool, default: false) — if true, forces re-evaluation before returning

Response:

json
{
  "members": [
    {
      "patient_id": 100,
      "name": "John Doe",
      "email": "[email protected]",
      "matched_at": "2025-01-15T10:30:00Z"
    },
    {
      "patient_id": 101,
      "name": "Jane Smith",
      "email": "[email protected]",
      "matched_at": "2025-01-15T11:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total": 42,
    "total_pages": 1
  }
}

Fresh Evaluation:

http
GET /v1/segments/{id}/members?fresh=true

Forces full re-evaluation (Tier 3) before returning members. Rate-limited to 1 request per minute per segment. If re-evaluation fails, returns cached members with a warning header:

X-Segment-Freshness: stale
X-Segment-Last-Evaluated: 2025-01-15T10:00:00Z

Get Patient Segments

http
GET /v1/patients/{id}/segments

Returns all segments this patient belongs to.

Response:

json
{
  "segments": [
    {
      "id": 1,
      "name": "High Pain Patients",
      "description": "Patients reporting big pain",
      "matched_at": "2025-01-15T10:30:00Z"
    },
    {
      "id": 2,
      "name": "Bucharest Seniors",
      "description": "Seniors in Bucharest",
      "matched_at": "2025-01-15T11:00:00Z"
    }
  ]
}

Access Control:

  • Patients cannot access this endpoint for their own data (segments are internal/admin-only)
  • Admins and specialists can query any patient's segments

Segment Evaluation

Trigger Full Rebuild

http
POST /v1/segments/{id}/evaluate

Rebuilds segment_members from scratch by re-evaluating rules against all patients.

Response:

json
{
  "status": "queued",
  "job_id": "eval-job-abc123",
  "estimated_time": "< 30 seconds"
}

Behavior:

  • Clears existing segment_members for this segment
  • Evaluates rules against ALL patients in organization
  • Inserts new segment_members rows for matches
  • Returns 202 Accepted immediately (processing happens async)

Polling for completion:

http
GET /v1/segments/{id}/evaluation-status

Response:

json
{
  "status": "completed",
  "started_at": "2025-01-15T12:00:00Z",
  "completed_at": "2025-01-15T12:00:15Z",
  "duration_ms": 15000,
  "members_added": 42,
  "members_removed": 5
}

Evaluate Single Patient

http
POST /v1/patients/{id}/evaluate-segments

Re-evaluates all segments for a single patient. Used after bulk profile updates or manual data corrections.

Response:

json
{
  "evaluated": 15,
  "added_to": [1, 3, 7],
  "removed_from": [2, 5]
}

Segment Version History

List Versions

http
GET /v1/segments/{id}/versions

Response:

json
{
  "versions": [
    {
      "version": 2,
      "rules": [...],
      "match_mode": "all",
      "changed_by": {
        "id": 10,
        "name": "Admin User"
      },
      "created_at": "2025-01-15T12:00:00Z"
    },
    {
      "version": 1,
      "rules": [...],
      "match_mode": "all",
      "changed_by": {
        "id": 10,
        "name": "Admin User"
      },
      "created_at": "2025-01-15T10:00:00Z"
    }
  ]
}

Get Specific Version

http
GET /v1/segments/{id}/versions/{version}

Response:

json
{
  "segment_id": 2,
  "version": 1,
  "rules": [...],
  "match_mode": "all",
  "changed_by": {
    "id": 10,
    "name": "Admin User"
  },
  "created_at": "2025-01-15T10:00:00Z"
}

Error Responses

Validation Errors

json
{
  "status": 400,
  "name": "ValidationError",
  "message": "Segment validation failed",
  "details": {
    "errors": [
      {
        "field": "rules[0].custom_field_id",
        "message": "custom field 999 not found"
      },
      {
        "field": "rules[1].template_id",
        "message": "form template 999 not found"
      }
    ]
  }
}

Rule Evaluation Errors

json
{
  "status": 500,
  "name": "SegmentEvaluationError",
  "message": "Failed to evaluate segment rules",
  "details": {
    "segment_id": 2,
    "patient_id": 100,
    "failing_rule": {
      "source": "form",
      "template_id": 5,
      "custom_field_id": 11
    },
    "error": "database timeout"
  }
}

Rate Limit (Fresh Evaluation)

json
{
  "status": 429,
  "name": "RateLimitError",
  "message": "Fresh evaluation rate limit exceeded",
  "details": {
    "retry_after": 45
  }
}

Webhooks (Future)

When segment membership changes, emit webhook events:

json
{
  "event": "segment.member.added",
  "timestamp": "2025-01-15T12:00:00Z",
  "data": {
    "segment_id": 2,
    "segment_name": "High Pain Patients",
    "patient_id": 100,
    "matched_at": "2025-01-15T12:00:00Z"
  }
}
json
{
  "event": "segment.member.removed",
  "timestamp": "2025-01-15T12:00:00Z",
  "data": {
    "segment_id": 2,
    "segment_name": "High Pain Patients",
    "patient_id": 100,
    "reason": "no longer matches rules"
  }
}

Use cases:

  • Trigger automated email campaigns when patients join a segment
  • Update external CRM when high-value patients are identified
  • Alert staff when at-risk patients enter a critical segment

Access Control

EndpointPatientSpecialistAdminSuperadmin
GET /v1/segments
POST /v1/segments
PUT /v1/segments/{id}
DELETE /v1/segments/{id}
GET /v1/segments/{id}/members
POST /v1/segments/{id}/evaluate
GET /v1/patients/{id}/segments
POST /v1/patients/{id}/evaluate-segments

Patients cannot see segments — segmentation is an internal organizational tool, not patient-facing.