An insurance card OCR API should accept the front and back of one card and return named fields: member ID, group number, payer name, plan, subscriber, the pharmacy RxBIN, RxPCN and RxGroup, copays and payer phone numbers. Each field needs a confidence score, the side it came from, and an explicit null when the card does not print it.
This post describes what a well-designed card API looks like, as a design reference. It is not documentation for a Schema Care product: Schema Care does not read insurance cards, and it has no API, SDK, webhooks, FHIR, X12 or 275 output. The endpoint api.example.com and every member, ID and value below are invented for illustration.
Insurance card OCR API request: front image, back image and file limits
An insurance card OCR API request should carry both sides of one card in one call, as two labelled images. The front usually holds the member, group and plan; the back usually holds claims addresses, payer phone numbers and sometimes pharmacy routing numbers. Sending them together lets the API return one record and say which side each value came from.
A clear request has a small number of parts:
| Part | Example value | Why it matters |
|---|---|---|
front | JPEG, 1.8 MB | Most identity fields live here |
back | JPEG, 1.4 MB | Phone numbers, claims address, often Rx fields |
card_type_hint | commercial (optional) | Lets the caller say what kind of card it expects |
idempotency_key | ex-intake-20261016-0042 | A retried upload does not create a second record |
File limits should be published, not discovered. A card API should document the formats it accepts, the largest file size, and the smallest image dimensions it can read reliably. It should reject a file outside those limits immediately, with a clear error, instead of accepting it and returning empty fields later.
Accept the back side as optional, but say so in the response. Some front desks only photograph the front. When the back is missing, fields that normally sit there should come back null with a reason such as side_not_provided, so the caller can tell "not captured" apart from "not printed".
How a front desk captures good photos in the first place, with framing and glare checks before upload, is covered in the guide to insurance card photo quality checks. The general request and response shape of extraction APIs, beyond cards, is in the guide to what a medical document extraction API should do.
The insurance card OCR API response, field by field
The insurance card OCR API response should return one object per field, each holding the value, a confidence score, the side of the card and a box marking where the value was printed. Fields the card does not show come back as null with a reason. Callers should never have to guess whether a missing key means "blank" or "failed".
The response below is a fictional example for a made-up commercial card, front and back, returned by a placeholder call to POST https://api.example.com/v1/insurance-cards. Nothing in it describes a real plan, person or service.
{
"request_id": "ex-req-7f3a",
"card": {
"member_id": { "value": "EXM882104417", "confidence": 0.97, "side": "front", "box": [0.08, 0.41, 0.46, 0.47] },
"member_name": { "value": "LENA EXAMPLE", "confidence": 0.96, "side": "front", "box": [0.08, 0.33, 0.52, 0.39] },
"group_number": { "value": null, "confidence": 0.94, "side": "front", "null_reason": "not_printed" },
"payer_name": { "value": "Example Health Plan", "confidence": 0.95, "side": "front", "box": [0.06, 0.05, 0.60, 0.14] },
"plan_name": { "value": "EX Silver PPO", "confidence": 0.91, "side": "front", "box": [0.62, 0.05, 0.94, 0.12] },
"subscriber_name":{ "value": "LENA EXAMPLE", "confidence": 0.93, "side": "front", "box": [0.08, 0.52, 0.52, 0.58] },
"rx_bin": { "value": "999001", "confidence": 0.88, "side": "back", "box": [0.10, 0.62, 0.30, 0.67] },
"rx_pcn": { "value": "EXRX", "confidence": 0.95, "side": "back", "box": [0.10, 0.68, 0.30, 0.73] },
"rx_group": { "value": "EXGRP7", "confidence": 0.94, "side": "back", "box": [0.10, 0.74, 0.32, 0.79] },
"copays": [
{ "label": "PCP", "value": "$25", "confidence": 0.82, "side": "front", "box": [0.62, 0.60, 0.80, 0.65] },
{ "label": "ER", "value": "$250", "confidence": 0.92, "side": "front", "box": [0.62, 0.66, 0.80, 0.71] }
],
"phones": [
{ "label": "Member services", "value": "+1-555-010-0199", "confidence": 0.96, "side": "back", "box": [0.10, 0.20, 0.55, 0.25] },
{ "label": "Provider services", "value": "+1-555-010-0177", "confidence": 0.95, "side": "back", "box": [0.10, 0.26, 0.55, 0.31] }
]
}
}
Two values fall under a 0.90 example threshold: the RxBIN at 0.88 and the PCP copay at 0.82. A caller applying that threshold would accept the rest and send those two to a person. The group number is null with high confidence, which means the reader is sure no group number is printed, not that it failed to read one. The structure of each confidence object is explained in the section on what a per-field confidence object contains.
What the pharmacy fields mean
The pharmacy fields route pharmacy claims, as the guide to what the Rx BIN number routes explains, and FHIR gives each its own coverage class code, listed in the section on which Coverage class types hold group, plan and Rx numbers. An API should return each under its own key and never merge the medical group number with the RxGroup, because they can differ on one card.
Which card fields matter most to billing, and why card layouts are hard to read, is covered in the guide to insurance card OCR fields worth extracting. What the member ID and group number each identify is in the section on where the member ID sits on an insurance card.
How should an insurance card API turn a printed payer name into a payer ID?
An insurance card API should match the printed payer name against a payer list the caller controls, return the best match with its own confidence score, and keep the printed name beside the matched ID. Cards print brand names and logos, not the electronic payer IDs billing systems need, so this step is a lookup, not a read.
A payer ID is the identifier your claims and eligibility connection uses to route a transaction to a plan. Those identifiers come from the payer list of whoever carries your transactions, so the same plan can have different IDs in different systems. That is a strong reason for the API to accept the caller's own payer table rather than inventing IDs. Why payer matching catches misread plan names, alongside pattern checks and intake cross-checks, is covered in the section on validating insurance card OCR output.
A good normalized payer block looks like this, again as a fictional example:
"payer": {
"printed_name": "Example Health Plan",
"matched_id": "EXPLAN01",
"matched_name": "EXAMPLE HEALTH PLAN COMMERCIAL",
"match_confidence": 0.86,
"candidates": [
{ "id": "EXPLAN01", "name": "EXAMPLE HEALTH PLAN COMMERCIAL", "score": 0.86 },
{ "id": "EXPLAN02", "name": "EXAMPLE HEALTH PLAN MEDICARE ADVANTAGE", "score": 0.71 }
]
}
Keep these rules in the design:
- Return the printed name as read. A reviewer compares it to the card; a matched name alone hides mistakes.
- Score the match separately from the read. The text can be read perfectly and still match the wrong product line.
- Return candidates when two products are close. When your payer table lists several products under one brand name, the printed name alone cannot pick between them.
- Use the back of the card as evidence. A claims address or provider phone number can split a tie between two candidates.
The same payer ID later goes into claim files; the guide to X12 837 generation explains where it sits in a claim.
What should an insurance card OCR API return for an unreadable photo?
An insurance card OCR API should treat an unreadable photo as an image-quality error, returned before extraction, with a reason the front desk can act on. A readable photo with uncertain fields is different: it should return values with low confidence scores. Mixing the two leaves staff unsure whether to retake the photo or review it.
An image-quality error should name the problem and the side. A fictional example:
{
"request_id": "ex-req-7f3b",
"error": {
"code": "image_quality",
"side": "front",
"reasons": ["glare_over_text", "card_edges_not_found"],
"retake_advised": true
}
}
Useful reasons are the ones a person can fix: glare, blur, the card cut off at an edge, the image too dark, or the wrong object in the frame. A vague "processing failed" forces a guess.
Low-confidence fields are a different case. If the member ID is readable but one character could be a 0 or an O, the API should return the value with a low score and let the caller's threshold send it to review. A retake may help, but a person comparing the photo to the card can usually settle it faster.
Two edge cases deserve explicit codes. A photo of something that is not an insurance card, such as a driver's license, should return not_an_insurance_card. A front image submitted as the back should return a side mismatch, because swapped sides put phone numbers in the member fields.
Insurance card API edge cases: Medicare, Medicaid and cards with no group number
Some insurance cards legitimately leave fields empty, and an insurance card API must return null for them instead of filling them from nearby text. The Medicare card has no group number or pharmacy fields, Medicaid cards follow their own layouts, and a commercial card can lack a group number too. A guessed value here becomes a claim error later.
The Original Medicare card is the clearest case. Its member ID is the 11-character Medicare Beneficiary Identifier (MBI), whose character rules and validation are set out in the guide on how intake software can validate an MBI. The Medicare.gov page on your Medicare card says the card shows Part A as HOSPITAL, Part B as MEDICAL, and the date coverage begins. It lists no group number or pharmacy fields. It also says people with Medicare Advantage use the plan's card, and people with a drug plan carry that plan's card too.
For an API, that means:
- Return
group_number: nullwithnull_reason: "not_printed"on a Medicare card, not a coverage date read as a group. - Check the MBI against its published pattern before returning a high score, so a look-alike character gets a low one.
- Return Part A and Part B start dates as dates, since they are the coverage facts this card does print.
- Flag Medicare Advantage cards separately. They are plan cards and look like commercial ones.
Medicaid cards need their own field rules too. How their fields differ is covered in the section on how Medicaid card fields differ from commercial cards. Whatever the card type, return null for anything not printed and never copy the member ID into the group field to fill a gap.
From the card API response to an eligibility request
An insurance card API response feeds the eligibility check. The member ID, member name, payer ID and sometimes the group number go into the request, with a date of birth from registration. Low-confidence fields and close payer matches need a person first, because a check run on a misread member ID fails or finds the wrong person.
X12 defines the electronic exchange. The X12 transaction set list says the 270 is used to ask about eligibility, coverage or benefits under a subscriber's or dependent's policy, and the 271 returns benefit status, effective dates, copays, coinsurance and deductibles.
Here is how the example response above divides:
| Field | Example value | Ready for the eligibility request? |
|---|---|---|
| Member ID | EXM882104417 | Yes, 0.97 |
| Member name | LENA EXAMPLE | Yes, 0.96 |
| Payer | EXPLAN01 | Needs a person: match confidence 0.86 with a close candidate |
| Group number | null | Yes, sent as absent, not as a guess |
| Date of birth | Not on the card | Comes from the intake form or registration |
| RxBIN | 999001 | Not needed for medical eligibility; review before pharmacy use |
The 271 also gives you something to check the card against. If the response returns copays that differ from the ones printed, trust the payer's current answer and treat the printed copay as a hint. The section on using member ID and group number for eligibility checks covers that workflow from the billing side.
Card data often ends up in two other structures. Stored as FHIR, it becomes a Coverage resource, mapped element by element in the guide to the FHIR Coverage resource from insurance cards. Collected on a paper form, it often arrives as the insurance section of an intake packet, which the guide to FHIR QuestionnaireResponse for intake forms models.
That paper intake route is where Schema Care fits. It reads patient intake forms, including the insurance, demographics, history and consent checkbox sections, gives each field a confidence score, and holds anything under the threshold you choose for a reviewer, as the review workflow on the homepage shows. To try that on sample intake forms, choose Get started at /signin.