The HIPAA 837 claim is the backbone of healthcare billing in the United States. This guide shows you how to convert 837P, 837I, and 837D claims to FHIR R4 using the Redix REST API, with complete working examples in curl, Python, and JavaScript.
Understanding the 837 Claim Family
The 837 transaction set has three variants, each for a different care setting:
| Type | Name | Use Case | FHIR Claim.type | Coding System |
|---|---|---|---|---|
| 837P | Professional | Physician offices, outpatient | professional |
CPT, HCPCS |
| 837I | Institutional | Hospitals and facilities (inpatient and outpatient), SNF | institutional |
Revenue codes; CPT/HCPCS (outpatient); ICD-10-PCS (inpatient procedures) |
| 837D | Dental | Dental practices | oral |
CDT |
FHIR R4 Output Structure
The fhir_bundle member of the API response is a FHIR Bundle containing multiple resources. An illustrative example (the resources present depend on the input):
├── Claim (the main claim resource)
├── Patient (subscriber/patient demographics)
├── Organization (billing provider)
├── Organization (payer)
├── Practitioner (rendering provider)
├── Coverage (insurance information)
└── Condition (diagnosis codes)
Resource Referencing
Important for developers: The Claim resource uses relative references to link to other resources in the same Bundle. For example, Claim.patient contains {"reference": "Patient/patient-001"} which points to the Patient resource with "id": "patient-001" in the same Bundle. Always resolve references within the Bundle context before attempting external FHIR server lookups.
Key FHIR Claim Elements
A high-level guide, not a complete field mapping; exact sources depend on the 837 variant, qualifiers, and the mapping configuration.
| FHIR Element | 837 Source (Loop) | Coding System URI |
|---|---|---|
Claim.type |
The 837 variant (837P, 837I, 837D) | http://terminology.hl7.org/CodeSystem/claim-type |
Claim.patient |
Loop 2010BA NM1 when the subscriber is the patient; Loop 2010CA NM1 for a dependent patient | - |
Claim.provider |
Loop 2010AA NM1 | - |
Claim.diagnosis |
Loop 2300 HI | http://hl7.org/fhir/sid/icd-10-cm (ICD-10-CM claims) |
Claim.item.productOrService |
Loop 2400: SV1 (837P), SV2 (837I procedure code), SV3 (837D) | Depends on the code qualifier: CPT or HCPCS (SV1, SV2), CDT (SV3) |
Claim.item |
Loop 2400 LX and the service line | - |
Standard Coding System URIs:
ICD-10-CM: http://hl7.org/fhir/sid/icd-10-cm
ICD-10-PCS: http://hl7.org/fhir/sid/icd-10-pcs
CPT: http://www.ama-assn.org/go/cpt
HCPCS: https://www.cms.gov/Medicare/Coding/HCPCSReleaseCodeSets
CDT: http://www.ada.org/cdt
Revenue Codes: https://www.nubc.org/CodeSystem/RevenueCodes
Note: Redix's current 837D mapping emits http://ada.org/cdt, and its 837I mapping maps the SV2 procedure code to productOrService without a revenue-code system. Adjust the mapping if your profiles require the HL7 identifiers above.
Quick Start: Convert Your First 837
Install the SDK
Python
pip install redix-client
Node.js
npm install redix-client
Or use curl / any HTTP client directly - no SDK required.
Basic Conversion
curl
Do not send PHI to the public sandbox. With the public demo key, use a Redix sample file (for example, one saved from the X12 to FHIR demo) and the sample header:
curl -X POST "https://demo.redix.com/api/v2/hipaa-to-fhir/convert" \
-H "X-API-Key: demo-key-12345" \
-H "X-Submission-Method: sample" \
-F "file=@redix_sample_837p.x12"
The API auto-detects whether it's 837P, 837I, or 837D. It returns a JSON envelope whose fhir_bundle member contains the FHIR Bundle, alongside metadata and warnings.
Your own files: the public demo key runs Redix sample files only. To convert your own de-identified files, request an evaluation key and use it in place of the demo key. The full examples below read that key from REDIX_API_KEY.
Complete Code Examples
Python: Full Implementation
"""
Convert HIPAA 837 Claims to FHIR R4
Supports: 837P (Professional), 837I (Institutional), 837D (Dental)
"""
import json
import os
from redix_client import RedixClient, RedixAPIError
# Initialize client
client = RedixClient(
base_url=os.getenv("REDIX_API_URL", "https://demo.redix.com"),
# Your evaluation key; the public demo key only runs Redix sample files.
api_key=os.environ["REDIX_API_KEY"] # raises KeyError if not set
)
def convert_837_to_fhir(file_path: str, transaction_type: str = None) -> dict:
"""
Convert an 837 claim file to FHIR R4.
Args:
file_path: Path to the 837 file
transaction_type: Optional ('837-professional', '837-institutional', '837-dental'). Auto-detected if not specified.
Returns:
dict containing FHIR Bundle and metadata
"""
params = {}
if transaction_type:
params["transaction_type"] = transaction_type
result = client.post(
"/api/v2/hipaa-to-fhir/convert",
files={"file": file_path},
params=params
)
return result
# Example usage
if __name__ == "__main__":
result = convert_837_to_fhir("/path/to/837p_claim.x12")
print(f"Transaction Type: {result.get('transaction_type')}")
fhir_bundle = result.get("fhir_bundle")
print(f"Resources Created: {len(fhir_bundle.get('entry', []))}")
# Save FHIR Bundle
with open("fhir_output.json", "w") as f:
json.dump(fhir_bundle, f, indent=2)
JavaScript: Full Implementation
/**
* Convert HIPAA 837 Claims to FHIR R4
* Supports: 837P (Professional), 837I (Institutional), 837D (Dental)
*/
const { RedixClient } = require('redix-client');
const fs = require('fs');
// Your evaluation key; the public demo key only runs Redix sample files.
if (!process.env.REDIX_API_KEY) {
throw new Error('Set REDIX_API_KEY to your Redix evaluation key');
}
const client = new RedixClient({
baseUrl: process.env.REDIX_API_URL || 'https://demo.redix.com',
apiKey: process.env.REDIX_API_KEY
});
async function convert837ToFhir(filePath, transactionType = null) {
const options = { files: { file: filePath } };
if (transactionType) {
options.params = { transaction_type: transactionType };
}
return await client.post('/api/v2/hipaa-to-fhir/convert', options);
}
// Example usage
async function main() {
const result = await convert837ToFhir('/path/to/837p_claim.x12');
console.log(`Transaction Type: ${result.transaction_type}`);
console.log(`Resources: ${result.fhir_bundle.entry?.length || 0}`);
fs.writeFileSync('fhir_output.json', JSON.stringify(result.fhir_bundle, null, 2));
}
main().catch(console.error);
Error Handling and Validation
Before converting, the engine reads the 837 against its 5010 definitions: syntax (segments, elements, order) and inter-segment rules. If the file cannot be converted, the API returns an HTTP error (for example 422) with the engine's error detail, so you can locate the problem segment.
from redix_client import RedixAPIError
try:
result = convert_837_to_fhir("/path/to/file.x12")
except RedixAPIError as e:
if e.status_code == 422:
# The file could not be converted; the detail explains why
print(f"Validation error: {e.message}")
For a full validation report (syntax, inter-segment, and balancing, with a 999 and TA1), run the file through the HIPAA X12 Validator first. See HIPAA X12 Validation Before Submission.
Performance: Single-Pass Architecture
The diagram shows the native conversion core: it reads the X12 once and writes the FHIR Bundle in the same pass, with no JVM. The REST layer around it reads the upload and returns a JSON response, so end-to-end time also includes the HTTP round trip. Actual timing depends on file size, hardware, and configuration; measure it on your own files during evaluation.
FHIR Version and Profiles
The output is FHIR R4 (4.0.1). This guide does not claim conformance to a specific implementation guide such as US Core, CARIN Blue Button, or Da Vinci PDex: validate the output against the profiles your own APIs expose, and adjust the mapping where your profile requires it. The mappings are editable definitions, so profile-specific changes do not need engine changes.
API Reference
| Endpoint | Method | Description |
|---|---|---|
/api/v2/hipaa-to-fhir/convert |
POST | Convert any 837, including 837D (auto-detect) |
/api/v2/hipaa-to-fhir/837-professional |
POST | Convert 837P specifically |
/api/v2/hipaa-to-fhir/837-institutional |
POST | Convert 837I specifically |
/api/v2/hipaa-to-rmap/sample/{type} |
GET | Get sample file content |
Full documentation: demo.redix.com/docs
Frequently Asked Questions
How do I resolve Patient references in a FHIR Claim Bundle?
The Claim.patient element uses a relative reference (e.g., 'Patient/patient-001') that points to a Patient resource with matching id contained in the same Bundle. Always resolve references within the Bundle context before attempting to fetch from an external FHIR server.
What is the difference between 837P, 837I, and 837D?
837P (Professional) is for physician and other professional claims, using CPT/HCPCS codes. 837I (Institutional) is for hospital and facility claims, using revenue codes with CPT/HCPCS for outpatient services and ICD-10-PCS for inpatient procedures. 837D (Dental) is for dental claims using CDT codes. All three convert to FHIR Claim resources with different type codes: professional, institutional, and oral.
What coding system URIs are used in FHIR Claims?
Standard URIs include: ICD-10-CM (http://hl7.org/fhir/sid/icd-10-cm), CPT (http://www.ama-assn.org/go/cpt), HCPCS (https://www.cms.gov/Medicare/Coding/HCPCSReleaseCodeSets), CDT (http://www.ada.org/cdt), and Revenue Codes (https://www.nubc.org/CodeSystem/RevenueCodes). Redix's current 837D mapping emits http://ada.org/cdt, and its 837I mapping does not emit a revenue-code system; mappings can be adjusted where a profile requires the HL7 identifiers.