CDQ API
For engineers at client health systems who send patient data to CDQ.
Overview
CDQ checks patient event data against approved care pathways. For each patient and pathway it reports which steps were done on time, done late, done too early, or missed, and which are not due yet.
You send:
- patients, each with a list of dated events (diagnoses, labs, visits, procedures, medications and so on),
- optional patient context (age or date of birth, sex, plan type, problem list, social needs flags),
- optional prior pathway assignments.
You get back:
- one status row per patient, pathway and step (
step_statuses), - one row per deviation: a missed, late or premature step (
deviations), - the patients and pathways that were not evaluated, with the reason (
patients_skipped), - recommendations, when any apply (
recommendations).
CDQ does not store the patient data you send. Only counts (patients submitted and evaluated per pathway) are kept, for billing.
Two ways to send data
Both run the same evaluation on the same data. Pick the one that fits where you are.
| Feature | File upload | JSON API |
|---|---|---|
| Best for | Getting started, one-off or monthly extracts | Automated pipelines |
| Where | Web app, Analysis page | POST /cdq/analysis/evaluate-batch |
| Format | CSV, TSV or JSON file | JSON request body |
| Sign-in | Your CDQ user account | API key (X-API-Key) or bearer token |
| Choosing pathways | CDQ shows which pathways the file's patients qualify for; you tick the ones to run | You list them in pathway_ids |
| Evaluation date | Optional date on the upload form | Optional as_of_date |
| Results | On screen, plus CSV downloads (deviations, step statuses, recommendations) | JSON response |
The two are designed to be interchangeable:
- The upload's JSON template (download it from the Analysis page,
cdq_events_template.json) is an API request body. The same file can be uploaded, or sent to the API once you fill inclient_idandpathway_ids. Uploads ignoreclient_id,pathway_idsandas_of_datein the file. - Each CSV column maps to one API field. See Upload ↔ API field mapping.
A common path is to start with uploads, then move the same extract into a pipeline that calls the API.
Authentication
Every /cdq/* endpoint needs one of these headers:
| Header | Who uses it | Identity |
|---|---|---|
X-API-Key: <key> | Pipelines | Role client_api, scoped to the key's client |
Authorization: Bearer <token> | Signed-in apps | Role and client from the token's claims (RS256, checked against CDQ's identity provider) |
If both are sent, X-API-Key is used.
- Getting a key. A user with the client admin role creates keys in the web app on the API keys page. The key is shown once; copy it then. Keys can be revoked on the same page, and a revoked key fails at once.
- Client scoping. Every request names a
client_id(a UUID). It must be the client the key or token belongs to; any other value returns 403. Your client ID is shown on the API keys page in the web app. - HTTPS only. Plain HTTP requests are refused with 403.
- Roles.
client_api(API keys) andclient_admincan evaluate and read pathways.demo_vieweraccounts can evaluate demo data but cannot change anything.
There are no request-rate limits. Repeated failed sign-ins are logged and alerted on.
Evaluate a batch
POST /cdq/analysis/evaluate-batch
Content-Type: application/json
X-API-Key: <your key>
The request body may be up to 25 MB by default. Split larger extracts into several requests that share one submission_id (see below).
Request
This is the upload template with client_id and pathway_ids filled in:
{
"client_id": "3f2c9a1e-5b7d-4c8e-9a10-2b3c4d5e6f70",
"pathway_ids": [
"8a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e"
],
"as_of_date": null,
"patients": [
{
"patient_id": "P001",
"patient_context": {"date_of_birth": "1967-04-12", "patient_sex": "F", "plan_type": "commercial",
"active_diagnoses": ["E11.9", "73211009"]},
"prior_assignments": [{"pathway_name": "Chronic Diabetes Management", "assignment_status": "active",
"assigned_since": "2025-09-02"}],
"events": [
{"event_id": "e1", "event_type": "diagnosis", "event_date": "2025-09-02", "event_code": "E11.9", "event_code_system": "ICD-10"},
{"event_id": "e2", "event_type": "lab", "event_date": "2025-09-20", "event_code": "4548-4", "event_code_system": "LOINC", "result_value": "8.1",
"communication_date": "2025-09-24"}
]
},
{
"patient_id": "P002",
"patient_context": {"date_of_birth": "1958-02-20", "patient_sex": "M", "plan_type": "medicare"},
"events": [
{"event_id": "e3", "event_type": "procedure", "event_date": "2025-10-03", "event_code": "27447",
"event_code_system": "CPT", "scheduled_date": "2025-11-14"}
]
}
]
}
With curl:
curl -sS https://<your CDQ host>/cdq/analysis/evaluate-batch \
-H "X-API-Key: $CDQ_API_KEY" \
-H "Content-Type: application/json" \
--data @batch.json
Fields
Top level
| Field | Type | Required | Meaning |
|---|---|---|---|
client_id | UUID string | Yes | Your client. Must match your key or token. |
pathway_ids | array of UUID strings | Yes, at least one | Pathways to evaluate. Each must be approved for your client (see GET /cdq/pathways/available). Repeats are ignored. |
patients | array | Yes, at least one | The patients. |
as_of_date | YYYY-MM-DD or null | No | The evaluation date: steps due after it are not yet missed. Can't be after today (422). Default: the latest event dated on or before today in the request. |
submission_id | UUID string | No | Groups requests into one billing submission. Omitted: CDQ creates one and returns it. |
mode | "evaluate" or "enrollment_preview" | No, default "evaluate" | enrollment_preview only checks who qualifies for each pathway: qualifying patients are listed in patients_enrolled, the rest in patients_skipped. It is not billed, works while billing is paused, and returns no step statuses or deviations. |
include_recommended_when_prior | boolean | No, default false | When a patient has prior assignments, CDQ normally evaluates only those pathways for them. true evaluates every listed pathway for every patient. |
enforce_assigned_scope | boolean | No, default false | true requires every patient to have prior assignments with pathway_id, and evaluates only those. Cannot be combined with include_recommended_when_prior: true. |
patients[]
| Field | Type | Required | Meaning |
|---|---|---|---|
patient_id | string | Yes | Your identifier for the patient. Returned as-is in every row. Use a pseudonymous ID, not an MRN, where you can. |
events | array | Yes (may be empty) | The patient's events. |
patient_context | object | No | Patient-level facts. See below. |
prior_assignments | array | No | Pathways the patient is already on. See below. |
prior_assignment | object | No | A single prior assignment. Used only when prior_assignments is empty. |
patients[].events[]
| Field | Type | Required | Meaning |
|---|---|---|---|
event_id | string | No | Your ID for the event, unique within the patient. Left out or empty: CDQ numbers the events. |
event_type | string | Yes | What kind of event, any case. See Event types and codes. |
event_date | YYYY-MM-DD | Yes | When it happened. For a booking, when it was booked. Events with a date CDQ can't read are ignored. |
event_code | string | No | The clinical code: ICD-10-CM, SNOMED CT, LOINC, CPT/HCPCS or RxNorm. Case doesn't matter. |
event_code_system | string | No | The code's system, for example ICD-10, LOINC, CPT, RxNorm. Matching uses the code itself. |
encounter_type | string | No | Visit setting, for example ambulatory, inpatient, emergency, wellness, discharge. |
result_value | string | No | A lab, vital sign or screening result, for example "8.1" or "positive". |
scheduled_date | YYYY-MM-DD | No | Set only for a procedure that is booked but not yet done: the booked date. An event whose scheduled date can't be read is ignored, as in an upload. See Scheduled procedures. |
communication_date | YYYY-MM-DD | No | When this event's result was given to the patient. Adds a communication event with the same code on that date. See Results and referrals. |
referral_date | YYYY-MM-DD | No | When a referral was made following this event. Adds a referral event with the same code on that date. |
provider_type | string | No | Accepted; not used in evaluation today. |
department | string | No | Accepted; not used in evaluation today. |
patients[].patient_context (all keys optional)
| Key | Type | Meaning |
|---|---|---|
patient_age | integer | Age in whole years. Ages over 90 are treated as 90. |
date_of_birth | YYYY-MM-DD | Used to work out age on the evaluation date when patient_age is not sent. |
patient_sex | string | For example F or M. |
plan_type | string | Coverage, for example commercial, medicare, medicaid. Some pathways apply to certain plan types only. |
active_diagnoses | array of strings | The problem list: ICD-10-CM or SNOMED CT codes. |
open_sdoh_flags | array of strings | Open social needs: food_insecurity, housing_instability, transportation, social_isolation, financial_strain. |
sdoh_food_insecurity, sdoh_housing_instability, sdoh_transportation, sdoh_social_isolation, sdoh_financial_strain | true, 1, "1", "true", "yes" | The same flags one at a time. A set flag is added to open_sdoh_flags. |
open_hcc_flags | array of strings | Open HCC flags. |
Wrong types return 422 naming the key, for example patient_context.patient_age must be an integer.
patients[].prior_assignments[] (all keys optional)
| Key | Type | Meaning |
|---|---|---|
pathway_id | UUID string | The pathway the patient is on. Preferred. |
pathway_name | string | The pathway's exact name. Used only when pathway_id is not given. |
assignment_status | string | completed means the pathway is finished for this patient; it is skipped. |
assigned_since | YYYY-MM-DD | When the patient started the pathway. The pathway's clock starts here instead of at the first triggering event. |
last_completed_step | string | Accepted; not used in evaluation today. |
Response
200 OK with a JSON object. Abridged example for the request above:
{
"total_patients": 2,
"total_pathways": 2,
"as_of_date": "2025-10-03",
"submission_id": "6d1f0b8e-2a4c-4e9b-9b7a-1c2d3e4f5a6b",
"possible_deviations_count": 0,
"deviations": [
{
"patient_id": "P001",
"pathway_id": "8a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"pathway_name": "Chronic Diabetes Management",
"step_name": "Diabetic eye exam",
"deviation_type": "missing_step",
"severity": "moderate",
"expected_date": "2025-10-02",
"actual_date": null,
"days_variance": null,
"confirmed": true,
"missing_fields": null,
"evaluation_source": "assigned",
"visit_group": "ambulatory_visit"
}
],
"step_statuses": [
{
"patient_id": "P001",
"pathway_id": "8a1d2c3b-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"pathway_name": "Chronic Diabetes Management",
"step_id": "c0ffee00-0000-4000-8000-000000000001",
"step_name": "HbA1c test",
"step_order": 1,
"status": "COMPLETE",
"actual_date": "2025-09-20",
"expected_by_date": "2025-12-01",
"window_open_date": "2025-09-02",
"days_variance": -72,
"evaluation_source": "assigned",
"trigger_type": "icd10",
"trigger_code": "E11",
"trigger_date": "2025-09-02"
},
{
"patient_id": "P002",
"pathway_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"pathway_name": "Total Knee Replacement",
"step_id": "c0ffee00-0000-4000-8000-000000000002",
"step_name": "Pre-operative assessment",
"step_order": 1,
"status": "DUE",
"actual_date": null,
"expected_by_date": "2025-11-07",
"window_open_date": "2025-10-15",
"days_variance": null,
"evaluation_source": "recommended",
"trigger_type": "procedure_scheduled",
"trigger_code": "27447",
"trigger_date": "2025-11-14"
}
],
"patients_skipped": [
{
"patient_id": "P001",
"pathway_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"pathway_name": "Total Knee Replacement",
"reason": "pathway not in patient prior assignments"
}
]
}
Pathway names, step names, codes and dates above are illustrative. The real ones come from your approved pathways.
Top level
| Field | Meaning |
|---|---|
total_patients | Patients in the request. |
total_pathways | Distinct pathways in the request. |
as_of_date | The evaluation date used. |
submission_id | The submission this request was billed under. null in enrollment_preview mode. |
deviations | Missed, late and premature steps, sorted by patient, then severity (critical first), then visit group. |
step_statuses | One row per patient, pathway and evaluated step. |
possible_deviations_count | Deviations with confirmed: false. |
patients_skipped | Patient and pathway pairs not evaluated, with the reason. |
patients_enrolled | enrollment_preview only: each patient and pathway pair that qualifies, with trigger_date (when the pathway would start) and evaluation_source. Left out when empty. |
recommendations | Present only when at least one applies. Never counted as deviations. |
deviations[]
| Field | Meaning |
|---|---|
deviation_type | missing_step (not done, due date passed), late_step (done after the due date), premature_step (done before the window opened), conditional_gap (a conditional step could not be checked because patient context is missing; confirmed: false). |
severity | critical, high, moderate, low, informational, as set on the step. |
expected_date | The due date (missing, late), or the date the window opened (premature). |
actual_date | When the step was done, if it was. |
days_variance | Late: days after the due date (positive). Premature: days before the window opened (negative). |
confirmed | false only for conditional_gap. |
missing_fields | For conditional_gap: the patient_context keys that would have let CDQ check the step. |
evaluation_source | assigned (clock started at a prior assignment's assigned_since) or recommended (CDQ found the patient qualifies from the data). |
visit_group | Where the gap would usually be closed: wellness_visit, ambulatory_visit, specialist_referral, ed_related. |
step_statuses[]
| Field | Meaning |
|---|---|
status | COMPLETE (a matching event was found, on time or not), DUE (not done, not yet past due), OVERDUE (not done, past due), NOT_APPLICABLE (the step depends on an earlier step that wasn't met), CANNOT_CHECK (the step lists no codes yet, so no event can show it was done; never a deviation). |
actual_date | Date of the event that met the step. |
window_open_date, expected_by_date | The step's window. |
days_variance | actual_date minus expected_by_date. Negative means early within the window. |
trigger_type, trigger_code, trigger_date | What starts the pathway, and the date the clock started for this patient. |
patients_skipped[].reason values:
| Reason | Meaning |
|---|---|
enrollment criteria not met | The patient doesn't qualify for the pathway from the data sent. |
no trigger-matching event found | No event starts the pathway for this patient. |
pathway not in patient prior assignments | The patient has prior assignments and this pathway isn't one of them. |
prior assignment status is 'completed' | The patient has finished this pathway. |
plan_type '…' not in pathway filter [...] | The pathway is for other plan types. |
missing enrollment criteria for canonical pathway | The pathway is not set up correctly. Tell Preact. |
evaluation error (see server logs) | CDQ hit an error on this pair. The rest of the batch is unaffected. Tell Preact. |
recommendations[]
| Field | Meaning |
|---|---|
recommendation | What is recommended, in words. |
action_type | The kind of recommendation, for example pending_discharge. |
step_name | The step (or event) that prompted it. |
value | The value that prompted it: a result, days overdue, or for pending_discharge the admission date. |
suggested_pathway | A pathway to consider, when the recommendation is to start one. |
priority | For example high. |
conditional_ref | The pathway rule that fired (empty for pending_discharge). |
Submissions and billing
- One billable run is one pathway evaluated in one submission.
- Send the same
submission_idon every request that belongs together, for example a large extract split into parts, or a retry. Repeating a pathway within a submission does not add a run. - A submission stays open for 24 hours after its first use. After that, a request with that
submission_idreturns 422; start a new one. enrollment_previewrequests are not billed and are not given a submission.
Upload ↔ API field mapping
The upload reads one row per event. Patient-level columns can repeat on every row of a patient, or appear on any one of them: the first non-blank value wins. Column names are matched without regard to case, and spaces, dots and dashes count as underscores (Event Date matches event_date). When several aliases are present, the first alias in the list wins.
| Upload column | Also accepted | API JSON path | Meaning |
|---|---|---|---|
patient_id | patientid, patient, mrn, id | patients[].patient_id | Patient identifier. Required in both. |
event_type | type, record_type, event_category | patients[].events[].event_type | Kind of event. Upload: optional, defaults to encounter. API: required. Any case in both. |
event_date | date, service_date, encounter_date, event_dt, date_of_service | patients[].events[].event_date | When it happened. Required in both. |
event_code | code, dx_code, procedure_code, icd_code, cpt_code, hcpcs_code | patients[].events[].event_code | Clinical code. |
event_code_system | code_system, coding_system, code_type | patients[].events[].event_code_system | Code system name. |
encounter_type | visit_type, setting, visit_setting | patients[].events[].encounter_type | Visit setting. |
result_value | value, lab_value, result, test_result | patients[].events[].result_value | Result. |
scheduled_date | scheduled_for, procedure_scheduled_date, surgery_date_scheduled | patients[].events[].scheduled_date | Booked date of a procedure not yet done. Blank otherwise. |
communication_date | result_communicated_date, results_communicated_date, date_communicated | patients[].events[].communication_date | When the row's result was given to the patient. Blank otherwise. |
referral_date | date_referred, referred_date | patients[].events[].referral_date | When a referral was made following the row's event. Blank otherwise. |
provider_type | provider, clinician_type, provider_role | patients[].events[].provider_type | Recognized; not used in evaluation. |
department | dept, specialty, clinic, service_line | patients[].events[].department | Recognized; not used in evaluation. |
patient_age | age | patients[].patient_context.patient_age | Age in years. Upload drops decimals (45.5 → 45). |
date_of_birth | dob, birth_date, birthdate | patients[].patient_context.date_of_birth | Date of birth. |
patient_sex | sex, gender | patients[].patient_context.patient_sex | Sex. |
plan_type | insurance_type, payer_type | patients[].patient_context.plan_type | Plan type. |
active_diagnoses | problem_list, active_problems, problems | patients[].patient_context.active_diagnoses | Problem list. Upload: codes separated by ; or |. API: array of strings. |
sdoh_food_insecurity | food_insecurity | patients[].patient_context.sdoh_food_insecurity (or open_sdoh_flags containing food_insecurity) | Food insecurity flag. |
sdoh_housing_instability | housing_instability | patients[].patient_context.sdoh_housing_instability | Housing instability flag. |
sdoh_transportation | transportation | patients[].patient_context.sdoh_transportation | Transportation flag. |
sdoh_social_isolation | social_isolation | patients[].patient_context.sdoh_social_isolation | Social isolation flag. |
sdoh_financial_strain | financial_strain | patients[].patient_context.sdoh_financial_strain | Financial strain flag. |
assigned_pathway | pathway, enrolled_pathway, pathway_name | patients[].prior_assignments[0].pathway_name | Pathway the patient is on, by name. |
assignment_status | pathway_status, enrollment_status, status | patients[].prior_assignments[0].assignment_status | Assignment status. Upload reads it from the patient's first row only. |
assigned_since | enrollment_date, pathway_start_date, trigger_date | patients[].prior_assignments[0].assigned_since | Start date on the pathway. |
last_completed_step | last_step, completed_step | patients[].prior_assignments[0].last_completed_step | Recognized; not used in evaluation. |
SDOH flag values: both treat 1, true, TRUE, yes and YES as set (the API also takes JSON true and 1).
Upload only
| Upload column | Also accepted | Meaning |
|---|---|---|
first_name | firstname, fname, given_name | Shown as the patient's label in the web app. Never sent to the engine. |
last_name | lastname, lname, family_name, surname | As above. |
| Evaluation date (form field, not a column) | Same as the API's as_of_date. The upload refuses a date in the future. |
API only
| API field | Meaning |
|---|---|
client_id | Your client. The upload uses the signed-in user's client. |
pathway_ids | Pathways to run. The upload lets you pick them after showing who qualifies. |
mode | enrollment_preview is the API's version of the upload's "who qualifies" step. |
submission_id | The upload makes a new submission each time you run it. |
include_recommended_when_prior, enforce_assigned_scope | Scope controls for prior assignments. |
patients[].events[].event_id | Optional in the API too; the upload always numbers events itself. |
patients[].prior_assignments[] after the first | The upload carries one assignment per patient. A JSON upload reads the first assignment's pathway_name, or its pathway_id when there's no name. |
patients[].patient_context.open_hcc_flags | No upload column. |
Dates. The API takes YYYY-MM-DD only. Uploads also take YYYY/MM/DD, MM/DD/YYYY and M/D/YYYY (month first; day-first dates like 15/03/2026 are not read). Upload rows with a date CDQ can't read are left out and counted on the job page.
Missing values. In uploads, empty cells and NA, N/A and null count as missing. In the API, leave the key out or send null.
JSON uploads. Besides the API body shape, a JSON upload can be an array of flat event rows using the column names above, or a {"patients": [...]}, {"events": [...]}, {"data": [...]}, {"records": [...]} or {"rows": [...]} wrapper around one.
Event types and codes
A step is met by the first event, on or after the step's window opens, whose event_type is one the step accepts and whose event_code is one of the step's codes. A step with no codes can't be met by any event (CANNOT_CHECK).
| Step type | Event types that can meet it |
|---|---|
| encounter, enrollment, documentation | encounter |
| referral | encounter, referral |
| communication | communication, encounter |
| immunization | immunization, medication, procedure, encounter |
| lab | lab |
| medication | medication |
| diagnosis | diagnosis |
| sdoh_screening | sdoh_screening, screening |
| vital_sign | vital_sign, lab, encounter |
| imaging | imaging, procedure, lab, encounter |
| procedure | procedure, encounter |
| screening | screening, encounter, sdoh_screening, lab |
Event types used to qualify patients for pathways (enrollment):
| Event type | Used for |
|---|---|
diagnosis | Diagnosis-based pathways (ICD-10-CM; a code also matches its child codes, so E11 matches E11.9). Also the principal diagnosis for a hospital stay. |
medication | Active-medication criteria. |
lab, vital_sign | Result thresholds (for example HbA1c, blood pressure, BMI). Put the number or text in result_value. |
encounter | Visits and utilization. With encounter_type inpatient it is an admission; with discharge it is a discharge. |
admission | An inpatient admission. |
discharge | A hospital discharge. |
procedure, claim | Procedures done (CPT/HCPCS). |
screening, sdoh_screening | Screening results (put the score or answer in result_value). |
Event types and codes are read in any case, with spaces trimmed, by both the upload and the API.
Codes by kind of event
| Kind | Code system to send |
|---|---|
| Diagnoses, problem list | ICD-10-CM (SNOMED CT also accepted on the problem list) |
| Labs, vital signs | LOINC |
| Visits, procedures | CPT or HCPCS. Some visit steps also accept an encounter_type such as wellness. |
| Medications | RxNorm (RXCUI) or NDC. See below. |
Medications. Medication steps and criteria name drug classes (for example statins or SGLT2 inhibitors). Send the medication as an RxNorm code: an ingredient, clinical drug, branded drug or pack RXCUI all work. NDC codes are also accepted, current or discontinued: 11 digits, or hyphenated 4-4-2, 5-3-2 or 5-4-1. CDQ maps them to RxNorm. event_code_system is optional for medications (RxNorm or NDC); CDQ recognises the code either way.
How results are worked out
Evaluation date. Results are as of one date. Steps due after it are DUE, not missed. If you don't set as_of_date, CDQ uses the latest event dated on or before today in the request (bookings and future-dated events left out); a set date can't be after today. This judges an older extract as of when it was pulled, not as of now. The upload form's evaluation date works the same way.
When the clock starts. Each pathway has a trigger. For each patient, the clock starts at:
- the prior assignment's
assigned_since, if you sent one for this pathway, else - the qualifying event, for pathways that start at an event (a discharge, a booked procedure), else
- the first event that matches the trigger (for example the first diabetes diagnosis), else
- for pathways based on age or plan alone, the patient's latest visit for the first step within its repeat interval (a year by default), or the evaluation date if there is none.
Each step then has a window counted in days from that start, or from the previous step.
Scheduled procedures. For a procedure that is booked but not yet done, send the event with scheduled_date set to the booked date (event_date is when it was booked). CDQ treats it as a booking on the booked date. A procedure pathway then starts at the booked date, so pre-operative steps fall due before it. A booking never counts as the procedure itself being done. A procedure already done is sent as a normal procedure event without scheduled_date, and the pathway starts at its date.
Results and referrals. Some steps check that a result was given to the patient (for example "Results Communication" after a mammogram) or that a referral was made (for example a community resource referral after a positive social-needs screen). Put the date on the row of the result or screen: communication_date for when the result was given, referral_date for when the referral was made. CDQ adds a communication or referral event with that row's code on that date, and it meets the step that follows the result or screen. You can also send such events directly, as rows with event_type communication or referral and the code of the result or screen. A visit with the step's own codes (an office visit to discuss results) also counts. Results are due within 7 days of the result for screening pathways.
Vaccines. Immunization steps accept a vaccine given at a visit (the CPT vaccine code or the Medicare administration code, as a procedure, encounter or immunization event) or as a prescription. CVX codes count only when event_code_system is CVX, since CVX and RxNorm share small numbers.
Transitions of care. Discharge pathways (for example heart failure or pneumonia follow-up) start at the discharge. Send the discharge as an event (event_type discharge, or an encounter with encounter_type discharge), plus the principal diagnosis as a diagnosis event during the stay. A patient admitted in the last 30 days with a qualifying diagnosis but no discharge yet is not evaluated (nothing is due until discharge). Instead the response carries a pending_discharge recommendation with the admission date, so the patient isn't missed.
Recommendations. Some pathways have rules that suggest a next action from a result or a delay, for example considering another pathway. These come back in recommendations. They are suggestions only: they never count as deviations and never change step statuses.
Early and late. An event before the step's window opens is a premature_step (counted as done, but flagged). For windows that open before the trigger, such as pre-operative steps, only events up to 60 days before the window count as premature; older ones are treated as unrelated history. An event after the due date is a late_step. No event by the due date is a missing_step.
Optional steps (for example "if abnormal" follow-ups) are shown in step_statuses but never reported as deviations.
Errors
Errors return JSON {"detail": "<message>"}.
| Status | When | Example body |
|---|---|---|
| 401 | No X-API-Key or bearer token | {"detail": "X-API-Key or Bearer token required"} |
| 401 | Key unknown or revoked | {"detail": "Invalid or revoked API key"} |
| 401 | Bearer token expired, or invalid | {"detail": "Token expired"}, {"detail": "Invalid token"} |
| 402 | Your subscription is paused (evaluations only; enrollment_preview still works) | {"detail": "This account's subscription is paused, so evaluations are unavailable. Resume the subscription from the Billing page to continue."} |
| 403 | Plain HTTP | {"detail": "HTTPS required for all CDQ endpoints"} |
| 403 | Your role can't call this endpoint | {"detail": "Insufficient permissions"} |
| 403 | client_id is not your client | {"detail": "Access denied — you can only access your own data"} |
| 404 | A pathway ID isn't one your client can run (your own, or a library pathway your plan includes) | {"detail": "Pathway 8a1d2c3b-… not found for this client"} |
| 413 | Body larger than the limit (25 MB by default) | {"detail": "Request body too large: send fewer patients per request"} |
| 422 | Body isn't valid JSON or a required field is missing or has the wrong type | {"detail": "Invalid request body: missing field event_type at line 9 column 5"} |
| 422 | pathway_ids or patients empty | {"detail": "pathway_ids and patients need at least one item"} |
| 422 | An ID isn't a UUID | {"detail": "client_id: Input should be a valid UUID"} |
| 422 | A pathway exists but isn't approved | {"detail": "Pathway 8a1d2c3b-… is not approved (status: draft)"} |
| 422 | Bad patient_context value | {"detail": "patient_context.patient_age must be an integer"} |
| 422 | Submission window closed | {"detail": "submission_id 6d1f… was first used more than 24 hours ago; start a new submission with a new submission_id"} |
| 422 | enforce_assigned_scope problems | {"detail": "enforce_assigned_scope=true requires include_recommended_when_prior=false"} |
| 400 | as_of_date not YYYY-MM-DD | {"detail": "as_of_date must be ISO 8601 (YYYY-MM-DD), got: 10/03/2025"} |
| 500 | Unexpected server error | {"detail": "Internal Server Error"} |
Every response carries an X-Request-Id header. Quote it when you contact Preact about a request.
An error evaluating one patient on one pathway does not fail the request: that pair appears in patients_skipped with reason evaluation error (see server logs).
Other endpoints
Pathways by plan. Every client runs its own approved pathways. Library pathways depend on the plan: Per Pathway Run, Explorer and a free trial run the whole library; HEDIS runs the pathways that report HEDIS measures; Custom Pathways runs up to 10 library pathways the client chooses; HEDIS + Custom runs both. Choose them on the Billing page or with the selection endpoints below. You can read the steps and references of every pathway you can run. Building your own pathways (creating or editing them) comes with Custom Pathways, HEDIS + Custom and Explorer once paid; during a free trial or on Per Pathway Run those requests get 403, and pathways you built before keep running.
All /cdq/* endpoints use the same authentication. Pass your own client_id.
| Method and path | Returns |
|---|---|
GET /cdq/pathways/available?client_id=<uuid> | Approved, active pathways you can evaluate: your own and the library pathways your plan includes (see Pathways by plan). {"pathways": [{"pathway_id", "pathway_name", "tier", "trigger_type", "description"}]}. Optional tier filter. Use these IDs in pathway_ids. |
GET /cdq/clients/{client_id}/pathway-selections | The library pathways a custom plan chose: {"plan_type", "selection_count", "max_selections", "selections": [...]}. Add ?with_catalog=true for the whole library as catalog: [{"pathway_id", "pathway_name", "hedis"}]. |
POST /cdq/clients/{client_id}/pathway-selections | Choose a library pathway ({"pathway_id": "<uuid>"}; client admins, up to 10). The Billing page does the same. |
DELETE /cdq/clients/{client_id}/pathway-selections/{pathway_id} | Remove a chosen pathway. |
GET /cdq/billing/plans/{client_id} | The plans on offer and yours: {"plan_type", "stripe_sub_status", "current_period_end", "cancel_at_period_end", "trial_days", "can_change", "plans": [{"plan_type", "display_name", "monthly_price_cents", "per_run_price_cents", "max_custom_pathways", "includes_hedis", "includes_all_pathways", "available", "current"}]}. Client admins. |
POST /cdq/billing/plan/{client_id} | Choose a plan ({"plan_type": "hedis_custom"}; client admins). action says what happened: checkout (no subscription yet: add a card at url, Stripe Checkout; the plan starts once Stripe confirms, free for trial_days on a first subscription), changed (the subscription moves to the new price now, prorated), ending (per_pathway: the subscription ends with the paid period) or resumed (your current plan again, undoing ending). 409 when there's nothing to change or payment is past due. The Billing page does the same. |
GET /cdq/pathways/definitions?client_id=<uuid> | Full pathway definitions: {"definitions": [...]}. Optional filters: status (approved, draft, pending_review, retired), is_active, trigger_type, limit (1–1000, default 100), offset. |
GET /cdq/pathways/definitions/{pathway_id}/steps?client_id=<uuid> | A pathway's steps, with type, codes (expected_code, cpt_codes, loinc_codes, drug_classes), timing, severity and conditions: {"pathway_id", "steps": [...]}. Useful for checking your codes against what a step looks for. |
GET /cdq/pathways/definitions/{pathway_id}/references?client_id=<uuid> | The evidence references behind a pathway's steps. |
GET /health | {"status": "healthy", "service": "cdq-api"}. No authentication. |
GET /readyz | {"status": "ready"}, or 503 when the database is unavailable. No authentication. |
Code systems and licences
CDQ uses ICD-10-CM, SNOMED CT, LOINC, CPT/HCPCS and RxNorm codes in its pathways and accepts them in your data.
This material contains content from LOINC® (http://loinc.org). LOINC is copyright © Regenstrief Institute, Inc. and the Logical Observation Identifiers Names and Codes (LOINC) Committee and is available at no cost under the license at http://loinc.org/license. LOINC® is a registered United States trademark of Regenstrief Institute, Inc.
CPT® is a registered trademark of the American Medical Association, and CPT codes are used under licence.
Medications can be sent as RxNorm codes, or as NDC codes, which CDQ maps to RxNorm. Drug classes are built from RxNorm. This product uses publicly available data courtesy of the U.S. National Library of Medicine (NLM), National Institutes of Health, Department of Health and Human Services; NLM is not responsible for the product and does not endorse or recommend this or any other product.