Schema Care

EOB API: What an Explanation of Benefits Endpoint Returns

By Schema CarePublished Updated

An EOB API should return each explanation of benefits as a payment with one or more claims, and each claim as a set of service lines. Every line needs its billed, allowed, adjusted, patient and paid amounts plus the adjustment and remark codes behind them. Each value should carry a confidence score and the page it came from.

Claim-level fields an EOB API should return

Claim-level fields are the facts that identify one claim on an explanation of benefits (EOB): who the patient is, which payer decided the claim, the payer's claim number, the dates of service and the claim totals. A billing system uses them to find the matching claim before it touches a single line.

The CMS guide to the explanation of benefits lists the facts an EOB shows: a claim number, the patient and plan details, the provider, the date of service, the payee and the plan's phone number. A provider EOB adds a check or electronic funds transfer number that ties many claims to one payment.

An EOB API should return claim-level fields in two layers.

  • Payment level, once per document: payer name, payee, check or EFT number, payment date and payment total. These apply to every claim the payment covers.
  • Claim level, once per claim: patient name, member ID, the provider's patient account number if printed, the payer's claim number, service date range, and claim totals for billed, allowed, patient responsibility and paid.

Matching keys deserve special care. The patient account number is the one your system assigned, so it is the strongest match when the payer prints it. When it is missing, a consumer needs patient name, member ID and service dates together, which means the API must return all three even when one seems redundant.

Claim totals should come back as printed, not recomputed. If the API sums the lines itself, a misread line and a matching wrong total look consistent. Returning the printed total lets the consumer check the lines against it. The full list of EOB fields and what each one means sits in the guide to EOB data extraction fields; this post is about how an endpoint arranges them.

EOB service lines and adjustments in the API response

EOB service lines are the rows that follow one procedure from the provider's charge to the plan's payment. An EOB API should return them as an array inside each claim, one object per line, with the amounts as numbers and the adjustments as a nested array that explains the gap between billed and paid.

Each line needs, at minimum, a line number, a service date, a procedure code with any modifiers, units, and five amounts: billed, allowed, total adjustments, patient responsibility and plan paid. The CMS guide names the core columns as provider charges, allowed charges, paid by insurer and what you owe. Patient responsibility is often split into deductible, coinsurance and copay, and the API should return each part when the EOB prints it.

Modifiers and units are easy to lose and expensive to miss. Two lines with the same procedure code on the same date are not duplicates if one carries a modifier the other lacks, or if the units differ. An API should return modifiers as a list next to the code, not glued onto it as one string, so a consumer can match each line to the claim line it was billed on.

Denied lines need the same shape as paid ones. A line with a zero allowed amount and a zero payment still has a billed amount and at least one adjustment that explains it. An API that skips zero-pay lines makes the claim's billed total impossible to reconcile and hides the denials a billing team most needs to work.

The arithmetic on a line is a free quality check. Billed minus adjustments should equal allowed, and allowed minus patient responsibility should equal paid. An API that returns the parts lets the consumer run that check and send any line that does not balance to a person.

Sample EOB API response

The response below is an example only, for a fictional two-line EOB. It comes from the placeholder address https://api.example.com, and the patient, member ID, claim number, check number and amounts were all made up for this post. Schema Care does not offer an API, and this is not its output. Amounts are in cents.

GET https://api.example.com/v1/eobs/eob_example_2210

