Holistic Care Workflow — Case Products API Guide
Audience: New clients integrating with the CareValidate platform and existing clients migrating from the legacy Dynamic Case API to the Case Products model.
Overview
The Case Products API is the recommended way to manage patient cases on the CareValidate platform. Unlike the legacy Dynamic Case API which ties one treatment to one case, the Case Products model lets you manage multiple treatments under a single case with independent lifecycles.
This enables you to:
- Create a case for a patient with one or more products (treatments) in a single request.
- Attach additional products to an existing case over time.
- Close or reopen individual products on a case independently.
- Receive real-time webhook notifications for every case-product lifecycle event.
Each product on a case can carry its own intake form, visit type, and subscription configuration, giving you granular control over multi-treatment workflows (e.g., a patient on both a weight-loss and a hair-loss program under one case).
Key Concepts
| Concept | Description |
|---|---|
| User (Patient) | The person receiving care. Identified by a unique email address. |
| Case | A container representing a patient's care journey. A patient can have multiple cases. Each case has a status (OPEN → ASSIGNED → IN_PROGRESS → APPROVED / REJECTED / NO_DECISION). |
| Product | A treatment or service offered by your organization (e.g., "Semaglutide", "Finasteride"). Products are pre-configured in your organization settings. |
| Case Product | The join between a Case and a Product. Tracks status (OPEN / CLOSE), visit type, subscription info, and an optional closure reason. |
| Case Product Request | A lifecycle record under a Case Product. Tracks the request through PENDING → APPROVED/REJECTED → ORDERED → SHIPPED → DELIVERED. |
| Form / Form Response | An intake questionnaire attached to a case product. Contains typed questions (TEXT, SINGLESELECT, MULTISELECT) with optional PHI flags. |
| Subscription | Optional recurring billing configuration on a case product (interval + count). |
| Webhook | An HTTP callback your system registers to receive real-time event notifications (e.g., product added, closed, reopened). |
Entity Relationships
User (Patient)
└── Case
├── CaseProduct ←→ OrganizationProduct
│ ├── CaseProductRequest
│ │ ├── FormResponse
│ │ └── CaseDecision
│ └── Subscription (interval, intervalCount)
├── Payment
└── CaseActivity (audit log → webhook events)
- A User can have multiple Cases.
- A Case can have multiple CaseProducts (one per unique product).
- Each CaseProduct can have multiple CaseProductRequests tracking the treatment lifecycle.
- FormResponses are attached at the CaseProductRequest level.
End-to-End Workflow
The Case Products lifecycle follows four core operations. Each operation triggers a corresponding webhook event.
1. Create a Case with Products
When a new patient enrolls, you create a case with one or more products in a single call to the Create Case endpoint. Each product can include its own intake form, visit type, and subscription configuration.
For example, a patient enrolling in a weight-loss program would have a case created with a Semaglutide product attached, including their intake form responses and a quarterly subscription.
Webhook triggered: ADD_CASE_PRODUCT (one per product) with status OPEN.
2. Add Products to an Existing Case
If a patient later needs additional treatments, you attach new products to their existing case using the Add Products to Case endpoint. This avoids creating a separate case for each treatment.
For example, the same weight-loss patient now wants to add a hair-loss treatment — you add a Finasteride product to their existing case with its own intake form and visit type.
- A case product is unique per case and organization product — you cannot add the same product to a case twice.
- Adding a product triggers an
ADD_CASE_PRODUCTwebhook event for each product added.
3. Close a Product
When a treatment is complete, denied, or no longer needed, you close that specific product using the Update Case Product endpoint with status CLOSE. The rest of the case and its other products remain unaffected.
A closure reason is optional but strongly recommended for the audit trail (e.g., "Treatment complete", "Patient ineligible — contraindication identified").
Webhook triggered: CLOSE_CASE_PRODUCT with the closure reason included in the payload.
4. Reopen a Previously Closed Product
If circumstances change (e.g., after further review a patient is found eligible), you can reopen a closed product by setting its status back to OPEN via the same Update Case Product endpoint. The closure reason is cleared on reopen.
Webhook triggered: OPEN_CASE_PRODUCT with status OPEN.
Workflow Summary
| Step | API Endpoint | Webhook Event |
|---|---|---|
| Create case + product | Create Case | ADD_CASE_PRODUCT |
| Add product to case | Add Products to Case | ADD_CASE_PRODUCT |
| Close a product | Update Case Product (status: CLOSE) | CLOSE_CASE_PRODUCT |
| Reopen a product | Update Case Product (status: OPEN) | OPEN_CASE_PRODUCT |
Webhook Events
When you register a webhook URL for your organization, the platform sends HTTP POST requests to your endpoint for every case-product lifecycle event. All three events (ADD_CASE_PRODUCT, CLOSE_CASE_PRODUCT, OPEN_CASE_PRODUCT) share a common payload structure containing:
caseProduct— The case product's ID, status (OPEN/CLOSE), closure reason, visit type, and creation timestamp.product— The organization product's ID and name.case— Full case details including status, submitter (patient) information, and organization ID.activity— An audit record with the event type, timestamp, actor (who triggered the event), and before/after values.
| Event | When it fires | Key payload values |
|---|---|---|
ADD_CASE_PRODUCT | Product added to a case (during creation or via Add Products) | status: OPEN, valueAfter: product name |
CLOSE_CASE_PRODUCT | Product closed on a case | status: CLOSE, reason: closure reason, valueAfter: "<ProductName> - <Reason>" |
OPEN_CASE_PRODUCT | Previously closed product reopened | status: OPEN, reason: null, valueAfter: product name |
Migration Guide (from Dynamic Case API)
If you are currently using the legacy Dynamic Case creation flow (single case per request without explicit product management), this section outlines the key differences and recommended migration steps.
Key Differences
| Aspect | Legacy Dynamic Case API | Case Products API |
|---|---|---|
| Products | Implicit — one treatment per case | Explicit products array — multiple treatments per case |
| Forms | Attached at the case level | Attached per product via products[].form |
| Subscriptions | Managed separately | Inline per product via products[].subscription |
| Closing treatments | Archive the entire case | Close individual products while keeping the case open |
| Webhooks | CREATE_CASE, ARCHIVE_CASE | ADD_CASE_PRODUCT, CLOSE_CASE_PRODUCT, OPEN_CASE_PRODUCT |
| Granularity | Case-level lifecycle | Per-product lifecycle within a case |
What Changes in the Request
The legacy /dynamic-case endpoint accepts patient info, payment, and form questions at the top level:
{
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"paymentAmount": 159,
"stripeSetupId": "seti_1Example123",
"productBundleId": "<your-product-bundle-id>",
"questions": [...]
}
The Case Products API moves product-specific data (forms, subscriptions, visit type) into a products array, and patient info into a user object:
{
"user": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phoneNumber": "+1987654321",
"dob": "1990-05-15"
},
"payment": {
"amount": 159,
"currency": "USD",
"providerReference": {
"type": "SETUP_INTENT",
"id": "seti_1Example123"
}
},
"products": [
{
"id": "<organization-product-uuid>",
"visitType": "ASYNC_TEXT_EMAIL",
"subscription": { "interval": "month", "intervalCount": 3 },
"form": {
"title": "Intake Form",
"questions": [...]
}
}
]
}
Key structural changes:
- Patient info moves from root-level fields into a
userobject, withshippingAddressnested inside. - Payment moves from flat fields (
paymentAmount,stripeSetupId) into a structuredpaymentobject withproviderReference. - Product bundle is replaced by an explicit
productsarray where each product has its ownid,form,subscription, andvisitType. - Questions move from a case-level
questionsarray intoproducts[].form.questions.
What Changes in Lifecycle Management
With the legacy API, closing a treatment means archiving the entire case. With the Case Products API, you close individual products while keeping the case open for other active treatments.
| Legacy Action | Case Products Equivalent |
|---|---|
| Archive case to end treatment | Close a specific product via Update Case Product with status CLOSE |
| Reopen archived case | Reopen a specific product with status OPEN |
| Create new case for additional treatment | Add product to existing case via Add Products to Case |
What Changes in Webhooks
| Legacy Webhook | Case Products Webhook | Notes |
|---|---|---|
CREATE_CASE | ADD_CASE_PRODUCT | Fires per product, not per case |
ARCHIVE_CASE | CLOSE_CASE_PRODUCT | Fires per product closure, not whole-case archive |
| (no equivalent) | OPEN_CASE_PRODUCT | New event for reopening individual products |
The webhook payload structure also changes — case-product webhooks include caseProduct and product objects alongside the existing case and activity data.
Recommended Migration Steps
-
Inventory your current integrations. Identify every call to the legacy case creation endpoint and note which product/treatment each call represents.
-
Map products. Ensure each treatment you offer is configured as an Organization Product in your CareValidate dashboard. Note the product UUIDs.
-
Update case creation calls. Replace legacy case creation with the Create Case endpoint including the
productsarray. Move form data from the case level intoproducts[].form. -
Update webhook handlers. Register for the new case-product webhook events (
ADD_CASE_PRODUCT,CLOSE_CASE_PRODUCT,OPEN_CASE_PRODUCT). Update your event processing logic to handle per-product payloads. -
Adopt per-product lifecycle management. Replace case-level archive/reopen flows with product-level close/reopen via Update Case Product.
-
Test in staging. Use
isTest: trueand the staging environment to validate the full workflow before switching production traffic. -
Cutover. Once staging validation is complete, update your production integration to use the Case Products endpoints.
Appendix: Enums Quick Reference
Case Status
OPEN → ASSIGNED → IN_PROGRESS → APPROVED / REJECTED / NO_DECISION
Case Product Status
OPEN | CLOSE
Case Product Request Status
PENDING → APPROVED / REJECTED / NO_DECISION → ORDERED → SHIPPED → DELIVERED
Other values: RE_ORDERED, SUPERSEDED, REFILL_PENDING
Visit Type
ASYNC_TEXT_EMAIL | SYNC_IN_PERSON | SYNC_PHONE | SYNC_VIDEO | ORDER_FORM | NO_SHOW
Payment Provider Reference Type
SETUP_INTENT | PAYMENT_INTENT | PAYMENT_TOKEN
Per-product payments only accept
PAYMENT_INTENT. Global (case-level) payments accept all three types.
Subscription Interval
day | week | month | year (used with intervalCount — e.g., month + 3 = quarterly)