If you are building healthcare claims software and have never parsed an X12 835 file at 2 AM trying to figure out why adjustment reason code CO-45 posted to the wrong bucket, this guide is for you. And if you have done that, this guide is still for you — because the entire EDI ecosystem is poorly documented, scattered across expensive implementation guides, and full of vendor-specific quirks that no specification covers.
Healthcare Electronic Data Interchange (EDI) is the backbone of every claims transaction in the United States. Over $4 trillion in healthcare spending flows through EDI transactions annually. HIPAA mandated these electronic formats in 1996, and the ASC X12 standard defines every segment, element, and code value. Yet finding a developer-focused guide that explains how these transactions actually work in production — with real segment structures, parsing code, and integration patterns — is nearly impossible.
This guide covers the six core EDI transaction sets you will encounter when building healthcare payer integrations: 837 (claims), 835 (remittance), 270/271 (eligibility), 276/277 (claim status), and 278 (prior authorization). We include actual segment structures, Python parsing examples, FHIR mapping patterns, and the clearinghouse integration architecture that connects it all.
What Is EDI in Healthcare?
Electronic Data Interchange (EDI) in healthcare is a standardized format for exchanging administrative and financial data between providers, payers, clearinghouses, and other covered entities. The standard is maintained by the Accredited Standards Committee X12 (ASC X12), and HIPAA mandates its use for specific transaction types.
The X12 Standard
ASC X12 defines the syntax, structure, and semantics for healthcare electronic transactions. The current version used in US healthcare is 005010 (sometimes written as 5010), which replaced the older 004010 version in 2012. Every X12 transaction follows a hierarchical envelope structure:
- ISA/IEA — Interchange envelope (outermost wrapper, identifies sender/receiver)
- GS/GE — Functional group (groups related transactions by type)
- ST/SE — Transaction set (individual transaction like a single claim or remittance)
Each segment within the transaction consists of an identifier (like CLM or SV1) followed by data elements separated by a delimiter (typically *). Segments are terminated by a segment terminator (typically ~). This delimiter-based format makes EDI files compact but notoriously difficult to read without a parser.
HIPAA-Mandated Transactions
HIPAA's Administrative Simplification provisions mandate electronic formats for these transaction types:
| Transaction Set | X12 Code | Purpose | Direction |
|---|---|---|---|
| Health Care Claim | 837P / 837I / 837D | Submit claims for payment | Provider to Payer |
| Health Care Claim Payment/Advice | 835 | Explain payments and adjustments | Payer to Provider |
| Eligibility Inquiry | 270 | Check patient insurance coverage | Provider to Payer |
| Eligibility Response | 271 | Return coverage/benefit information | Payer to Provider |
| Claim Status Inquiry | 276 | Check status of submitted claim | Provider to Payer |
| Claim Status Response | 277 | Return claim processing status | Payer to Provider |
| Authorization Request | 278 | Request/respond to prior auth | Bidirectional |
| Enrollment/Disenrollment | 834 | Communicate enrollment changes | Sponsor to Payer |
| Payment/Remittance | 820 | Premium payment | Sponsor to Payer |
Every covered entity (healthcare provider, health plan, or clearinghouse) must be able to send and receive these transactions in the standard X12 format. Non-compliance carries penalties under HIPAA enforcement.
EDI 837: Healthcare Claims
The 837 transaction set is the electronic equivalent of the CMS-1500 (professional) or UB-04 (institutional) paper claim form. It contains everything a payer needs to adjudicate a claim: patient demographics, provider information, diagnosis codes, procedure codes, charges, and supporting documentation references.
837P: Professional Claims
The 837P (Professional) transaction is defined in implementation guide 005010X222A1. It is used for claims from physicians, outpatient clinics, ambulatory surgery centers, and other non-institutional providers. The key segments are:
ISA*00* *00* *ZZ*SENDER_ID *ZZ*RECEIVER_ID *260319*1430*^*00501*000000001*0*P*:~
GS*HC*SENDER_CODE*RECEIVER_CODE*20260319*1430*1*X*005010X222A1~
ST*837*0001*005010X222A1~
BHT*0019*00*CLAIM_REF_001*20260319*1430*CH~
NM1*85*1*SMITH*JOHN****XX*1234567890~
N3*123 MEDICAL DRIVE~
N4*CHICAGO*IL*60601~
REF*EI*123456789~
NM1*87*2*BILLING GROUP INC****XX*9876543210~
HL*1**20*1~
NM1*IL*1*DOE*JANE****MI*XYZ123456789~
N3*456 PATIENT STREET~
N4*CHICAGO*IL*60602~
DMG*D8*19850115*F~
SBR*P*18*GROUP_PLAN_001******CI~
CLM*PATIENT_ACCT_001*250***11:B:1*Y*A*Y*Y~
HI*ABK:J06.9~
DTP*472*D8*20260315~
LX*1~
SV1*HC:99213*125*UN*1***1~
DTP*472*D8*20260315~
LX*2~
SV1*HC:87880*85*UN*1***1~
DTP*472*D8*20260315~
REF*6R*LINE_REF_002~
SE*25*0001~
GE*1*1~
IEA*1*000000001~ The critical segments to understand:
- CLM (Claim Information) — Contains the patient account number, total charge amount, place of service code (11 = office, 21 = inpatient hospital, 22 = outpatient hospital), and claim filing indicator
- SV1 (Professional Service Line) — Each line item with CPT/HCPCS code, charge amount, unit count, and modifier. The format is
SV1*HC:CPT_CODE:MODIFIER*CHARGE*UN*UNITS***DIAGNOSIS_POINTER~ - HI (Health Care Diagnosis Code) — ICD-10 diagnosis codes.
ABKqualifier = principal diagnosis,ABF= other diagnosis - NM1*85 (Billing Provider) — NPI and name of the billing entity
- NM1*IL (Subscriber) — Member/subscriber identification
837I: Institutional Claims
The 837I (Institutional) transaction is defined in implementation guide 005010X223A2. Used by hospitals, skilled nursing facilities, home health agencies, and other institutional providers. The critical difference from 837P:
- SV2 replaces SV1 — Uses revenue codes instead of CPT codes as the primary identifier. Format:
SV2*REVENUE_CODE*HC:HCPCS_CODE*RATE*UN*UNITS~ - CL1 (Institutional Claim Code) — Contains admission type, admission source, and patient status codes unique to facility claims
- Type of Bill (TOB) — A 3-digit code embedded in the CLM segment indicating facility type (hospital, SNF, HHA), bill classification (inpatient, outpatient), and frequency (admit, interim, final)
837D: Dental Claims
The 837D follows a similar structure but uses dental-specific procedure codes (CDT codes) and has segments for tooth number, oral cavity designation, and dental-specific qualifying information. Defined in 005010X224A2.
EDI 835: Remittance Advice (ERA)
The 835 transaction set is the Electronic Remittance Advice (ERA). It tells you exactly what the payer paid, what they adjusted, why they adjusted it, and what the patient owes. This is the transaction that drives payment posting automation in every practice management and billing system.
835 Segment Structure
The 835 is defined in implementation guide 005010X221A1. Here is a simplified example:
ISA*00* *00* *ZZ*PAYER_ID *ZZ*PROVIDER_ID *260319*0800*^*00501*000000001*0*P*:~
GS*HP*PAYER_CODE*PROVIDER_CODE*20260319*0800*1*X*005010X221A1~
ST*835*0001~
BPR*I*1250.00*C*ACH*CCP*01*999999999*DA*123456789*1234567890**01*999888777*DA*987654321*20260319~
TRN*1*TRACE_NUMBER_001*1234567890~
DTM*405*20260319~
N1*PR*BLUE CROSS BLUE SHIELD~
N1*PE*DR SMITH MEDICAL GROUP*XX*1234567890~
CLP*PATIENT_ACCT_001*1*250.00*125.00**12*PAYER_CLM_001*11~
NM1*QC*1*DOE*JANE~
SVC*HC:99213*125.00*100.00**1~
CAS*CO*45*25.00~
CAS*PR*2*25.00~
DTM*472*20260315~
SVC*HC:87880*85.00*25.00**1~
CAS*CO*45*35.00~
CAS*PR*1*25.00~
DTM*472*20260315~
CLP*PATIENT_ACCT_002*4*500.00*0.00**12*PAYER_CLM_002*21~
CAS*CO*4*500.00~
SE*20*0001~
GE*1*1~
IEA*1*000000001~ The critical segments for auto-posting:
- BPR (Beginning Segment for Payment) — Contains payment type (I=remittance info), total payment amount, payment method (ACH, check), and banking information for EFT reconciliation
- TRN (Trace Number) — The check number or EFT trace number used to match the ERA to a bank deposit
- CLP (Claim Level) — Maps to an individual claim. Contains the patient account number (for matching to your internal claim), claim status (1=processed, 2=denied, 4=denied, 22=reversed), charged amount, paid amount, and payer claim control number
- SVC (Service Line) — Individual procedure line within a claim. Shows what was billed, what was paid, and the units
- CAS (Claim Adjustment Segment) — The adjustment details. Each CAS segment contains an adjustment group code and one or more reason code/amount pairs
CAS Adjustment Group Codes
Understanding CAS group codes is essential for correct payment posting:
| Group | Code | Meaning | Posting Action |
|---|---|---|---|
| CO | Contractual Obligation | Payer's contractual write-off | Write off — do NOT bill patient |
| PR | Patient Responsibility | Patient owes this amount | Transfer to patient balance |
| OA | Other Adjustment | Neither contractual nor patient | Review individually |
| PI | Payer Initiated | Payer-initiated reduction | Review — may warrant appeal |
| CR | Correction/Reversal | Correcting a prior payment | Reverse and repost |
Auto-Posting Logic
Here is the decision logic most billing systems implement for ERA auto-posting:
- Match CLP to internal claim — Use the patient account number (CLP01) or payer claim number (CLP07) to find the matching claim in your system
- Check claim status — CLP02 value: 1/2 = primary payer processed, 19 = processed primary forwarded to secondary, 22 = reversal, 4 = denied
- Process each SVC line — Match procedure code to the claim's line items
- Apply CAS adjustments — CO amounts = contractual write-off, PR amounts = patient responsibility, OA/PI = flag for review
- Post insurance payment — The SVC paid amount goes to insurance payment bucket
- Transfer patient responsibility — Sum of PR adjustment amounts moves to patient balance
- Handle denials — CLP02 = 4 or CAS with denial reason codes (CO-4, CO-16, CO-97) route to denial work queue
- Reconcile to deposit — Match BPR payment amount + TRN trace to bank deposit
EDI 270/271: Eligibility Verification
The 270 (Eligibility Inquiry) and 271 (Eligibility Response) transaction pair is used to verify a patient's insurance coverage before rendering services. This is the most frequently executed EDI transaction — most practices run eligibility checks for every scheduled appointment.
270 Request Structure
ISA*00* *00* *ZZ*PROVIDER_ID *ZZ*PAYER_ID *260319*1000*^*00501*000000001*0*P*:~
GS*HS*PROVIDER_CODE*PAYER_CODE*20260319*1000*1*X*005010X279A1~
ST*270*0001*005010X279A1~
BHT*0022*13*ELIG_REQ_001*20260319*1000~
HL*1**20*1~
NM1*PR*2*BLUE CROSS BLUE SHIELD****PI*12345~
HL*2*1*21*1~
NM1*1P*2*DR SMITH MEDICAL GROUP****XX*1234567890~
HL*3*2*22*0~
NM1*IL*1*DOE*JANE****MI*XYZ123456789~
DMG*D8*19850115*F~
DTP*291*D8*20260319~
EQ*30~
SE*13*0001~
GE*1*1~
IEA*1*000000001~ Key elements: NM1*IL identifies the subscriber with member ID, DTP*291 specifies the date of service for eligibility, and EQ*30 requests health benefit plan coverage information (code 30 = all plan information).
271 Response Structure
The 271 response returns detailed benefit information through EB (Eligibility/Benefit) segments:
EB*1*IND*30*CI*PREMIER PLAN~
EB*C*IND*30**25.00~
EB*G*IND*30**500.00****23*20260101-20261231~
EB*G*IND*30**350.00***Y*23*20260101-20261231~ EB segment element meanings:
- EB01 — Eligibility code: 1=Active Coverage, 6=Inactive, 8=Not Covered, C=Co-Payment, G=Deductible
- EB02 — Coverage level: IND=Individual, FAM=Family, EMP=Employee, CHD=Child
- EB03 — Service type code: 30=Health Benefit Plan, 98=Professional Physician Visit, AL=Vision
- EB05 — Amount (co-pay amount, deductible amount, etc.)
- EB07 — Percentage (for coinsurance)
Real-Time vs. Batch Eligibility
Most clearinghouses support both modes:
- Real-time (270/271) — Send request, get response within 5-30 seconds. Used for front-desk eligibility checks at patient check-in. Requires HTTPS/API connectivity to clearinghouse
- Batch (270/271) — Upload a file of multiple 270 requests via SFTP, receive batch 271 response file within 1-24 hours. Used for pre-scheduling eligibility runs for the next day's appointments
For developers building eligibility verification into scheduling systems, the real-time approach via clearinghouse REST APIs is preferred. Most modern clearinghouses (Availity, Waystar, Change Healthcare) offer JSON-based APIs that abstract the X12 format while still returning the same data elements.
EDI 276/277: Claim Status
The 276 (Claim Status Inquiry) and 277 (Claim Status Response) transaction pair allows you to check the processing status of a submitted claim. This is the transaction behind every "claim status" button in a billing system.
276 Inquiry Structure
ST*276*0001*005010X212~
BHT*0010*13*STATUS_REQ_001*20260319*1000~
HL*1**20*1~
NM1*PR*2*BLUE CROSS BLUE SHIELD****PI*12345~
HL*2*1*21*1~
NM1*41*2*DR SMITH MEDICAL GROUP****46*123456789~
HL*3*2*22*0~
NM1*IL*1*DOE*JANE****MI*XYZ123456789~
DMG*D8*19850115*F~
TRN*1*PATIENT_ACCT_001*1234567890~
REF*D9*PAYER_CLM_001~
AMT*T3*250.00~
DTP*472*RD8*20260315-20260315~
SE*14*0001~ 277 Response — Status Category Codes
The 277 response uses Status Category Codes (STC) that indicate where the claim is in the adjudication pipeline:
| STC Code | Category | Meaning | Developer Action |
|---|---|---|---|
| A0 | Acknowledgment | Forwarded to payer — accepted | No action, wait for adjudication |
| A1 | Acknowledgment | Receipt acknowledged | Mark as received by payer |
| A2 | Acceptance | Accepted for adjudication | Update status to "in processing" |
| A3 | Rejected | Rejected — needs correction | Route to correction queue |
| P0 | Pending | Adjudication pending | Re-check in 5-10 business days |
| P1 | Pending | Pending — additional info requested | Alert billing team, retrieve request |
| P2 | Pending | Pending — in review | Monitor, no action needed yet |
| F0 | Finalized | Payment or denial finalized | Expect ERA (835) soon |
| F1 | Finalized | Denied | Route to denial management |
| F2 | Finalized | Paid | Match to incoming ERA |
| R0-R16 | Request | Additional information requested | Retrieve specific request, respond |
For developers building claim tracking dashboards, the 276/277 cycle should be automated: poll claim status 14 days after submission, then every 7 days until finalized. Many billing systems implement this as a background job that runs nightly against all outstanding claims.
EDI 278: Prior Authorization
The 278 transaction set handles prior authorization (pre-certification) requests and responses. This is the electronic equivalent of calling a payer to request authorization for a procedure or referral.
278 Request Structure
The 278 request includes:
- UM (Health Care Services Review Information) — Request type (HS=Health Services Review), certification type (I=Initial, R=Renewal, A=Appeal), and service type
- HCR (Health Care Services Review) — In the response, contains the certification decision: A1=Certified, A2=Certified with modifications, A3=Not Certified, A6=Pended
- SV1/SV2 — Service lines being requested (same structure as 837 service lines)
- DTP*472 — Requested date range for the authorization
- HI — Diagnosis codes supporting medical necessity
ST*278*0001*005010X217~
BHT*0007*11*AUTH_REQ_001*20260319*1000~
HL*1**20*1~
NM1*X3*2*BLUE CROSS BLUE SHIELD****PI*12345~
HL*2*1*21*1~
NM1*1P*2*DR SMITH MEDICAL GROUP****XX*1234567890~
HL*3*2*22*1~
NM1*IL*1*DOE*JANE****MI*XYZ123456789~
DMG*D8*19850115*F~
HL*4*3*EV*1~
UM*HS*I*3~
HI*ABK:M54.5~
DTP*472*RD8*20260401-20260430~
SV1*HC:72148*1200*UN*1~
SE*16*0001~ 278 Response — Authorization Decisions
The authorization response returns in HCR segment:
- A1 — Certified: Authorization granted. Extract the authorization number from REF*BB segment and store it for claim submission
- A2 — Certified with modifications: Authorized but with changes (different dates, fewer units). Review the modified SV1 segments
- A3 — Not certified: Denied. Route to appeal workflow. The HCR segment may include a denial reason
- A6 — Pended: Additional clinical information needed. Extract the request from the 278 response and trigger clinical documentation workflow
When building prior auth automation, store the authorization number (REF*BB) and certified date range. The authorization number must be included in the subsequent 837 claim submission in the REF*G1 segment — missing this is one of the most common claim rejection causes.
Parsing EDI in Code
Parsing X12 EDI files requires understanding the delimiter structure and segment hierarchy. Here is a practical Python approach.
Basic X12 Parser
import re
from typing import Dict, List, Optional
from dataclasses import dataclass, field
@dataclass
class Segment:
segment_id: str
elements: List[str]
def element(self, index: int, default: str = "") -> str:
if 0 < index < len(self.elements):
return self.elements[index]
return default
def __repr__(self):
return f"{self.segment_id}*{'*'.join(self.elements[1:])}"
class X12Parser:
def __init__(self, raw: str):
self.raw = raw.strip()
self.element_sep = "*"
self.segment_sep = "~"
self.sub_element_sep = ":"
self.segments: List[Segment] = []
self._detect_delimiters()
self._parse()
def _detect_delimiters(self):
if self.raw[:3] != "ISA":
raise ValueError("Not a valid X12 file: must start with ISA")
self.element_sep = self.raw[3]
self.sub_element_sep = self.raw[104]
self.segment_sep = self.raw[105]
def _parse(self):
clean = self.raw.replace("\n", "").replace("\r", "")
raw_segments = clean.split(self.segment_sep)
for raw_seg in raw_segments:
raw_seg = raw_seg.strip()
if not raw_seg:
continue
elements = raw_seg.split(self.element_sep)
self.segments.append(Segment(
segment_id=elements[0],
elements=elements
))
def get_segments(self, segment_id: str) -> List[Segment]:
return [s for s in self.segments if s.segment_id == segment_id]
def get_segment(self, segment_id: str) -> Optional[Segment]:
matches = self.get_segments(segment_id)
return matches[0] if matches else None
def get_transaction_sets(self) -> List[List[Segment]]:
sets = []
current = []
in_set = False
for seg in self.segments:
if seg.segment_id == "ST":
in_set = True
current = [seg]
elif seg.segment_id == "SE":
current.append(seg)
sets.append(current)
in_set = False
elif in_set:
current.append(seg)
return sets
# Usage:
# parser = X12Parser(open("claim_837.edi").read())
# for seg in parser.get_segments("CLM"):
# print(f"Claim: {seg.element(1)}, Amount: {seg.element(2)}") 835 ERA Parser — Extract Payments
@dataclass
class PaymentLine:
procedure_code: str
charged: float
paid: float
adjustments: List[Dict]
service_date: str = ""
@dataclass
class ClaimPayment:
patient_account: str
status: str
total_charged: float
total_paid: float
payer_claim_number: str
lines: List[PaymentLine] = field(default_factory=list)
adjustments: List[Dict] = field(default_factory=list)
def parse_835(raw_edi: str) -> List[ClaimPayment]:
parser = X12Parser(raw_edi)
claims = []
current_claim = None
current_line = None
STATUS_MAP = {
"1": "processed_primary",
"2": "processed_secondary",
"3": "processed_tertiary",
"4": "denied",
"19": "processed_primary_fwd_secondary",
"22": "reversal",
}
for seg in parser.segments:
if seg.segment_id == "CLP":
if current_claim:
claims.append(current_claim)
current_claim = ClaimPayment(
patient_account=seg.element(1),
status=STATUS_MAP.get(seg.element(2), seg.element(2)),
total_charged=float(seg.element(3, "0")),
total_paid=float(seg.element(4, "0")),
payer_claim_number=seg.element(7, ""),
)
current_line = None
elif seg.segment_id == "SVC" and current_claim:
proc_info = seg.element(1, "").split(":")
proc_code = proc_info[1] if len(proc_info) > 1 else proc_info[0]
current_line = PaymentLine(
procedure_code=proc_code,
charged=float(seg.element(2, "0")),
paid=float(seg.element(3, "0")),
adjustments=[],
)
current_claim.lines.append(current_line)
elif seg.segment_id == "CAS":
group = seg.element(1) # CO, PR, OA, PI, CR
adj_list = current_line.adjustments if current_line else (
current_claim.adjustments if current_claim else []
)
# CAS can have up to 6 reason/amount pairs
i = 2
while i < len(seg.elements) - 1:
reason = seg.element(i)
amount = float(seg.element(i + 1, "0"))
if reason:
adj_list.append({
"group": group,
"reason": reason,
"amount": amount,
})
i += 3 # Each triplet: reason, amount, quantity
elif seg.segment_id == "DTM" and seg.element(1) == "472":
if current_line:
current_line.service_date = seg.element(2, "")
if current_claim:
claims.append(current_claim)
return claims Useful Python Libraries
- pyx12 — Full X12 validation and parsing library. Validates against official X12 implementation guides. Open source.
pip install pyx12 - edi-835-parser — Focused 835 parser that extracts payment data into structured objects.
pip install edi-835-parser - python-hl7 — While primarily for HL7 v2, useful when bridging HL7 and X12 systems.
pip install python-hl7 - tigershark — X12 parser and generator. Generates Python classes from X12 implementation guides
- stedi — Commercial API that handles EDI parsing, validation, and generation as a service. Good for teams that do not want to maintain parsers
EDI-to-FHIR Transformation
As healthcare moves toward FHIR-based interoperability, transforming EDI transactions to FHIR resources becomes critical. The FHIR Financial Module and the CARIN Alliance Blue Button Implementation Guide define the mappings.
837 CLM to FHIR Claim Resource
{
"resourceType": "Claim",
"identifier": [{
"system": "https://provider.example.org/claims",
"value": "PATIENT_ACCT_001"
}],
"status": "active",
"type": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/claim-type",
"code": "professional"
}]
},
"patient": {
"reference": "Patient/jane-doe"
},
"provider": {
"reference": "Organization/dr-smith-medical"
},
"priority": {"coding": [{"code": "normal"}]},
"diagnosis": [{
"sequence": 1,
"diagnosisCodeableConcept": {
"coding": [{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "J06.9"
}]
}
}],
"insurance": [{
"sequence": 1,
"focal": true,
"coverage": {"reference": "Coverage/bcbs-xyz123"}
}],
"item": [{
"sequence": 1,
"productOrService": {
"coding": [{
"system": "http://www.ama-assn.org/go/cpt",
"code": "99213"
}]
},
"unitPrice": {"value": 125.00, "currency": "USD"},
"quantity": {"value": 1}
}],
"total": {"value": 250.00, "currency": "USD"}
} 835 CLP/SVC/CAS to FHIR ExplanationOfBenefit
The ExplanationOfBenefit (EOB) resource is the FHIR equivalent of the 835 ERA. Each CLP claim maps to one EOB, with SVC lines mapping to EOB.item and CAS adjustments mapping to EOB.item.adjudication:
{
"resourceType": "ExplanationOfBenefit",
"identifier": [{
"value": "PAYER_CLM_001"
}],
"status": "active",
"type": {"coding": [{"code": "professional"}]},
"outcome": "complete",
"patient": {"reference": "Patient/jane-doe"},
"insurer": {"reference": "Organization/bcbs"},
"item": [{
"sequence": 1,
"productOrService": {
"coding": [{"code": "99213"}]
},
"adjudication": [
{
"category": {"coding": [{"code": "submitted"}]},
"amount": {"value": 125.00}
},
{
"category": {"coding": [{"code": "benefit"}]},
"amount": {"value": 100.00}
},
{
"category": {"coding": [{"code": "deductible"}]},
"amount": {"value": 0.00}
},
{
"category": {"coding": [{"code": "copay"}]},
"reason": {"coding": [{"code": "CO-45"}]},
"amount": {"value": 25.00}
}
]
}],
"total": [
{"category": {"coding": [{"code": "submitted"}]}, "amount": {"value": 250.00}},
{"category": {"coding": [{"code": "benefit"}]}, "amount": {"value": 125.00}}
],
"payment": {
"amount": {"value": 125.00}
}
} Key Mapping References
- CARIN Blue Button IG —
hl7.org/fhir/us/carin-bb— Defines the EOB profile for payer-to-consumer data exchange - Da Vinci PDex —
hl7.org/fhir/us/davinci-pdex— Payer Data Exchange for payer-to-payer and payer-to-provider scenarios - Da Vinci PAS —
hl7.org/fhir/us/davinci-pas— Prior Authorization Support using FHIR instead of 278 - FHIR Financial Module — Core Claim, ClaimResponse, ExplanationOfBenefit, Coverage, and CoverageEligibilityRequest/Response resources
Clearinghouse Integration
Clearinghouses sit between providers and payers, handling routing, validation, format translation, and connectivity. Unless you are a very large health system with direct payer connections, you will connect to payers through a clearinghouse.
Availity
Availity is the largest multi-payer network with 95+ direct payer connections. Their integration options:
- Availity Essentials API — RESTful JSON API with OAuth 2.0 authentication. Supports real-time 270/271, 276/277, and 278. Claims (837) are typically batch via SFTP. Documentation at
developer.availity.com - SFTP Batch — Upload X12 flat files to your dedicated SFTP directory. Files are picked up, validated, and routed. Response files (835, 271, 277) appear in your download directory within hours
- Availity API Marketplace — Third-party integrations built on Availity's connectivity. Useful for niche payer-specific workflows
Change Healthcare (Optum)
Change Healthcare processes 15 billion transactions annually. Post-acquisition by UnitedHealth Group (Optum), they are consolidating platforms:
- ConnectCenter API — REST API for eligibility, claim status, and remittance. API key authentication. Supports both X12 and JSON payloads
- Relay Health Exchange — SFTP-based file exchange for batch claims and remittance. Mature platform with extensive payer routing rules
- FHIR APIs — Growing FHIR R4 support aligned with Da Vinci implementation guides. Primarily for payer-facing data exchange
Waystar
Waystar's platform processes claims for over 1 million providers:
- Platform API v2 — REST API with JWT authentication. Supports eligibility, claim status, prior auth, and denial intelligence. Webhook support for real-time event notifications
- File Gateway — SFTP batch file exchange supporting X12 and Waystar proprietary formats. ERA auto-download capability
- AltitudeAI Integration — API access to Waystar's AI features: predictive denial scoring, automated appeal generation, financial clearance recommendations
Choosing a Connection Method
For new integrations, follow this decision framework:
- Start with SFTP batch — It is the simplest to implement and every clearinghouse supports it. Upload 837 files, download 835/271/277 files on a schedule
- Add real-time APIs for eligibility — The 270/271 workflow benefits most from real-time. Integrate the clearinghouse REST API for appointment-time eligibility checks
- Move claims to API when volume justifies — API-based claim submission gives you immediate validation feedback and claim tracking. Worth the investment at 500+ claims/month
- Implement webhooks for ERA — Instead of polling for 835 files, use webhooks to be notified when new ERAs are available. Reduces processing latency from hours to minutes
Testing EDI Transactions
Testing EDI integrations requires specialized environments, sample files, and validation tools. Here is the practical approach.
Test Environments
- Clearinghouse sandboxes — Availity, Change Healthcare, and Waystar all offer sandbox environments with test payer IDs. Apply for developer access through their portals
- CMS HETS — The HIPAA Eligibility Transaction System for testing Medicare/Medicaid 270/271 transactions. Available at
cms.gov/CCIIO/Programs-and-Initiatives/Health-Insurance-Marketplaces/HETS - WEDI (Workgroup for Electronic Data Interchange) — Industry resources and testing guidance at
wedi.org
Sample Files
Generate valid test X12 files with:
- X12 sample files from implementation guides — The official ASC X12 implementation guides (purchased from
x12.org) include example transactions for each transaction set - Stedi EDI Inspector — Free online tool at
edi.stedi.comthat validates, formats, and generates sample X12 files - CMS sample files — CMS publishes sample 837, 835, and other transaction files for Medicare testing
Validation Tools
- EDI Notepad — Free Windows tool for viewing and editing X12 files with segment highlighting and element labeling
- Stedi EDI Inspector — Browser-based validator that checks X12 compliance against implementation guides. Free tier available
- pyx12 validator — Open-source Python library that validates X12 against implementation guides. Use for automated CI/CD validation of generated EDI files
- Edifecs — Enterprise-grade EDI testing platform used by major clearinghouses and payers
Unit Testing Strategy
import unittest
class TestEDI835Parser(unittest.TestCase):
def setUp(self):
with open("test_data/sample_835.edi") as f:
self.raw_835 = f.read()
def test_parse_payment_amount(self):
claims = parse_835(self.raw_835)
self.assertEqual(len(claims), 2)
self.assertEqual(claims[0].total_paid, 125.00)
self.assertEqual(claims[1].total_paid, 0.00)
def test_denied_claim_status(self):
claims = parse_835(self.raw_835)
denied = [c for c in claims if c.status == "denied"]
self.assertEqual(len(denied), 1)
self.assertEqual(denied[0].patient_account, "PATIENT_ACCT_002")
def test_adjustment_parsing(self):
claims = parse_835(self.raw_835)
line = claims[0].lines[0]
co_adjs = [a for a in line.adjustments if a["group"] == "CO"]
pr_adjs = [a for a in line.adjustments if a["group"] == "PR"]
self.assertEqual(len(co_adjs), 1)
self.assertEqual(co_adjs[0]["reason"], "45")
self.assertEqual(co_adjs[0]["amount"], 25.00)
self.assertEqual(len(pr_adjs), 1)
self.assertEqual(pr_adjs[0]["amount"], 25.00)
def test_claim_matching(self):
claims = parse_835(self.raw_835)
accounts = [c.patient_account for c in claims]
self.assertIn("PATIENT_ACCT_001", accounts)
def test_delimiter_detection(self):
parser = X12Parser(self.raw_835)
self.assertEqual(parser.element_sep, "*")
self.assertEqual(parser.segment_sep, "~") Common EDI Errors and Fixes
After years of building EDI integrations, these are the errors that cause the most pain and the fixes that save the most time.
999 Implementation Acknowledgment Errors
The 999 (or older 997) transaction is the acknowledgment that your EDI file was received and structurally valid. If you get a 999 with rejections, your file never made it to the payer — fix these first:
- AK905 = R (Rejected): The entire transaction set was rejected. Check IK3/IK4 segments for specific segment and element errors
- IK304 = 1 (Unrecognized Segment): You included a segment that does not exist in the implementation guide. Common cause: using 4010 segments in a 5010 file
- IK304 = 3 (Required Segment Missing): A mandatory segment was omitted. Most common: missing NM1*85 (billing provider), missing HI (diagnosis), or missing DTP (service date)
- IK403 = 1 (Required Element Missing): A mandatory data element within a segment is empty. Common: missing NPI in NM1*85, missing member ID in NM1*IL
- IK403 = 6 (Invalid Character): X12 allows only alphanumeric characters and a limited set of special characters in most elements. Common culprits: curly quotes, em-dashes, non-ASCII characters from copy-paste
TA1 Interchange Errors
The TA1 is an interchange-level acknowledgment. If you get a TA1 rejection, the problem is in your ISA/IEA envelope — the clearinghouse could not even read the contents:
- TA1-009 (Unknown receiver): Your ISA06 receiver ID is not registered with the clearinghouse. Contact the clearinghouse to register your sender/receiver trading partner agreement
- TA1-014 (Invalid date): ISA09 must be YYMMDD format. Double-check your date formatting
- TA1-022 (Invalid characters): Non-allowed characters in the ISA envelope. The ISA segment has fixed-length fields — extra or missing characters shift everything
Top Claim-Level Rejections
These are the rejections that come back from the payer (not the clearinghouse). They mean your file structure was valid but the claim data has issues:
- Invalid/Missing NPI — Verify the billing provider NPI at
npiregistry.cms.hhs.gov. Ensure the NPI is 10 digits and active. If the NPI is valid but still rejected, the provider may not be enrolled with that payer - Subscriber ID mismatch — The member ID in NM1*IL does not match the payer's records. Run a 270/271 eligibility check before submitting claims to catch these early
- Duplicate claim — CLM segment patient account number + service date + procedure code matches an existing claim. Add a frequency code in CLM-05 for corrected claims (frequency 7 = replacement, 8 = void)
- Missing diagnosis pointer — SV1 service line references a diagnosis position that does not exist in the HI segment. Each SV1 diagnosis pointer must point to a valid HI line
- Timely filing — Claim was submitted past the payer's filing deadline. Filing limits vary: Medicare = 12 months, commercial payers = 90-365 days. No fix except prevention
Debugging Checklist
When an EDI transaction fails, follow this order:
- Check the 999/TA1 acknowledgment first — is it a structural or data issue?
- Use an EDI viewer (Stedi Inspector, EDI Notepad) to format and highlight the file
- Verify delimiters — is your segment terminator consistent throughout?
- Count your segments — does the SE segment count match the actual segment count?
- Validate control numbers — ISA13, GS06, and ST02 must be unique per file
- Check the implementation guide version — 005010 is current; submitting 004010 format will be rejected
- Compare against a known-good file — use a previously accepted transaction as a template
- Contact the clearinghouse support desk — they see these errors daily and can often identify the issue in minutes
Production Implementation Checklist
Before going live with EDI transactions in production, verify every item on this checklist:
| Category | Checklist Item | Notes |
|---|---|---|
| Trading Partners | Registered sender/receiver IDs with clearinghouse | ISA05/06 + ISA07/08 must be registered |
| Trading Partners | Completed trading partner agreement (TPA) | Legal document required by most clearinghouses |
| Trading Partners | Test transactions accepted in sandbox | Submit test 837, receive test 999/835 |
| Connectivity | SFTP credentials + host verified | Test upload/download cycle end to end |
| Connectivity | API credentials + OAuth flow tested | For real-time 270/271, 276/277 |
| Connectivity | Firewall rules for clearinghouse IPs | Whitelist both inbound and outbound |
| Security | HIPAA BAA signed with clearinghouse | Required before transmitting PHI |
| Security | TLS 1.2+ enforced on all connections | No unencrypted EDI transmission |
| Security | PHI logging controls in place | Do not log full EDI content in plain text |
| Validation | Outbound 837 validation against 5010 IG | Use pyx12 or Stedi for pre-submission validation |
| Validation | Inbound 835 parser tested with 10+ real ERAs | Cover all CAS group/reason code combos |
| Validation | 999/TA1 processing automated | Auto-route rejections to correction queue |
| Operations | Monitoring + alerting on failed transactions | Alert within 1 hour of 999 rejection |
| Operations | Retry logic for transient failures | Exponential backoff, max 3 retries |
| Operations | Archival of all sent/received EDI files | HIPAA requires 6-year retention |
Frequently Asked Questions
What is the difference between EDI 837P and 837I?
EDI 837P (Professional) is used for claims from physicians, outpatient clinics, and non-institutional providers — it uses SV1 segments with CPT/HCPCS codes and Place of Service codes. EDI 837I (Institutional) is used for hospital, skilled nursing, and facility claims — it uses SV2 segments with revenue codes, CL1 admission data, and Type of Bill codes. The 837P maps to the CMS-1500 paper form while the 837I maps to the UB-04 form. Both follow the X12 005010 standard but use different implementation guides (005010X222A1 for 837P, 005010X223A2 for 837I).
How do I parse an EDI 835 remittance file in Python?
You can parse EDI 835 files using the edi-835-parser library (pip install edi-835-parser) for a quick solution, or build a custom parser using the X12Parser pattern shown in this guide. The key steps are: detect delimiters from the ISA segment (element separator at position 3, segment terminator at position 105), split into segments, then iterate through CLP (claim-level), SVC (service-line), and CAS (adjustment) segments to extract payment data. For production systems, use the pyx12 library which validates against the official 005010X221A1 implementation guide.
What are CAS adjustment reason codes CO-45 and PR-1?
CAS adjustment reason codes appear in EDI 835 remittance files to explain why the paid amount differs from the charged amount. CO-45 means "Charges exceed your contracted/legislated fee arrangement" — this is a contractual write-off that should be automatically written off and never billed to the patient. PR-1 means "Deductible Amount" — this is patient responsibility and should be transferred to the patient's balance. The group codes (CO = Contractual Obligation, PR = Patient Responsibility) determine the posting action, while the reason code (45, 1, etc.) explains why. Full code lists are maintained by the Washington Publishing Company (WPC) at wpc-edi.com.
How do I connect to a clearinghouse for EDI transactions?
Start by selecting a clearinghouse (Availity, Change Healthcare, or Waystar are the three largest). Register as a trading partner to get your sender/receiver IDs and SFTP credentials. For batch transactions (837 claims, 835 ERAs), upload X12 flat files to your SFTP directory and download response files on a schedule. For real-time transactions (270/271 eligibility, 276/277 status), use the clearinghouse REST API with OAuth 2.0 or API key authentication. Most clearinghouses offer sandbox environments for testing — submit test claims, receive test acknowledgments, and verify your parser handles all response scenarios before going to production.
Can I convert EDI transactions to FHIR?
Yes. The FHIR Financial Module defines direct mappings: EDI 837 CLM segments map to FHIR Claim resources, 835 CLP/SVC/CAS segments map to ExplanationOfBenefit resources, 271 EB segments map to Coverage and CoverageEligibilityResponse, and 277 STC segments map to ClaimResponse resources. The CARIN Blue Button Implementation Guide (hl7.org/fhir/us/carin-bb) standardizes the EOB profile, while Da Vinci PAS (hl7.org/fhir/us/davinci-pas) provides a FHIR-native alternative to 278 prior authorization. For production implementations, build a transform layer that reads parsed X12 segments and outputs FHIR JSON — this is the architecture used by most modern clearinghouses and payer platforms.

