The DRG Grouper API provides programmatic access to Medicare Severity-Diagnosis Related Group (MS-DRG) calculation based on patient demographics and ICD-10 codes.
Our API accepts patient information including diagnoses, procedures, age, sex, and discharge status, then returns the calculated MS-DRG along with validation results and Hospital-Acquired Condition (HAC) logic.
Base URL: https://api.revenuecycle.works/api/drg
The API supports grouper versions V40.0 through V44.0 (FY2027). This page is maintained by hand; the machine-readable OpenAPI spec is the authoritative reference. Where the two disagree, trust the spec.
Skip the docs. Copy a prompt with everything your agent needs to start grouping DRGs for you.
## DRG Grouper API Reference
POST https://api.revenuecycle.works/api/drg/group
Content-Type: application/json
X-API-Key: your_key // omit to use the unauthenticated tier
Machine-readable spec: https://api.revenuecycle.works/v3/api-docs
### Grouper version selection
Match the version to the patient's discharge date fiscal year. Send bare digits —
"v440" is rejected with a 400.
"440" — FY2027 (discharges on/after Oct 1 2026) ← current
"431" — FY2026 mid-year (Apr 1 2026 – Sep 30 2026)
"430" — FY2026 (Oct 1 2025 – Mar 31 2026)
"421" — FY2025 mid-year | "420" — FY2025
"411" — FY2024 mid-year | "410" — FY2024
"401" — FY2023 mid-year | "400" — FY2023
x.1 versions add new codes mid-year but share DRG definitions and weights with x.0.
### Request body (JSON)
{
"grouperVersion": "440", // required — bare digits, see table above
"principalDiagnosis": { // required
"code": "I21.3", // ICD-10-CM; periods are stripped automatically
"poa": "Y" // optional, defaults to "BLANK"
},
"sex": "MALE", // required — "MALE"|"FEMALE"|"UNKNOWN"
"dischargeStatus": "HOME_SELFCARE_ROUTINE", // required — exact constant, see below
"ageInYears": 68, // always send — omitting it means age 0, which
// routes the claim through neonatal logic
"admissionDiagnosis": "I21.3", // optional — plain string, not an object
"secondaryDiagnoses": [ // optional — same shape as principalDiagnosis
{"code": "J9601", "poa": "Y"}
],
"procedures": ["0271356"], // optional — ICD-10-PCS strings
"ageDaysAdmit": 0, // optional — neonatal (MDC 15) logic only
"ageDaysDischarge": 0 // optional — neonatal (MDC 15) logic only
}
### POA (Present on Admission) indicators
"Y" = present on admission "N" = not present on admission
"W" = clinically undetermined "U" = documentation insufficient
"E" = exempt "ONE" = unreported/exempt
"BLANK" = not provided (default) "INVALID" = unrecognised value
The field name is "poa" — "presentOnAdmission" is rejected with a 400.
### Common dischargeStatus values (UB-04 code → enum)
Must be the exact constant. Shorthand like "HOME" or "ROUTINE" is rejected with a 400.
01 → "HOME_SELFCARE_ROUTINE" 02 → "SHORT_TERM_HOSPITAL"
03 → "SNF" 06 → "HOME_HEALTH_SERVICE"
07 → "LEFT_AGAINST_MEDICAL_ADVICE" 20 → "DIED"
50 → "HOSPICE_HOME" 62 → "REHAB_FACILITY_REHAB_UNIT"
63 → "LONG_TERM_CARE_HOSPITAL" 65 → "PSYCH_HOSP_UNIT"
Planned-readmission variants: append _W_PLANNED_READMISSION (e.g., 81 → "HOME_SELF_CARE_W_PLANNED_READMISSION").
Full list: 01-07, 20-21, 30, 43, 50-51, 61-66, 69-70, 81-95, plus "NONE".
### Response (200 OK)
{
"finalDrg": "321", // MS-DRG to use for reimbursement
// "999" = ungroupable, "998" = error DRG
// (998 comes back with return code "OK")
"finalBaseDrg": "321", // DRG family before the severity split
"finalMdc": "5", // Major Diagnostic Category 1-25; "0" = ungroupable
"finalSeverity": "MCC", // "MCC"|"CC"|"NON_CC"|"NONE"
"finalDrgSdxSeverity": "MCC", // highest severity from any secondary diagnosis
"finalMedSurgType": "SURGICAL", // "MEDICAL"|"SURGICAL"|"NONE"
"finalGrouperReturnCode": "OK", // CHECK THIS — see error handling below
"hacStatus": "NOT_APPLICABLE", // "NOT_APPLICABLE"|"FINAL_DRG_NO_CHANGE"
// |"FINAL_DRG_CHANGES"|"FINAL_DRG_UNGROUPABLE"
"numHacCategoriesSatisfied": 0,
"initialDrg": "321", // initial* = the same six fields before HAC processing
"diagnosisValidation": {}, // code → bool; false = not in this version, ignored
"diagnosisSeverityFlags": {}, // code → "MCC"|"CC"|"NEITHER"|"MCC_EXCLUDED"
// |"CC_EXCLUDED"|"MCC_EXCLUDED_BY_DRG_LOGIC"
// |"CC_EXCLUDED_BY_DRG_LOGIC"
"diagnosisAffectsDrg": {}, // code → bool
"diagnosisAffectsDrgDetail": {}, // code → "INITIAL"|"FINAL"|"BOTH"|"NONE"
"diagnosisPoaStatus": {}, // code → "POA_NOT_CHECKED"|"POA_NOT_RECOGNIZED"
// |"POA_RECOGNIZED_NOT_POA"|"POA_RECOGNIZED_YES_POA"
// |"HOSPITAL_EXEMPT"|"BLANK_DX_NOT_CONSIDERED"
"procedureValidation": {}, // code → bool
"procedureAffectsDrg": {}, // code → bool
"procedureAffectsDrgDetail": {}, // code → "INITIAL"|"FINAL"|"BOTH"|"NONE"
"procedureIsOperatingRoom": {}, // code → bool; OR procedures drive SURGICAL DRGs
"diagnosisHacCategories": {} // code → ["HAC-12: Surgical Site Infection (FINAL_DRG_CHANGES)"]
}
### Error handling — read this before trusting a result
A grouper rejection returns HTTP 200, not an error status. An unrecognised principal
diagnosis comes back as 200 with finalDrg "999", finalMdc "0", finalSeverity "NONE"
and finalGrouperReturnCode "INVALID_PDX".
Checking finalGrouperReturnCode == "OK" is necessary but NOT sufficient. Reject the
error DRGs by number as well:
"999" — claim could not be grouped; arrives with a non-OK return code
"998" — error DRG that arrives with finalGrouperReturnCode "OK" and a real finalMdc.
A male patient with an obstetric principal diagnosis returns finalDrg "998",
finalMdc "14" and "OK", so a return-code check alone lets it through.
Treat a result as usable only when finalGrouperReturnCode == "OK" AND finalDrg is
neither "998" nor "999".
finalGrouperReturnCode values:
"OK" — clean assignment
"INVALID_PDX" — principal diagnosis not recognised
"DX_CANNOT_BE_PDX" — code cannot be principal (e.g. a manifestation code)
"INVALID_SEX" — sex conflicts with a diagnosis or procedure
"INVALID_DISCHARGE_STATUS" — discharge status not recognised by the grouper
"HAC_MISSING_ONE_POA", "HAC_STATUS_INVALID_POA_N_OR_U",
"HAC_STATUS_INVALID_POA_INVALID_OR_1",
"HAC_STATUS_INVALID_MULT_HACS_POA_NOT_Y_W" — POA problems on a HAC diagnosis
HTTP errors return {"status", "error", "message", "details", "timestamp"}:
400 — malformed request or invalid enum value; "details" names the offending field
401 — X-API-Key did not match an active account
403 — key is valid but the account is deactivated
404 — no DRG output could be generated
429 — rate limit or monthly allowance exhausted; wait Retry-After seconds
500 — server error
### Quick-start workflow
1. Pick grouperVersion from the discharge date fiscal year
2. POST the payload (periods in ICD-10 codes are fine — they are stripped)
3. Check finalGrouperReturnCode == "OK" before using the result
4. Read finalDrg + finalMdc for the assignment
5. Use diagnosisAffectsDrgDetail / procedureAffectsDrgDetail to see which codes drove it
6. Use diagnosisSeverityFlags to explain why a CC or MCC did not raise severity
API authentication is optional but recommended. Include your API key in the request header:
X-API-Key: your_api_key_here
💡 Try before you buy! The endpoint answers unauthenticated requests, so you can evaluate it before requesting API access.
Without an API key: requests are rate limited per IP address and can return
429 until a valid key is supplied. Suitable for evaluation, not for production traffic.
With an API key: requests are not rate limited. They count against your plan's
monthly call allowance, and a 429 is also returned once that allowance is exhausted.
Contact us to discuss volume.
Every 429 carries a Retry-After header giving the number of seconds to wait.
The RateLimit-Reset header carries the same value.
A key that does not match an active account returns 401. A valid key on a deactivated
account returns 403 — the fix is billing, not the request.
curl https://api.revenuecycle.works/api/drg/group \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json"
The primary endpoint for calculating MS-DRG assignments. Submit patient demographics, diagnoses, and procedures to receive the appropriate DRG classification, severity level, and detailed validation results.
POST https://api.revenuecycle.works/api/drg/group
Content-Type: application/json
This endpoint processes inpatient hospital claims and returns:
| Parameter | Description |
|---|---|
| X-API-Key optional | Your API key. Omit it to evaluate the API on the rate-limited unauthenticated tier. |
| Content-Type | application/json |
| Parameter | Description |
|---|---|
|
grouperVersion
required
string
|
MS-DRG version: "400", "401", "410", "411", "420", "421", "430", "431", or "440" (FY2027)Send bare digits. A v prefix such as "v440" is rejected with a 400.See effective dates in Enum Reference → |
|
principalDiagnosis
required
object
|
Primary diagnosis object: • code required - ICD-10-CM code. Periods are stripped automatically, so "I25.10" and "I2510" are equivalent.• poa optional - Present on Admission indicator. Defaults to "BLANK".
|
|
ageInYears
optional
integer
|
Patient age in years at discharge. Always send this for adult claims. The field is technically optional, but omitting it yields age 0, which routes the claim through neonatal logic and changes the assigned DRG. |
|
sex
required
string
|
Sex enum value: "MALE", "FEMALE", or "UNKNOWN" |
|
dischargeStatus
required
string
|
Discharge status enum value. Common values: "HOME_SELFCARE_ROUTINE", "SNF", "HOME_HEALTH_SERVICE", "DIED", "HOSPICE_HOME"See all 37 accepted values in Enum Reference below |
|
admissionDiagnosis
optional
string
|
Admission diagnosis ICD-10-CM code (e.g., "C022"). Unlike principalDiagnosis this is a plain string, not an object, so it carries no POA indicator. |
|
secondaryDiagnoses
optional
array
|
Array of diagnosis objects, each with the same shape as principalDiagnosis:• code required - ICD-10-CM code, periods optional• poa optional - Present on Admission indicatorAccepted up to the grouper's per-claim limit. |
|
procedures
optional
array
|
Array of ICD-10-PCS procedure code strings, 7 characters each (e.g., "0CYS0Z0"). Periods are stripped automatically. |
|
ageDaysAdmit
optional
integer
|
Age in days at admission. Used only by neonatal (MDC 15) logic — leave it out for adult claims. |
|
ageDaysDischarge
optional
integer
|
Age in days at discharge. Used only by neonatal (MDC 15) logic. |
Pick the version whose effective range contains the patient's discharge date. The x.1
mid-year releases add new ICD-10 codes but share DRG definitions and relative weights with the
x.0 release for that fiscal year.
| Version | Effective Dates | API Value |
|---|---|---|
| V40.0 | 10/01/2022 - 03/31/2023 | "400" |
| V40.1 | 04/01/2023 - 09/30/2023 | "401" |
| V41.0 | 10/01/2023 - 03/31/2024 | "410" |
| V41.1 | 04/01/2024 - 09/30/2024 | "411" |
| V42.0 | 10/01/2024 - 03/31/2025 | "420" |
| V42.1 | 04/01/2025 - 09/30/2025 | "421" |
| V43.0 | 10/01/2025 - 03/31/2026 | "430" |
| V43.1 | 04/01/2026 - 09/30/2026 | "431" |
| V44.0 | 10/01/2026 - 03/31/2027 | "440" ← currently in effect |
POA drives HAC logic. The field name is poa — sending
presentOnAdmission is rejected with a 400 rather than silently ignored.
| Code | API Enum Value | Description |
|---|---|---|
| Y | "Y" |
Yes, condition was present at admission |
| N | "N" |
No, condition was not present at admission |
| U | "U" |
Unknown/documentation insufficient |
| W | "W" |
Clinically undetermined |
| 1 | "ONE" |
Exempt from POA reporting |
| E | "E" |
Exempt from POA reporting |
| empty string "" | "BLANK" |
Not provided. This is the default when poa is omitted. |
| — | "INVALID" |
Value was not recognised as a POA indicator |
| API Enum Value | Description |
|---|---|
"MALE" |
Male patient |
"FEMALE" |
Female patient |
"UNKNOWN" |
Unknown or unspecified sex |
Must be one of these exact constants. Common shorthand like "HOME" or
"ROUTINE" is rejected with a 400. A routine discharge to home is
"HOME_SELFCARE_ROUTINE".
| Code | API Enum Value |
|---|---|
| — | "NONE" no discharge status supplied |
| 01 | "HOME_SELFCARE_ROUTINE" |
| 02 | "SHORT_TERM_HOSPITAL" |
| 03 | "SNF" |
| 04 | "CUST_SUPP_CARE" |
| 05 | "CANC_CHILD_HOSP" |
| 06 | "HOME_HEALTH_SERVICE" |
| 07 | "LEFT_AGAINST_MEDICAL_ADVICE" |
| 20 | "DIED" |
| 21 | "COURT_LAW_ENFRC" |
| 30 | "STILL_A_PATIENT" |
| 43 | "FEDERAL_HOSPITAL" |
| 50 | "HOSPICE_HOME" |
| 51 | "HOSPICE_MEDICAL_FACILITY" |
| 61 | "SWING_BED" |
| 62 | "REHAB_FACILITY_REHAB_UNIT" |
| 63 | "LONG_TERM_CARE_HOSPITAL" |
| 64 | "NURSING_FACILITY_MEDICAID_CERTIFIED" |
| 65 | "PSYCH_HOSP_UNIT" |
| 66 | "CRIT_ACC_HOSP" |
| 69 | "DESIGNATED_DISASTER_ALTERNATIVE_CARE_SITE" |
| 70 | "OTH_INSTITUTION" |
| 81 | "HOME_SELF_CARE_W_PLANNED_READMISSION" |
| 82 | "SHORT_TERM_HOSPITAL_W_PLANNED_READMISSION" |
| 83 | "SNF_W_PLANNED_READMISSION" |
| 84 | "CUST_SUPP_CARE_W_PLANNED_READMISSION" |
| 85 | "CANC_CHILD_HOSP_W_PLANNED_READMISSION" |
| 86 | "HOME_HEALTH_SERVICE_W_PLANNED_READMISSION" |
| 87 | "COURT_LAW_ENFRC_W_PLANNED_READMISSION" |
| 88 | "FEDERAL_HOSPITAL_W_PLANNED_READMISSION" |
| 89 | "SWING_BED_W_PLANNED_READMISSION" |
| 90 | "REHAB_FACILITY_UNIT_W_PLANNED_READMISSION" |
| 91 | "LTCH_W_PLANNED_READMISSION" |
| 92 | "NURSG_FAC_MEDICAID_CERT_W_PLANNED_READMISSION" |
| 93 | "PSYCH_HOSP_UNIT_W_PLANNED_READMISSION" |
| 94 | "CRIT_ACC_HOSP_W_PLANNED_READMISSION" |
| 95 | "OTH_INSTITUTION_W_PLANNED_READMISSION" |
Code formatting: Periods in ICD-10 codes are stripped automatically, so
"I25.10" and "I2510" group identically — you do not need to normalise codes before sending them.
Response maps are always keyed by the stripped form ("I2510"), so look codes up by that key.
curl -X POST https://api.revenuecycle.works/api/drg/group \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"grouperVersion": "440",
"admissionDiagnosis": "I213",
"principalDiagnosis": {
"code": "I213",
"poa": "Y"
},
"secondaryDiagnoses": [
{"code": "J9601", "poa": "Y"},
{"code": "I5021", "poa": "Y"},
{"code": "J9811", "poa": "Y"},
{"code": "I255", "poa": "Y"},
{"code": "F17210", "poa": "Y"},
{"code": "I959", "poa": "Y"},
{"code": "R739", "poa": "Y"}
],
"procedures": ["0271356", "02C03Z6", "B2151ZZ", "B2111ZZ", "4A023N7", "3E073PZ"],
"ageInYears": 68,
"sex": "MALE",
"dischargeStatus": "HOME_SELFCARE_ROUTINE"
}'
Returns a JSON object with the calculated DRG and validation information. A 200 means the request was processed, not that the grouping succeeded — always check finalGrouperReturnCode first (see Errors).
💡 Severity Levels: MCC (Major Complication/Comorbidity), CC (Complication/Comorbidity) and NON_CC represent the severity of secondary diagnoses that can affect DRG assignment and reimbursement rates. On the wire the third value is "NON_CC", not "No CC/MCC".
| Field | Description |
|---|---|
| Final Results (use these) | |
| finalDrg | MS-DRG assigned after HAC processing. This is the DRG to use for reimbursement. A value of "999" means the claim could not be grouped, and "998" is an error DRG — see Errors, as 998 arrives with return code "OK". |
| finalBaseDrg | The DRG family before the severity split. DRGs 291, 292 and 293 all share base DRG "291". |
| finalMdc | Major Diagnostic Category — a broad body-system or condition grouping, 1-25. "0" accompanies an ungroupable claim. |
| finalSeverity | Severity level of the assigned DRG: "MCC", "CC", "NON_CC", or "NONE". Note the wire value for no complication is "NON_CC". |
| finalDrgSdxSeverity | Highest severity contributed by any secondary diagnosis. This can exceed finalSeverity when DRG logic excludes the contributing code — compare the two to spot severity that was earned but not paid. |
| finalMedSurgType | "MEDICAL", "SURGICAL", or "NONE". "SURGICAL" means an operating-room procedure drove the assignment. |
| finalGrouperReturnCode | "OK" means a clean assignment. Any other value means the grouper rejected or could not fully process an input, and the DRG should not be used without review. See all values → |
| Initial Results (before HAC processing) | |
| initialDrg | MS-DRG before HAC processing. Equals finalDrg unless hacStatus is "FINAL_DRG_CHANGES". |
| initialBaseDrg | Base DRG before HAC processing. |
| initialMdc | Major Diagnostic Category before HAC processing. |
| initialSeverity | Severity level before HAC processing. Same value set as finalSeverity. |
| initialDrgSdxSeverity | Highest secondary-diagnosis severity before HAC processing. |
| initialMedSurgType | Medical or surgical classification before HAC processing. |
| initialGrouperReturnCode | Grouper status for the initial pass. Same value set as finalGrouperReturnCode. |
| Diagnosis Details | |
| diagnosisValidation | Map of diagnosis code → boolean: whether the grouper recognised the code. false means the code is not in this grouper version and was ignored during assignment. Keys are the submitted codes with periods stripped. |
| diagnosisSeverityFlags | Map of diagnosis code → the severity classification actually used: "MCC", "CC", "NEITHER", "MCC_EXCLUDED", "CC_EXCLUDED", "MCC_EXCLUDED_BY_DRG_LOGIC", or "CC_EXCLUDED_BY_DRG_LOGIC".The _EXCLUDED values mean the code carries that severity in general but was excluded for this claim, either by DRG-specific logic or by the principal diagnosis exclusion list. This is the field that explains why a CC or MCC did not raise the severity. |
| diagnosisAffectsDrg | Map of diagnosis code → boolean: whether the code influenced either grouping pass. Convenience field — true whenever diagnosisAffectsDrgDetail is not "NONE". |
| diagnosisAffectsDrgDetail | Map of diagnosis code → which pass the code influenced: "INITIAL", "FINAL", "BOTH", or "NONE". Use this rather than diagnosisAffectsDrg when you need to explain the result. |
| diagnosisPoaStatus | Map of diagnosis code → present-on-admission evaluation: "POA_NOT_CHECKED", "POA_NOT_RECOGNIZED", "POA_RECOGNIZED_NOT_POA", "POA_RECOGNIZED_YES_POA", "HOSPITAL_EXEMPT", or "BLANK_DX_NOT_CONSIDERED"."POA_NOT_CHECKED" is the common case and is not an error — it means the grouper had no reason to examine POA for that code. |
| Procedure Details | |
| procedureValidation | Map of procedure code → boolean: whether the grouper recognised the code. false also happens for valid ICD-10-PCS codes that simply are not in this grouper version's procedure table. |
| procedureAffectsDrg | Map of procedure code → boolean: whether the code influenced either grouping pass. |
| procedureAffectsDrgDetail | Map of procedure code → which pass the code influenced. Same value set as diagnosisAffectsDrgDetail. |
| procedureIsOperatingRoom | Map of procedure code → boolean: whether the code is an operating-room procedure. OR procedures are what push a claim to a surgical DRG. |
| HAC (Hospital-Acquired Conditions) | |
| hacStatus | Effect of HAC processing on the assignment: • "NOT_APPLICABLE" — no HAC logic applied• "FINAL_DRG_NO_CHANGE" — HACs present but the DRG did not change• "FINAL_DRG_CHANGES" — the DRG was downgraded because a condition was hospital-acquired• "FINAL_DRG_UNGROUPABLE" — HAC processing left no groupable DRG |
| numHacCategoriesSatisfied | Count of distinct HAC categories satisfied by the claim. |
| diagnosisHacCategories | Map of diagnosis code → array of HAC categories satisfied. Only diagnoses that triggered at least one HAC appear, so this is usually empty. Each entry is formatted <list>-<number>: <description> (<status>), for example "HAC-12: Surgical Site Infection (FINAL_DRG_CHANGES)". |
{
"finalDrg": "359",
"finalBaseDrg": "359",
"finalMdc": "5",
"finalSeverity": "MCC",
"finalDrgSdxSeverity": "MCC",
"finalMedSurgType": "SURGICAL",
"initialDrg": "359",
"initialBaseDrg": "359",
"initialMdc": "5",
"initialSeverity": "MCC",
"initialDrgSdxSeverity": "MCC",
"initialMedSurgType": "SURGICAL",
"finalGrouperReturnCode": "OK",
"initialGrouperReturnCode": "OK",
"hacStatus": "NOT_APPLICABLE",
"numHacCategoriesSatisfied": 0,
"diagnosisValidation": {
"J9811": true,
"J9601": true,
"I959": true,
"I255": true,
"I213": true,
"F17210": true,
"R739": true,
"I5021": true
},
"procedureValidation": {
"0271356": true,
"02C03Z6": true,
"B2151ZZ": true,
"B2111ZZ": true,
"4A023N7": true,
"3E073PZ": true
},
"diagnosisSeverityFlags": {
"J9811": "CC",
"J9601": "MCC",
"I959": "NEITHER",
"I255": "NEITHER",
"I213": "NEITHER",
"F17210": "NEITHER",
"R739": "NEITHER",
"I5021": "MCC"
},
"diagnosisAffectsDrg": {
"J9811": false,
"J9601": false,
"I959": false,
"I255": false,
"I213": true,
"F17210": false,
"R739": false,
"I5021": true
},
"diagnosisAffectsDrgDetail": {
"J9811": "NONE",
"J9601": "NONE",
"I959": "NONE",
"I255": "NONE",
"I213": "BOTH",
"F17210": "NONE",
"R739": "NONE",
"I5021": "BOTH"
},
"diagnosisPoaStatus": {
"J9811": "POA_NOT_CHECKED",
"J9601": "POA_NOT_CHECKED",
"I959": "POA_NOT_CHECKED",
"I255": "POA_NOT_CHECKED",
"I213": "POA_NOT_CHECKED",
"F17210": "POA_NOT_CHECKED",
"R739": "POA_NOT_CHECKED",
"I5021": "POA_NOT_CHECKED"
},
"procedureAffectsDrg": {
"0271356": true,
"02C03Z6": true,
"B2151ZZ": false,
"B2111ZZ": false,
"4A023N7": false,
"3E073PZ": false
},
"procedureAffectsDrgDetail": {
"0271356": "BOTH",
"02C03Z6": "BOTH",
"B2151ZZ": "NONE",
"B2111ZZ": "NONE",
"4A023N7": "NONE",
"3E073PZ": "NONE"
},
"procedureIsOperatingRoom": {
"0271356": true,
"02C03Z6": true,
"B2151ZZ": false,
"B2111ZZ": false,
"4A023N7": false,
"3E073PZ": false
},
"diagnosisHacCategories": {}
}
Here's how to make a request in Python and JavaScript:
import requests
response = requests.post(
"https://api.revenuecycle.works/api/drg/group",
headers={
"X-API-Key": "your_api_key_here",
"Content-Type": "application/json"
},
json={
"grouperVersion": "440",
"admissionDiagnosis": "I213",
"principalDiagnosis": {"code": "I213", "poa": "Y"},
"secondaryDiagnoses": [
{"code": "J9601", "poa": "Y"},
{"code": "I5021", "poa": "Y"},
{"code": "J9811", "poa": "Y"},
{"code": "I255", "poa": "Y"},
{"code": "F17210", "poa": "Y"},
{"code": "I959", "poa": "Y"},
{"code": "R739", "poa": "Y"}
],
"procedures": ["0271356", "02C03Z6", "B2151ZZ", "B2111ZZ", "4A023N7", "3E073PZ"],
"ageInYears": 68,
"sex": "MALE",
"dischargeStatus": "HOME_SELFCARE_ROUTINE"
}
)
result = response.json()
print(f"DRG: {result['finalDrg']}")
print(f"MDC: {result['finalMdc']}")
print(f"Severity: {result['finalSeverity']}")
const response = await fetch(
"https://api.revenuecycle.works/api/drg/group",
{
method: "POST",
headers: {
"X-API-Key": "your_api_key_here",
"Content-Type": "application/json"
},
body: JSON.stringify({
grouperVersion: "440",
admissionDiagnosis: "I213",
principalDiagnosis: { code: "I213", poa: "Y" },
secondaryDiagnoses: [
{ code: "J9601", poa: "Y" },
{ code: "I5021", poa: "Y" },
{ code: "J9811", poa: "Y" },
{ code: "I255", poa: "Y" },
{ code: "F17210", poa: "Y" },
{ code: "I959", poa: "Y" },
{ code: "R739", poa: "Y" }
],
procedures: ["0271356", "02C03Z6", "B2151ZZ", "B2111ZZ", "4A023N7", "3E073PZ"],
ageInYears: 68,
sex: "MALE",
dischargeStatus: "HOME_SELFCARE_ROUTINE"
})
}
);
const data = await response.json();
console.log("DRG:", data.finalDrg);
console.log("MDC:", data.finalMdc);
console.log("Severity:", data.finalSeverity);
Read this first: a grouper rejection is not an HTTP error. An unrecognised principal diagnosis returns HTTP 200 with "finalDrg": "999", "finalMdc": "0", "finalSeverity": "NONE" and "finalGrouperReturnCode": "INVALID_PDX".
Checking the return code is necessary but not sufficient. DRG "998" is an error DRG that arrives with "finalGrouperReturnCode": "OK" and a real finalMdc — a male patient with an obstetric principal diagnosis returns "998", "14" and "OK". Treat a result as usable only when finalGrouperReturnCode === "OK" and finalDrg is neither "998" nor "999".
| 200 | The request was processed. This does not mean the grouping succeeded — check finalGrouperReturnCode. |
| 400 | Invalid request: a malformed body, a missing required field, or an unaccepted enum value. The details field names the offending field and value. |
| 401 | The supplied X-API-Key did not match an active account. Only returned when a key is actually sent. |
| 403 | The key is valid but the account has been deactivated. |
| 404 | No DRG output could be generated for the given inputs. |
| 429 | Rate limit exceeded, or the account's monthly call allowance is exhausted. Wait the number of seconds given in Retry-After. |
| 500 | Internal server error during DRG calculation. Contact support. |
Every HTTP error returns the same JSON shape. details tells you what to fix.
{
"status": 400,
"error": "Bad Request",
"message": "Invalid input data",
"details": "Invalid value for 'grouperVersion': 'v440'. Must be a numeric version like 400, 420, or 440. See API documentation for valid values.",
"timestamp": "2026-09-04T05:16:48.559994333"
}
The finalGrouperReturnCode and initialGrouperReturnCode fields carry the grouper's own status. These arrive on a 200 response, so they must be checked explicitly.
| Return Code | Meaning |
|---|---|
"OK" |
Clean assignment. The DRG is safe to use. |
"INVALID_PDX" |
The principal diagnosis code was not recognised by this grouper version. |
"DX_CANNOT_BE_PDX" |
The code is valid but cannot be used as a principal diagnosis — a manifestation code, for example. |
"INVALID_SEX" |
The submitted sex conflicts with a diagnosis or procedure on the claim. |
"INVALID_DISCHARGE_STATUS" |
The discharge status was not recognised by the grouper. |
"HAC_MISSING_ONE_POA" |
A HAC diagnosis is missing its POA indicator. |
"HAC_STATUS_INVALID_POA_N_OR_U" |
A HAC diagnosis carries POA "N" or "U", which HAC logic rejects. |
"HAC_STATUS_INVALID_POA_INVALID_OR_1" |
A HAC diagnosis carries an unrecognised POA value or "ONE". |
"HAC_STATUS_INVALID_MULT_HACS_POA_NOT_Y_W" |
Multiple HACs are present and at least one has a POA other than "Y" or "W". |
An unrecognised principal diagnosis, abridged. Note the 200 status.
{
"finalDrg": "999",
"finalBaseDrg": "999",
"finalMdc": "0",
"finalSeverity": "NONE",
"finalMedSurgType": "NONE",
"finalGrouperReturnCode": "INVALID_PDX",
"initialGrouperReturnCode": "INVALID_PDX",
"hacStatus": "NOT_APPLICABLE",
"numHacCategoriesSatisfied": 0,
"diagnosisValidation": { "ZZZZZ": false },
"diagnosisSeverityFlags": { "ZZZZZ": "NEITHER" },
"diagnosisAffectsDrgDetail": { "ZZZZZ": "NONE" }
}
Six things that trip up most first integrations.
| Mistake | Looks like | Fix |
|---|---|---|
Prefixing the grouper version with v |
{"grouperVersion": "v440"} |
Send bare digits: "440". A v prefix returns a 400 with Invalid value for 'grouperVersion'. |
Using presentOnAdmission instead of poa |
{"code": "I213", "presentOnAdmission": "Y"} |
The field is poa. The longer name is rejected with a 400, not silently ignored. |
| Shorthand discharge status | {"dischargeStatus": "HOME"} |
The grouper needs its exact constant. A routine discharge to home is "HOME_SELFCARE_ROUTINE". |
Omitting ageInYears |
{"sex": "MALE", "dischargeStatus": "..."} |
The request succeeds, but the patient is treated as age 0 and the claim runs through neonatal logic. Always send an age for adult claims. |
| Trusting a 200 response | if (res.ok) use(res.finalDrg) |
A grouper rejection still returns 200. Gate on finalGrouperReturnCode === "OK" before using the DRG. |
Expecting "No CC/MCC" |
finalSeverity === "No CC/MCC" |
The wire value is "NON_CC". "NONE" appears on ungroupable claims. |
The API publishes a machine-readable OpenAPI 3.0 document. Point your client generator, Postman, or coding agent at it rather than transcribing this page by hand — it is generated from the running service, so it never drifts.
| JSON spec | api.revenuecycle.works/v3/api-docs |
| YAML spec | api.revenuecycle.works/v3/api-docs.yaml |
| Interactive browser | api.revenuecycle.works/swagger-ui/index.html |
Questions about integration? Contact our support team or try the free web tool.