{
  "eob_id": "eob_example_2210",
  "payment": {
    "payer_name": { "value": "Example Mutual Health", "confidence": 0.98, "page": 1 },
    "check_number": { "value": "EX0049917", "confidence": 0.96, "page": 1 },
    "payment_date": { "value": "2026-09-18", "confidence": 0.95, "page": 1 },
    "payment_total_cents": { "value": 9500, "confidence": 0.97, "page": 2 }
  },
  "claims": [
    {
      "pages": [1, 2],
      "patient_name": { "value": "Imani Castellano", "confidence": 0.96, "page": 1 },
      "member_id": { "value": "XEM778201", "confidence": 0.84, "page": 1, "needs_review": true },
      "payer_claim_number": { "value": "EXC-26-0815-0042", "confidence": 0.94, "page": 1 },
      "patient_account_number": { "value": "ACCT-EX-3391", "confidence": 0.93, "page": 1 },
      "totals": {
        "billed_cents": { "value": 24500, "confidence": 0.95, "page": 2 },
        "allowed_cents": { "value": 14500, "confidence": 0.94, "page": 2 },
        "patient_responsibility_cents": { "value": 5000, "confidence": 0.93, "page": 2 },
        "paid_cents": { "value": 9500, "confidence": 0.96, "page": 2 }
      },
      "service_lines": [
        {
          "line_number": 1,
          "service_date": { "value": "2026-08-27", "confidence": 0.95, "page": 1 },
          "procedure_code": { "value": "99214", "confidence": 0.97, "page": 1 },
          "modifiers": [],
          "units": { "value": 1, "confidence": 0.98, "page": 1 },
          "billed_cents": { "value": 22000, "confidence": 0.96, "page": 1 },
          "allowed_cents": { "value": 14500, "confidence": 0.92, "page": 1 },
          "patient_responsibility_cents": { "value": 5000, "confidence": 0.91, "page": 1 },
          "paid_cents": { "value": 9500, "confidence": 0.95, "page": 1 },
          "adjustments": [
            { "group_code": "CO", "reason_code": "45", "description": "Charge exceeds fee schedule/maximum allowable or contracted/legislated fee arrangement.", "amount_cents": 7500, "confidence": 0.93, "page": 1 },
            { "group_code": "PR", "reason_code": "1", "description": "Deductible Amount", "amount_cents": 5000, "confidence": 0.91, "page": 1 }
          ],
          "remark_codes": []
        },
        {
          "line_number": 2,
          "service_date": { "value": "2026-08-27", "confidence": 0.95, "page": 2 },
          "procedure_code": { "value": "36415", "confidence": 0.89, "page": 2, "needs_review": true },
          "modifiers": [],
          "units": { "value": 1, "confidence": 0.98, "page": 2 },
          "billed_cents": { "value": 2500, "confidence": 0.95, "page": 2 },
          "allowed_cents": { "value": 0, "confidence": 0.94, "page": 2 },
          "patient_responsibility_cents": { "value": 0, "confidence": 0.94, "page": 2 },
          "paid_cents": { "value": 0, "confidence": 0.96, "page": 2 },
          "adjustments": [
            { "group_code": "CO", "reason_code": "97", "description": "The benefit for this service is included in the payment/allowance for another service/procedure that has already been adjudicated.", "amount_cents": 2500, "confidence": 0.9, "page": 2 }
          ],
          "remark_codes": [
            { "code": "M15", "description": "Separately billed services/tests have been bundled as they are considered components of the same procedure. Separate payment is not allowed.", "confidence": 0.92, "page": 2 }
          ]
        }
      ]
    }
  ],
  "provider_adjustments": []
}

Both lines balance. Line 1 runs $220.00 billed, minus a $75.00 CO-45 adjustment, to $145.00 allowed, then minus a $50.00 deductible to $95.00 paid. Line 2's $25.00 charge is adjusted to zero under CO-97. The claim totals and the payment total match the lines. Two fields carry needs_review because their scores fell under an example threshold of 0.9.

How should an EOB API report remark and adjustment codes?

An EOB API should report every adjustment as a group code, a reason code, an amount and the code's description, attached to the line it explains. Remark codes belong in a separate list on the same line, because they add information rather than account for money. A code without its amount or its line is close to useless.

The X12 Claim Adjustment Reason Codes list says these codes "describe why a claim or service line was paid differently than it was billed." It defines group codes including CO (Contractual Obligation), PR (Patient Responsibility), PI (Payer Initiated Reductions), and OA (Other Adjustment). It also lists CR (Corrections and Reversal) with a note that it is not to be used with 005010 and later, so a consumer may meet it only on old remittances. The group code tells a billing system who absorbs the amount, so an API that drops it forces the consumer to guess.

