Skip to Content
API ReferencePatient Graph API

Patient Graph API

The Patient Graph API is the clinical data backbone of Loop Health. It provides CRUD operations for patient profiles, lab results, protocols, events, treatments, prescriptions, and conversation history — all protected by RBAC and audit logging.

Base URLs:

  • Production: https://patient-graph.loop.health
  • Development: http://localhost:3000

Authentication: Clerk JWT bearer token required on all endpoints (except webhooks).

Rate Limit: 100 requests per 60 seconds per user.


Profiles

List Profiles

curl -X GET "https://patient-graph.loop.health/profiles?limit=20&offset=0" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
emailstringNoFilter by email address
subscriptionTierstringNoFilter by tier
tagstringNoFilter by tag
limitnumberNoMax results (default: 20, max: 100)
offsetnumberNoOffset for pagination (default: 0)

RBAC: profile:read. Customers see only their own profile.

Response:

{ "success": true, "data": [ { "id": "prof_abc123", "externalId": "user_clerk_123", "email": "patient@example.com", "firstName": "Jane", "lastName": "Doe", "dateOfBirth": "1990-01-15", "biologicalSex": "female", "tags": ["beta-tester"], "metadata": {}, "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-06-15T12:00:00Z" } ], "meta": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 } }

Get Profile

curl -X GET "https://patient-graph.loop.health/profiles/:id" \ -H "Authorization: Bearer $CLERK_JWT"

RBAC: profile:read. Customers can only access their own profile.

Create Profile

curl -X POST "https://patient-graph.loop.health/profiles" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "externalId": "user_clerk_456", "email": "newpatient@example.com", "firstName": "John", "lastName": "Smith", "dateOfBirth": "1985-03-20", "biologicalSex": "male", "tags": [], "metadata": {} }'

RBAC: profile:write

Response: 201 Created with Location header.

Update Profile

curl -X PATCH "https://patient-graph.loop.health/profiles/:id" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "firstName": "Jonathan", "tags": ["vip"] }'

RBAC: profile:write. Customers can only update their own profile.

Delete Profile

curl -X DELETE "https://patient-graph.loop.health/profiles/:id" \ -H "Authorization: Bearer $CLERK_JWT"

RBAC: profile:delete (admin/staff only).


Lab Results

List Labs

curl -X GET "https://patient-graph.loop.health/labs?customerId=prof_abc123&limit=10" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
customerIdstringYesCustomer ID to filter by
statusstringNoFilter by status (pending, reviewed, archived)
providerstringNoFilter by lab provider
fromDatestringNoISO date — labs after this date
toDatestringNoISO date — labs before this date
limitnumberNoMax results (default: 20, max: 100)
offsetnumberNoOffset for pagination (default: 0)

RBAC: lab_results:read. Customers see only their own labs.

Response:

{ "success": true, "data": [ { "id": "lab_xyz789", "customerId": "prof_abc123", "labDate": "2024-06-01", "provider": "Quest Diagnostics", "status": "reviewed", "biomarkers": [ { "code": "testosterone-total", "name": "Total Testosterone", "value": 650, "unit": "ng/dL", "referenceRange": { "low": 300, "high": 1000 }, "status": "normal" } ], "fileUrl": "https://...", "notes": "Annual panel", "createdAt": "2024-06-02T10:00:00Z" } ], "meta": { "page": 1, "limit": 10, "total": 3, "totalPages": 1 } }

Get Lab Result

curl -X GET "https://patient-graph.loop.health/labs/:id" \ -H "Authorization: Bearer $CLERK_JWT"

Create Lab Result

curl -X POST "https://patient-graph.loop.health/labs" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "customerId": "prof_abc123", "labDate": "2024-06-01", "provider": "Quest Diagnostics", "biomarkers": [ { "code": "testosterone-total", "name": "Total Testosterone", "value": 650, "unit": "ng/dL" } ], "status": "pending", "notes": "Annual bloodwork" }'

RBAC: lab_results:write

Delete Lab Result

curl -X DELETE "https://patient-graph.loop.health/labs/:id" \ -H "Authorization: Bearer $CLERK_JWT"

RBAC: lab_results:delete (admin/staff only).


Protocols

List Protocols

curl -X GET "https://patient-graph.loop.health/protocols?customerId=prof_abc123" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
customerIdstringYesCustomer ID to filter by
statusstringNoFilter by status (active, paused, completed, cancelled)
limitnumberNoMax results (default: 20, max: 100)
offsetnumberNoOffset for pagination (default: 0)

RBAC: protocols:read. Customers see only their own protocols.

Response:

{ "success": true, "data": [ { "id": "proto_def456", "customerId": "prof_abc123", "title": "BPC-157 Recovery Protocol", "description": "8-week recovery protocol", "status": "active", "startDate": "2024-06-01", "endDate": "2024-07-27", "items": [ { "compound": "BPC-157", "dosage": "250mcg", "frequency": "2x daily", "route": "subcutaneous" } ], "prescriberId": "dr_smith", "notes": "Start low, titrate up", "createdAt": "2024-06-01T00:00:00Z" } ] }

Create Protocol

curl -X POST "https://patient-graph.loop.health/protocols" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "customerId": "prof_abc123", "title": "BPC-157 Recovery Protocol", "description": "8-week recovery protocol", "status": "active", "startDate": "2024-06-01", "endDate": "2024-07-27", "items": [ { "compound": "BPC-157", "dosage": "250mcg", "frequency": "2x daily", "route": "subcutaneous" } ], "prescriberId": "dr_smith" }'

