Segments API
Endpoints
Segment CRUD
List Segments
GET /v1/segmentsResponse:
{
"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
GET /v1/segments/{id}Response:
{
"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
POST /v1/segmentsRequest:
{
"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:
{
"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:
namerequired, 1-255 charactersmatch_modemust be"all"or"any"rulesmust be a non-empty array- Each rule must have valid
source,op, and source-specific fields - Profile rules:
custom_field_idmust exist incustom_fieldswithentity_type='patient' - Form rules:
template_idmust exist,custom_field_idmust exist incustom_fields - Appointment rules:
metricmust be"count"or"last_date"
Update Segment
PUT /v1/segments/{id}Request:
{
"name": "Updated Name",
"description": "Updated description",
"match_mode": "any",
"rules": [...]
}Behavior:
- Current rules are archived to
segment_versionsbefore update versionis incremented- Full rebuild is enqueued asynchronously (Tier 3 evaluation)
- Existing
segment_membersremain valid until rebuild completes
Response:
{
"id": 2,
"version": 2,
"updated_at": "2025-01-15T12:00:00Z",
...
}Delete Segment
DELETE /v1/segments/{id}Response: 204 No Content
Cascade: Deletes all segment_members and segment_versions for this segment.
Segment Members
Get Members
GET /v1/segments/{id}/membersQuery Parameters:
page(int, default: 1)per_page(int, default: 50, max: 200)fresh(bool, default: false) — if true, forces re-evaluation before returning
Response:
{
"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:
GET /v1/segments/{id}/members?fresh=trueForces 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:00ZGet Patient Segments
GET /v1/patients/{id}/segmentsReturns all segments this patient belongs to.
Response:
{
"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
POST /v1/segments/{id}/evaluateRebuilds segment_members from scratch by re-evaluating rules against all patients.
Response:
{
"status": "queued",
"job_id": "eval-job-abc123",
"estimated_time": "< 30 seconds"
}Behavior:
- Clears existing
segment_membersfor this segment - Evaluates rules against ALL patients in organization
- Inserts new
segment_membersrows for matches - Returns 202 Accepted immediately (processing happens async)
Polling for completion:
GET /v1/segments/{id}/evaluation-statusResponse:
{
"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
POST /v1/patients/{id}/evaluate-segmentsRe-evaluates all segments for a single patient. Used after bulk profile updates or manual data corrections.
Response:
{
"evaluated": 15,
"added_to": [1, 3, 7],
"removed_from": [2, 5]
}Segment Version History
List Versions
GET /v1/segments/{id}/versionsResponse:
{
"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
GET /v1/segments/{id}/versions/{version}Response:
{
"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
{
"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
{
"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)
{
"status": 429,
"name": "RateLimitError",
"message": "Fresh evaluation rate limit exceeded",
"details": {
"retry_after": 45
}
}Webhooks (Future)
When segment membership changes, emit webhook events:
{
"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"
}
}{
"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
| Endpoint | Patient | Specialist | Admin | Superadmin |
|---|---|---|---|---|
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.