The X12 Remittance Advice Remark Codes list describes remark codes as providing additional explanation for an adjustment already described by a reason code. It also notes that some remark codes, called Alerts, are informational and never tied to a specific adjustment. That is why the sample keeps remark_codes as its own array rather than nesting each remark under an adjustment.

Return the code exactly as printed and the description from the published list, as two separate values. Payers sometimes print their own wording, and a member EOB may use the payer's private short codes. The CMS guide describes a member EOB's remark code as a short code, usually two or three letters and numbers, explained at the bottom of the EOB. An API should mark codes it could not find in the X12 lists instead of inventing a description.

Codes also change. The CMS page on EFT and remittance advice operating rules says the CARC and RARC committees review change requests three times a year. An API that maps codes to descriptions needs a dated code table and a way to tell you which version it used.

Which codes billing teams meet most often, and what each group code means for the patient's balance, is covered in other posts. For an endpoint, the rule is simple: one adjustment, one object, one line.

Multi-patient and multi-page EOBs in an API

A multi-patient EOB is one payment that covers claims for many patients, and an EOB API should return it as one payment object holding an array of claims. A multi-page EOB is any EOB whose claims or totals run across pages, and every returned field should say which page it came from.

The structure in the sample already handles both. payment appears once. claims is an array, each claim carries its own pages list, and each field carries its own page. A claim that starts at the bottom of page 1 and ends on page 2 shows up as one claim with pages 1 and 2, not as two partial claims.

Splitting is the hard part, and a good API does it for you. It finds where one patient's claim ends and the next begins, using the patient name, member ID and claim number lines that payers print at each break. The workflow side, including how a billing team files each patient's portion, is covered in splitting a bulk EOB by patient.

The check total ties it together. The sum of every claim's paid amount, plus or minus any provider-level adjustments, should equal the payment total printed on the EOB. Provider-level adjustments, such as recoupment of an earlier overpayment, belong to no claim, so the API should return them in their own array, as provider_adjustments does in the sample. If the claims add up and the payment does not, that array is where the difference lives.

Two failure modes are worth testing for. The first is a claim assigned to the wrong patient because a page break fell between the patient header and the lines. The second is a missing last page, which leaves the payment total unknown. An API should return a null total with a reason rather than a sum of the lines it found.

Does an EOB API replace an 835 feed?

An EOB API does not replace an 835 feed from a payer that sends one. The 835 is the electronic remittance advice standard, and it arrives already structured. Extraction from paper or PDF EOBs fills the gap for payers, claims and documents that never reach you as an 835.

The CMS page on EFT and remittance advice operating rules describes the electronic remittance advice (ERA) as "an explanation from a health plan to a provider about a claim payment" and names X12 Version 5010 (835) as the adopted standard. The same page says health plans must use CARCs and RARCs rather than proprietary codes to explain adjustments, and that CMS encourages providers to enroll for ERA with each health plan they work with.

Paper EOB extraction still earns its place in several situations:

  • A payer you bill has not set up ERA with you and still mails paper.
  • A secondary payer's decision depends on the primary EOB, which arrives as a PDF from the patient or the primary plan.
  • A patient brings in a member EOB to dispute a balance.
  • Old claims from before ERA enrollment need to be worked or audited.

When both exist for the same payment, take the 835 and use the paper for reference only, or you risk posting the payment twice. The side-by-side for billing teams is in EOB vs ERA 835 differences, and the segment structure a parser works with is mapped in the 835 loop and segment map. Teams that want paper EOB data to enter the same 835 workflow can read about converting a paper EOB to an 835.

An EOB API response like the sample also maps onto FHIR. How claims, lines and adjustments land on the ExplanationOfBenefit resource is covered in the FHIR ExplanationOfBenefit resource. For the lab side of the same design, see the lab report API post.

Schema Care reads EOBs into service lines, allowed amounts, plan payments and remark codes, scoring each field and routing doubtful ones to a reviewer. Schema Care offers no API, SDK, webhooks, FHIR output, 835 output or 837 output. You can watch a fictional EOB pass through its review threshold in the Schema Care threshold demo.

All posts in APIs, FHIR and Integration