Update Protocol

curl -X PATCH "https://patient-graph.loop.health/protocols/:id" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "status": "paused", "notes": "Paused for travel" }'

Delete Protocol

curl -X DELETE "https://patient-graph.loop.health/protocols/:id" \ -H "Authorization: Bearer $CLERK_JWT"

Patient Events

List Events

curl -X GET "https://patient-graph.loop.health/events?customerId=prof_abc123&type=lab_parsed" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
customerIdstringYesCustomer ID
typestringNoEvent type filter
sourcestringNoEvent source filter
fromDatestringNoISO date start
toDatestringNoISO date end
limitnumberNoMax results (default: 20, max: 100)
offsetnumberNoOffset (default: 0)

Event Types: lab_parsed, protocol_started, protocol_completed, note_added, check_in, wearable_sync, treatment_approved, prescription_shipped

RBAC: events:read. Customers see only their own events.

Create Event

curl -X POST "https://patient-graph.loop.health/events" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "customerId": "prof_abc123", "type": "note_added", "description": "Patient reported improved sleep", "data": { "category": "wellness", "severity": "info" }, "source": "luna-ai" }'

Treatments

List Treatments

curl -X GET "https://patient-graph.loop.health/treatments?customerId=prof_abc123" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
customerIdstringYesCustomer ID
statusstringNoTreatment status
rimoTreatmentIdstringNoRimo treatment ID
limitnumberNoMax results (default: 20)
offsetnumberNoOffset (default: 0)

Create Treatment

curl -X POST "https://patient-graph.loop.health/treatments" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "customerId": "prof_abc123", "rimoTreatmentId": "rimo_treat_123", "offeringId": "offering_456", "offeringName": "TRT Protocol", "status": "pending" }'

Update Treatment

curl -X PATCH "https://patient-graph.loop.health/treatments/:id" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "status": "approved", "approvedAt": "2024-06-15T12:00:00Z" }'

Prescriptions

List Prescriptions

curl -X GET "https://patient-graph.loop.health/prescriptions?customerId=prof_abc123" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
customerIdstringYesCustomer ID
treatmentIdstringNoFilter by treatment
statusstringNoPrescription status
rimoOrderIdstringNoRimo order ID
limitnumberNoMax results (default: 20)
offsetnumberNoOffset (default: 0)

Create Prescription

curl -X POST "https://patient-graph.loop.health/prescriptions" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "customerId": "prof_abc123", "treatmentId": "treat_789", "rimoOrderId": "rimo_ord_456", "medicationName": "BPC-157", "dosage": "5mg/vial", "quantity": 2, "refills": 3, "status": "pending" }'

Conversation History

List Conversations

curl -X GET "https://patient-graph.loop.health/conversation-history?customerId=prof_abc123&channel=luna&limit=50" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
customerIdstringYesCustomer ID
channelstringNoChannel filter (luna, support, voice)
sessionIdstringNoSession ID filter
rolestringNoMessage role filter (user, assistant, system)
fromDatestringNoISO date start
toDatestringNoISO date end
limitnumberNoMax results (default: 50, max: 500)
offsetnumberNoOffset (default: 0)

Create Conversation Entry

curl -X POST "https://patient-graph.loop.health/conversation-history" \ -H "Authorization: Bearer $CLERK_JWT" \ -H "Content-Type: application/json" \ -d '{ "customerId": "prof_abc123", "channel": "luna", "role": "user", "content": "What are my latest lab results?", "sessionId": "sess_abc123", "metadata": {} }'

Audit Logs

List Audit Logs

curl -X GET "https://patient-graph.loop.health/audit-logs?limit=50" \ -H "Authorization: Bearer $CLERK_JWT"

Query Parameters:

ParamTypeRequiredDescription
actorIdstringNoFilter by actor (user who performed action)
customerIdstringNoFilter by patient whose data was accessed
actionstringNoFilter by action (read, write, delete, export)
resourcestringNoFilter by resource type
outcomestringNoFilter by outcome (allowed, denied)
fromDatestringNoISO date start
toDatestringNoISO date end
limitnumberNoMax results (default: 20)
offsetnumberNoOffset (default: 0)

RBAC: audit_log:read — admin (full) and staff (read-only). Not accessible to customers or support.

Response:

{ "success": true, "data": [ { "id": "audit_123", "actorId": "user_clerk_789", "customerId": "prof_abc123", "role": "staff", "action": "read", "resource": "lab_results", "outcome": "allowed", "metadata": {}, "createdAt": "2024-06-15T12:00:00Z" } ] }

Rimo Webhooks

Receive Webhook

POST /webhooks/rimo

Authentication: HMAC-SHA256 signature verification (no Clerk JWT).

Headers:

  • X-Rimo-Signature — HMAC-SHA256 hex digest
  • X-Rimo-Timestamp — Unix timestamp (must be within 5 minutes)

Events:

  • treatment.created — New treatment created in Rimo
  • treatment.approved — Treatment approved by clinician
  • order.transmitted — Prescription sent to pharmacy
  • order.shipped — Order shipped to patient
  • charge.captured — Payment captured

Idempotency: Events are deduplicated by event ID (in-memory).

Webhook Health Check

curl https://patient-graph.loop.health/webhooks/rimo/health

Returns { "status": "ok" }.