Skip to content

Docs: by job

Eligibility

Ask whether a patient is covered and what they owe, from the JSON you already hold, and read the payer’s 271 back in words.

Read as markdown

Payerworks writes and explains files; it sends nothing to a payer. Every file a route writes says transmitted: false, and is yours to send on your own connection. A test key is for invented data only, never a real patient.

Eligibility is two halves. You write the question: POST /v1/eligibility/270 turns the JSON your system holds (the payer, the provider, the subscriber, the day and the service types) into a 270 file. You send that file on your own connection. Then you hand Payerworks the 271 that came back, as X12 or as JSON, and POST /v1/eligibility/explain says it per patient: active or not on the day you asked about, and the deductible, out-of-pocket limit, coinsurance and copay in and out of network.

The eligibility sandbox (POST /v1/sandbox/eligibility) answers a 270 the way a payer would, so you can build the whole loop before you have a connection. The member ID’s ending picks the answer; the list is under Code lists below.

New here? Start with A first 271, explained.

Routes

Each route shows its call with curl and with the two clients, then its real answer, shortened, and the fields it holds. The client calls assume one client made once, const pw = new Payerworks() in JavaScript or pw = payerworks.Payerworks() in Python, each reading PAYERWORKS_API_KEY and PAYERWORKS_BASE_URL, and body is the request.json shown.

POST/v1/eligibility/270

Write a 270

Up to one hundred eligibility questions written as one 005010X279A1 interchange, from the JSON shape most clearinghouse APIs take: the payer, the provider, the subscriber and at most one dependent, the day and the service types. Ask about any day, a later one included; with no day named, the payer answers for its own current day. A question may also ask about procedures (CPT, HCPCS, CDT or NDC codes with up to four modifiers), point each to the patient’s diagnoses, and carry the place of service, a prior authorization or referral number, the provider’s taxonomy, a payer PIN and a birth order for twins; a question that asks only about procedures asks no service type beside them. Each question is checked field by field first, and nothing is written if any would be refused. You are told when a member ID begins with the card issuer number (80840), a name holds a title, a degree or a suffix, a backtick was written as an apostrophe, or the day is more than a year back. A question that uses none of these fields is written exactly as before. A test key writes test files only.

Request

curl -sS -X POST "$PAYERWORKS_BASE_URL/v1/eligibility/270" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
request.jsonJSON
{
  "sender_id": "SAMPLEBILLER",
  "receiver_id": "99999",
  "control_number": 12,
  "request": {
    "tradingPartnerServiceId": "99999",
    "tradingPartnerName": "SAMPLE HEALTH PLAN",
    "provider": {
      "organizationName": "SAMPLE FAMILY MEDICINE",
      "npi": "1234567893"
    },
    "subscriber": {
      "memberId": "W123456789",
      "firstName": "SAMPLE",
      "lastName": "PATIENT",
      "dateOfBirth": "19800214",
      "gender": "F"
    },
    "encounter": {
      "dateOfService": "20261020",
      "serviceTypeCodes": [
        "30"
      ]
    },
    "controlNumber": "ELIG-0001"
  }
}
The answer (200)JSON
{
  "version": "005010X279A1",
  "usage": "T",
  "x12": "ISA*00*          *00*          *ZZ*SAMPLEBILLER   *ZZ*99999          *261019*1500*^*00501*000000012*0*T*:~GS*HS*SAMPLEBILLER*99999*20261019*1500*12*X*005010X279A1~ST*270*0001*005010X279A1~BHT*0022*13*ELIG-0001*20261019*1500~HL*1**20*1~NM1*P…",
  "interchange_control_number": "000000012",
  "group_control_number": "12",
  "transaction_set_control_numbers": [
    "0001"
  ],
  "segments": 17,
  "requests": [
    {
      "index": 0,
      "trace": "ELIG-0001",
      "transaction_set_control_number": "0001",
      "warnings": []
    }
  ],
  "transmitted": false
}

Shortened here: lists show their first two items and long strings are cut. The whole answer is this route’s example in /openapi.json.

Field, Type, What it is
FieldTypeWhat it is
versionstringThe implementation guide written.
usageone of "T", "P"T or P.
x12stringThe whole interchange.
interchange_control_numberstring
group_control_numberstring
transaction_set_control_numberslist of string
segmentsinteger
transmittedthe value falsePayerworks wrote the file and sent it nowhere.
requestslist of object

POST/v1/eligibility/explain

Explain an eligibility answer

Any 271, as X12 or as the JSON most clearinghouse APIs return, said per patient: whether the coverage is active on the day you asked about, the plan, and for each service type the deductible and out-of-pocket limit with what is met and what remains, in and out of network, individual and family, the coinsurance, the copay, and what is not covered or needs prior authorization at each tier. A refusal comes back as every reason in words, with the field to fix and where to check it, or when to ask again. The JSON may be either shape most clearinghouse APIs return, a flat list of benefits or benefits grouped by plan with each kind in its own list, and one answer explains the same in any of the three forms; what the grouped shape holds that is not read is named. Add ?include=answer for the 271 read to JSON as well, and &shape=plans to have it in the grouped shape.

Request

curl -sS -X POST "$PAYERWORKS_BASE_URL/v1/eligibility/explain" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
request.jsonJSON
{
  "x12": "ISA*00*          *00*          *ZZ*SAMPLEPAYER    *ZZ*SAMPLEBILLER   *261018*0900*^*00501*000000091*0*T*:~\nGS*HB*SAMPLEPAYER*SAMPLEBILLER*20261018*0900*91*X*005010X279A1~\nST*271*0001*005010X279A1~\nBHT*0022*11*ELIG-0001*20261018*0900~\nHL*1**20*1~\nNM1*PR*2*SAMPLE HEALTH PLAN*****PI*99999~\nHL*2*1*21*1~\nNM1*1P*2*SAMPLE FAMILY MEDICINE*****XX*1234567893~\nHL*3*2*22*0~\nTRN*2*ELIG-0001*9SAMPLEBIL~\nNM1*IL*1*PATIENT*SAMPLE****MI*W123456789~\nREF*6P*G7700*SAMPLE EMPLOYER GROUP~\nDMG*D8*19800214*F~\nDTP*346*D8*20260101~\nEB*1*IND*30*PR*SAMPLE PPO~\nEB*C*IND*30***23*1500*****Y~\nEB*C*IND*30***29*600*****Y~\nEB*G*IND*30***23*6000*****Y~\nEB*G*IND*30***29*4100*****Y~\nEB*C*FAM*30***23*3000*****Y~\nEB*C*FAM*30***29*1800*****Y~\nEB*A*IND*30*****.2****Y~\nEB*C*IND*30***23*3000*****N~\nEB*C*IND*30***29*3000*****N~\nEB*A*IND*30*****.4****N~\nEB*B*IND*98****25*****Y~\nEB*A*IND*98*****.4****N~\nEB*1*IND*62********Y~\nSE*27*0001~\nGE*1*91~\nIEA*1*000000091~",
  "asked": {
    "service_type_codes": [
      "30",
      "98",
      "62"
    ],
    "date_of_service": "2026-10-20"
  }
}
The answer (200)JSON
{
  "kind": "271",
  "source": "x12",
  "envelope": {
    "sender_qualifier": "ZZ",
    "sender": "SAMPLEPAYER",
    "receiver_qualifier": "ZZ",
    "receiver": "SAMPLEBILLER",
    "date": "261018",
    "time": "0900",
    "control_number": "000000091",
    "usage": "T",
    "functional_group": "HB",
    "version": "005010X279A1",
    "transaction_sets": {
      "271": 1
    },
    "segments": 30
  },
  "asked": {
    "service_type_codes": [
      "30",
      "98"
    ],
    "date_of_service": "20261020"
  },
  "members": [
    {
      "member": "subscriber",
      "who": {
        "first_name": "SAMPLE",
        "last_name": "PATIENT",
        "member_id": "W123456789",
        "date_of_birth": "1980-02-14",
        "relationship": "18"
      },
      "payer": {
        "name": "SAMPLE HEALTH PLAN",
        "id": "99999"
      },
      "coverage": {
        "state": "active",
        "words": "The payer says the coverage is active.",
        "active_on_date": "yes",
        "by_insurance_type": null
      },
      "plan": {
        "name": "SAMPLE PPO",
        "group_number": "G7700",
        "group_name": "SAMPLE EMPLOYER GROUP",
        "plan_number": "",
        "begins": "2026-01-01",
        "ends": "",
        "eligibility_begins": "",
        "eligibility_ends": "",
        "insurance_type": {
          "code": "PR",
          "words": "preferred provider organization (PPO)"
        }
      },
      "services": [
        {
          "service_type": "98",
          "words": "physician visit, office",
          "known": true,
          "asked": true,
          "answered": true,
          "coverage": "not_stated",
          "in_network": {
            "individual": {
              "deductible": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              },
              "out_of_pocket": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              }
            },
            "family": {
              "deductible": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              },
              "out_of_pocket": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              }
            },
            "coinsurance_percent": null,
            "copay": 25,
            "not_covered": null,
            "prior_auth_required": null
          },
          "out_of_network": {
            "individual": {
              "deductible": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              },
              "out_of_pocket": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              }
            },
            "family": {
              "deductible": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              },
              "out_of_pocket": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              }
            },
            "coinsurance_percent": 40,
            "copay": null,
            "not_covered": null,
            "prior_auth_required": null
          },
          "network_not_stated": null,
          "prior_auth_required": null,
          "not_covered": null,
          "limitations": [],
          "messages": []
        },
        {
          "service_type": "62",
          "words": "MRI or CAT scan",
          "known": true,
          "asked": true,
          "answered": true,
          "coverage": "active",
          "in_network": null,
          "out_of_network": null,
          "network_not_stated": {
            "individual": {
              "deductible": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              },
              "out_of_pocket": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              }
            },
            "family": {
              "deductible": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              },
              "out_of_pocket": {
                "total": null,
                "remaining": null,
                "met": null,
                "period": "",
                "begins": "",
                "ends": "",
                "status": null
              }
            },
            "coinsurance_percent": null,
            "copay": null,
            "not_covered": null,
            "prior_auth_required": true
          },
          "prior_auth_required": true,
          "not_covered": null,
          "limitations": [],
          "messages": []
        }
      ],
      "plan_level": {
        "in_network": {
          "individual": {
            "deductible": {
              "total": 1500,
              "remaining": 600,
              "met": 900,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": 6000,
              "remaining": 4100,
              "met": 1900,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "family": {
            "deductible": {
              "total": 3000,
              "remaining": 1800,
              "met": 1200,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": null,
              "remaining": null,
              "met": null,
              "period": "",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "coinsurance_percent": 20,
          "copay": null,
          "not_covered": null,
          "prior_auth_required": null
        },
        "out_of_network": {
          "individual": {
            "deductible": {
              "total": 3000,
              "remaining": 3000,
              "met": 0,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": null,
              "remaining": null,
              "met": null,
              "period": "",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "family": {
            "deductible": {
              "total": null,
              "remaining": null,
              "met": null,
              "period": "",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": null,
              "remaining": null,
              "met": null,
              "period": "",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "coinsurance_percent": 40,
          "copay": null,
          "not_covered": null,
          "prior_auth_required": null
        },
        "network_not_stated": null,
        "figures_source": {
          "deductible": "plan_lines",
          "out_of_pocket": "plan_lines"
        },
        "stated_on": {
          "deductible": [],
          "out_of_pocket": []
        },
        "prior_auth_required": null,
        "not_covered": null,
        "limitations": [],
        "messages": []
      },
      "procedures": [],
      "other_payers": [],
      "refusal": null,
      "notes": [
        "A remaining amount is what was left on the day the payer answered.",
        "In network and out of network are the plan’s benefit tiers as the payer stated them (EB12): what the plan pays at each tier. A 271 does not say whether this provider participates with the plan."
      ]
    }
  ]
}

Shortened here: lists show their first two items and long strings are cut. The whole answer is this route’s example in /openapi.json.

Field, Type, What it is
FieldTypeWhat it is
kindthe value "271"
sourceone of "x12", "json"
envelopeEnvelope or null
askedobject
memberslist of EligibilityMember
answerlist of valuesOnly with ?include=answer: the 271 read to JSON, in the shape answer_shape names.
answer_shapeone of "plans"With &shape=plans: the answer is written grouped by plan, each kind of benefit in its own list.

POST/v1/sandbox/eligibility

Run the eligibility sandbox

Your eligibility questions written as a 270 and answered by the sandbox payer with a 271 in the same call, every answer explained. Any key may call it, every file is a test file, and how the member ID ends decides the answer.

Request

curl -sS -X POST "$PAYERWORKS_BASE_URL/v1/sandbox/eligibility" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
request.jsonJSON
{
  "request": {
    "tradingPartnerServiceId": "99999",
    "tradingPartnerName": "SAMPLE HEALTH PLAN",
    "provider": {
      "organizationName": "SAMPLE FAMILY MEDICINE",
      "npi": "1234567893"
    },
    "subscriber": {
      "memberId": "W123456789",
      "firstName": "SAMPLE",
      "lastName": "PATIENT",
      "dateOfBirth": "19800214",
      "gender": "F"
    },
    "encounter": {
      "dateOfService": "20261020",
      "serviceTypeCodes": [
        "30"
      ]
    },
    "controlNumber": "ELIG-0001"
  }
}
The answer (200)JSON
{
  "usage": "T",
  "transmitted": false,
  "control": {
    "interchange_270": "000000001",
    "interchange_271": "000000002",
    "sets": [
      "0001"
    ]
  },
  "files": {
    "eligibility_270": "ISA*00*          *00*          *ZZ*SUBMITTER      *ZZ*PWSANDBOX      *261019*1500*^*00501*000000001*0*T*:~GS*HS*SUBMITTER*PWSANDBOX*20261019*1500*1*X*005010X279A1~ST*270*0001*005010X279A1~BHT*0022*13*ELIG-0001*20261019*1500~HL*1**20*1~NM1*P…",
    "eligibility_271": "ISA*00*          *00*          *ZZ*PWSANDBOX      *ZZ*SUBMITTER      *261019*1500*^*00501*000000002*0*T*:~GS*HB*PWSANDBOX*SUBMITTER*20261019*1500*2*X*005010X279A1~ST*271*0001*005010X279A1~BHT*0022*11*ELIG-0001*20261019*1500~HL*1**20*1~NM1*P…"
  },
  "outcomes": [
    {
      "index": 0,
      "trace": "ELIG-0001",
      "outcome": "active",
      "trigger": ""
    }
  ],
  "requests": [
    {
      "index": 0,
      "trace": "ELIG-0001",
      "warnings": []
    }
  ],
  "explained": [
    {
      "index": 0,
      "trace": "ELIG-0001",
      "member": "subscriber",
      "who": {
        "first_name": "SAMPLE",
        "last_name": "PATIENT",
        "member_id": "W123456789",
        "date_of_birth": "1980-02-14",
        "relationship": "18"
      },
      "payer": {
        "name": "PAYERWORKS SANDBOX PAYER",
        "id": "PWSANDBOX"
      },
      "coverage": {
        "state": "active",
        "words": "The payer says the coverage is active.",
        "active_on_date": "yes",
        "by_insurance_type": null
      },
      "plan": {
        "name": "SANDBOX PPO PLAN",
        "group_number": "PWG44444",
        "group_name": "SANDBOX GROUP",
        "plan_number": "",
        "begins": "2026-01-01",
        "ends": "2026-12-31",
        "eligibility_begins": "",
        "eligibility_ends": "",
        "insurance_type": {
          "code": "PR",
          "words": "preferred provider organization (PPO)"
        }
      },
      "services": [],
      "plan_level": {
        "in_network": {
          "individual": {
            "deductible": {
              "total": 2500,
              "remaining": 1750,
              "met": 750,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": 5000,
              "remaining": 4250,
              "met": 750,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "family": {
            "deductible": {
              "total": 5000,
              "remaining": 4250,
              "met": 750,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": 10000,
              "remaining": 9250,
              "met": 750,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "coinsurance_percent": 20,
          "copay": null,
          "not_covered": null,
          "prior_auth_required": null
        },
        "out_of_network": {
          "individual": {
            "deductible": {
              "total": 5000,
              "remaining": 5000,
              "met": 0,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": 10000,
              "remaining": 10000,
              "met": 0,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "family": {
            "deductible": {
              "total": 10000,
              "remaining": 10000,
              "met": 0,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            },
            "out_of_pocket": {
              "total": 20000,
              "remaining": 20000,
              "met": 0,
              "period": "calendar year",
              "begins": "",
              "ends": "",
              "status": null
            }
          },
          "coinsurance_percent": 40,
          "copay": null,
          "not_covered": null,
          "prior_auth_required": null
        },
        "network_not_stated": null,
        "figures_source": {
          "deductible": "plan_lines",
          "out_of_pocket": "plan_lines"
        },
        "stated_on": {
          "deductible": [],
          "out_of_pocket": []
        },
        "prior_auth_required": null,
        "not_covered": null,
        "limitations": [],
        "messages": []
      },
      "procedures": [],
      "other_payers": [],
      "refusal": null,
      "notes": [
        "A remaining amount is what was left on the day the payer answered.",
        "In network and out of network are the plan’s benefit tiers as the payer stated them (EB12): what the plan pays at each tier. A 271 does not say whether this provider participates with the plan."
      ]
    }
  ]
}

Shortened here: lists show their first two items and long strings are cut. The whole answer is this route’s example in /openapi.json.

Field, Type, What it is
FieldTypeWhat it is
usagethe value "T"
transmittedthe value false
controlobject
filesobject
outcomeslist of object
requestslist of object
explainedlist of EligibilityMember

GET/v1/codes/service-type/{code}

A service type code

A service type code in words: its 005010 meaning where Payerworks has verified it, and the meaning later X12 versions gave a code they reassigned.

Request

curl -sS -X GET "$PAYERWORKS_BASE_URL/v1/codes/service-type/98" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY"
The answer (200)JSON
{
  "code": "98",
  "words": "physician visit, office",
  "known": true,
  "verified_in_005010": true,
  "later_words": "prescription drug",
  "label_source": "paraphrase"
}
Field, Type, What it is
FieldTypeWhat it is
codestring
wordsstring
knownboolean
verified_in_005010boolean
later_wordsstring
label_sourcethe value "paraphrase"

GET/v1/codes/aaa/{code}

A reject reason code

A reject reason from a 271 (its AAA segment) in words, whether asking again can help, and the request fields to correct. Add ?loop=2100D for a dependent.

Request

curl -sS -X GET "$PAYERWORKS_BASE_URL/v1/codes/aaa/72" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY"
The answer (200)JSON
{
  "code": "72",
  "words": "the subscriber’s member ID is missing or not valid",
  "known": true,
  "retryable": false,
  "fix_field": "subscriber.memberId",
  "fix_fields": [
    "subscriber.memberId"
  ],
  "loop": "",
  "label_source": "paraphrase"
}
Field, Type, What it is
FieldTypeWhat it is
codestring
wordsstring
knownboolean
retryableboolean
fix_fieldstring
fix_fieldslist of string
loopstring
label_sourcethe value "paraphrase"

POST/v1/eligibility/identity-retry

Find the patient again

After a payer could not match the patient (reject reasons 65, 67, 71, 72, 73, 75 or 77, sent as reasons or read from the 271 itself): the next questions to try, in order, each built from your own question and the facts you give (the legal first name, a previous last name, a dependent’s own member ID), each with its changes listed and checked as a 270 is. No name is guessed and there is no nickname table; a day and month swapped is offered only as one to check first. Suggestions only: Payerworks looks nobody up, and nothing is sent.

Request

curl -sS -X POST "$PAYERWORKS_BASE_URL/v1/eligibility/identity-retry" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
request.jsonJSON
{
  "request": {
    "tradingPartnerServiceId": "99999",
    "tradingPartnerName": "SAMPLE HEALTH PLAN",
    "provider": {
      "organizationName": "SAMPLE FAMILY MEDICINE",
      "npi": "1234567893"
    },
    "subscriber": {
      "memberId": "W123456789",
      "firstName": "SAM",
      "lastName": "PATIENT-JONES",
      "dateOfBirth": "19800214",
      "gender": "F"
    },
    "encounter": {
      "dateOfService": "20261020",
      "serviceTypeCodes": [
        "30"
      ]
    },
    "controlNumber": "ELIG-0001"
  },
  "reasons": [
    {
      "code": "73",
      "loop": "2100C"
    }
  ],
  "known": {
    "legal_first_name": "SAMANTHA"
  }
}
The answer (200)JSON
{
  "applies": true,
  "reasons": [
    {
      "code": "73",
      "loop": "2100C"
    }
  ],
  "answers_codes": [
    "65",
    "67"
  ],
  "person": "subscriber",
  "steps": [
    {
      "step": "legal_name",
      "status": "ready",
      "why": "A nickname or short name is the most common mismatch: ask with the full legal first name, as the card or a government ID prints it.",
      "changes": [
        {
          "op": "replace",
          "path": "subscriber.firstName",
          "value": "SAMANTHA"
        }
      ],
      "request": {
        "tradingPartnerServiceId": "99999",
        "tradingPartnerName": "SAMPLE HEALTH PLAN",
        "provider": {
          "organizationName": "SAMPLE FAMILY MEDICINE",
          "npi": "1234567893"
        },
        "subscriber": {
          "memberId": "W123456789",
          "firstName": "SAMANTHA",
          "lastName": "PATIENT-JONES",
          "dateOfBirth": "19800214",
          "gender": "F"
        },
        "encounter": {
          "dateOfService": "20261020",
          "serviceTypeCodes": [
            "30"
          ]
        },
        "controlNumber": "ELIG-0001"
      },
      "errors": [],
      "warnings": [],
      "needs": []
    },
    {
      "step": "compound_first_part",
      "status": "ready",
      "why": "A compound last name may be held as one part only: ask with the first part.",
      "changes": [
        {
          "op": "replace",
          "path": "subscriber.lastName",
          "value": "PATIENT"
        }
      ],
      "request": {
        "tradingPartnerServiceId": "99999",
        "tradingPartnerName": "SAMPLE HEALTH PLAN",
        "provider": {
          "organizationName": "SAMPLE FAMILY MEDICINE",
          "npi": "1234567893"
        },
        "subscriber": {
          "memberId": "W123456789",
          "firstName": "SAM",
          "lastName": "PATIENT",
          "dateOfBirth": "19800214",
          "gender": "F"
        },
        "encounter": {
          "dateOfService": "20261020",
          "serviceTypeCodes": [
            "30"
          ]
        },
        "controlNumber": "ELIG-0001"
      },
      "errors": [],
      "warnings": [],
      "needs": []
    }
  ],
  "notes": [],
  "transmitted": false,
  "scope": "Suggestions only, each built from the request and the facts you sent: Payerworks looks nobody up, and sends nothing."
}

Shortened here: lists show their first two items and long strings are cut. The whole answer is this route’s example in /openapi.json.

Field, Type, What it is
FieldTypeWhat it is
appliesboolean
reasonslist of object
answers_codeslist of string
personstring
stepslist of object
noteslist of string
transmittedthe value false
scopestring

POST/v1/eligibility/service-type-test

Does a payer answer a service type

Two answers you hold from one payer, one to 30 (or 35, for dental) and one to a specific service type: whether the payer answered the specific one, likely ignored it, answered it under other service types, or refused it, with how many lines it added and removed. It reads only the two answers, as they stood when they were given: a payer may change what it answers.

Request

curl -sS -X POST "$PAYERWORKS_BASE_URL/v1/eligibility/service-type-test" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
request.jsonJSON
{
  "baseline": {
    "x12": "ISA*00*          *00*          *ZZ*SAMPLEPAYER    *ZZ*SAMPLEBILLER   *261018*0900*^*00501*000000091*0*T*:~\nGS*HB*SAMPLEPAYER*SAMPLEBILLER*20261018*0900*91*X*005010X279A1~\nST*271*0001*005010X279A1~\nBHT*0022*11*ELIG-0001*20261018*0900~\nHL*1**20*1~\nNM1*PR*2*SAMPLE HEALTH PLAN*****PI*99999~\nHL*2*1*21*1~\nNM1*1P*2*SAMPLE FAMILY MEDICINE*****XX*1234567893~\nHL*3*2*22*0~\nTRN*2*ELIG-0001*9SAMPLEBIL~\nNM1*IL*1*PATIENT*SAMPLE****MI*W123456789~\nREF*6P*G7700*SAMPLE EMPLOYER GROUP~\nDMG*D8*19800214*F~\nDTP*346*D8*20260101~\nEB*1*IND*30*PR*SAMPLE PPO~\nEB*C*IND*30***23*1500*****Y~\nEB*C*IND*30***29*600*****Y~\nEB*G*IND*30***23*6000*****Y~\nEB*G*IND*30***29*4100*****Y~\nEB*C*FAM*30***23*3000*****Y~\nEB*C*FAM*30***29*1800*****Y~\nEB*A*IND*30*****.2****Y~\nEB*C*IND*30***23*3000*****N~\nEB*C*IND*30***29*3000*****N~\nEB*A*IND*30*****.4****N~\nEB*B*IND*98****25*****Y~\nEB*A*IND*98*****.4****N~\nEB*1*IND*62********Y~\nSE*27*0001~\nGE*1*91~\nIEA*1*000000091~"
  },
  "specific": {
    "x12": "ISA*00*          *00*          *ZZ*SAMPLEPAYER    *ZZ*SAMPLEBILLER   *261018*0900*^*00501*000000096*0*T*:~\nGS*HB*SAMPLEPAYER*SAMPLEBILLER*20261018*0900*96*X*005010X279A1~\nST*271*0001*005010X279A1~\nBHT*0022*11*ELIG-0002*20261018*0900~\nHL*1**20*1~\nNM1*PR*2*SAMPLE HEALTH PLAN*****PI*99999~\nHL*2*1*21*1~\nNM1*1P*2*SAMPLE FAMILY MEDICINE*****XX*1234567893~\nHL*3*2*22*0~\nTRN*2*ELIG-0002*9SAMPLEBIL~\nNM1*IL*1*PATIENT*SAMPLE****MI*W123456789~\nDMG*D8*19800214*F~\nDTP*346*D8*20260101~\nEB*1*IND*30*PR*SAMPLE PPO~\nEB*C*IND*30***23*1500*****Y~\nEB*C*IND*30***29*600*****Y~\nEB*A*IND*30*****.2****Y~\nSE*16*0001~\nGE*1*96~\nIEA*1*000000096~"
  },
  "service_type_code": "98",
  "baseline_service_type": "30"
}
The answer (200)JSON
{
  "code": "98",
  "words": "physician visit, office",
  "baseline": "30",
  "verdict": "answered_differently",
  "said": "The answer to 98 differs from the baseline but never names 98: the payer reads the code, and answers under other service types. Read the lines it added.",
  "named_in_answer": false,
  "differs": true,
  "added": 0,
  "removed": 10,
  "notes": [],
  "scope": "Read from the two answers you sent, for this payer at the time they were given: a payer may change what it answers."
}
Field, Type, What it is
FieldTypeWhat it is
codestring
wordsstring
baselinestring
verdictstring
saidstring
named_in_answerboolean
differsboolean
addedinteger
removedinteger
noteslist of string
scopestring

POST/v1/eligibility/coordination

Which plan pays first

The plans you list for one person, two to ten, put in the order they pay by the NAIC coordination of benefits model regulation and Medicare’s secondary payer rules, each step naming its rule and its section. Medicaid always pays last, and after Medicare a Medigap policy comes before TRICARE; injury coverage not related to the service is left out of the order. Where a rule needs a fact you did not send, no order is given and the fact is named, never guessed. It knows only the plans you send: it cannot find a person’s other coverage.

Request

curl -sS -X POST "$PAYERWORKS_BASE_URL/v1/eligibility/coordination" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
request.jsonJSON
{
  "person": {
    "medicare": {
      "basis": "age"
    }
  },
  "date_of_service": "2026-10-20",
  "plans": [
    {
      "id": "RETIREE",
      "kind": "employer_group",
      "covers_as": "self",
      "subscriber_status": "retired",
      "through": "retirement"
    },
    {
      "id": "MEDICARE",
      "kind": "medicare"
    },
    {
      "id": "SPOUSE",
      "kind": "employer_group",
      "covers_as": "dependent",
      "dependent_of": "spouse",
      "subscriber_status": "active",
      "through": "spouse_employment",
      "employer_size": 250
    }
  ]
}
The answer (200)JSON
{
  "decided": true,
  "order": [
    {
      "position": 1,
      "plan_id": "SPOUSE",
      "kind": "employer_group"
    },
    {
      "position": 2,
      "plan_id": "MEDICARE",
      "kind": "medicare"
    }
  ],
  "not_ordered": [],
  "steps": [
    {
      "between": [
        "SPOUSE",
        "RETIREE"
      ],
      "first": "SPOUSE",
      "rule": "medicare_reversal",
      "source": "naic",
      "words": "6.D(1)(b): the person is a Medicare beneficiary, Medicare is secondary to the plan covering them as a dependent and primary to the plan covering them otherwise, so the dependent plan pays first."
    },
    {
      "between": [
        "SPOUSE",
        "MEDICARE"
      ],
      "first": "SPOUSE",
      "rule": "msp_current_employment",
      "source": "cms_msp",
      "words": "65 or older, covered through current employment at an employer of 20 or more employees: the group plan pays first."
    }
  ],
  "needs": [],
  "notes": [],
  "scope": "Ordered from the plans you listed and the facts you gave, by the NAIC model regulation and CMS’s Medicare Secondary Payer rules. Payerworks does not know a person’s other coverage: a plan you did not list is not here, and a state’s own rule…",
  "sources": [
    {
      "id": "naic",
      "title": "NAIC Coordination of Benefits Model Regulation (MO-120), October 2013",
      "url": "https://content.naic.org/sites/default/files/model-law-120.pdf",
      "read": "10/19/2026"
    },
    {
      "id": "cms_msp",
      "title": "CMS, Medicare Secondary Payer, MLN006903 (July 2025), Table 1",
      "url": "https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/Downloads/MSP_Fact_Sheet.pdf",
      "read": "10/19/2026"
    }
  ]
}

Shortened here: lists show their first two items and long strings are cut. The whole answer is this route’s example in /openapi.json.

Field, Type, What it is
FieldTypeWhat it is
decidedboolean
orderlist of objectEmpty unless every step was decided.
partial_orderlist of objectWhen undecided: what could be ordered. Not an order to bill by.
not_orderedlist of objectPlans no rule placed, each with why; pays_this_service false is coverage that pays nothing for this service and leaves the order decided.
stepslist of object
needslist of objectEach fact a rule needs, by plan.
noteslist of string
scopestring
sourceslist of object

POST/v1/eligibility/discovery/verify

Is this coverage the patient’s

Up to ten coverages an insurance discovery search found, each a 271, checked against the patient: the last name (a two-part surname allowed), the first name, the exact date of birth, the gender and the address. Each is said as a match, one to review or not this patient, and one the payer reports as pending is marked to confirm with the payer. Payerworks runs no discovery search itself.

Request

curl -sS -X POST "$PAYERWORKS_BASE_URL/v1/eligibility/discovery/verify" \
  -H "Authorization: Bearer $PAYERWORKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
request.jsonJSON
{
  "patient": {
    "first_name": "SAMPLE",
    "last_name": "PATIENT",
    "date_of_birth": "1980-02-14",
    "gender": "F"
  },
  "coverages": [
    {
      "x12": "ISA*00*          *00*          *ZZ*SAMPLEPAYER    *ZZ*SAMPLEBILLER   *261018*0900*^*00501*000000091*0*T*:~\nGS*HB*SAMPLEPAYER*SAMPLEBILLER*20261018*0900*91*X*005010X279A1~\nST*271*0001*005010X279A1~\nBHT*0022*11*ELIG-0001*20261018*0900~\nHL*1**20*1~\nNM1*PR*2*SAMPLE HEALTH PLAN*****PI*99999~\nHL*2*1*21*1~\nNM1*1P*2*SAMPLE FAMILY MEDICINE*****XX*1234567893~\nHL*3*2*22*0~\nTRN*2*ELIG-0001*9SAMPLEBIL~\nNM1*IL*1*PATIENT*SAMPLE****MI*W123456789~\nREF*6P*G7700*SAMPLE EMPLOYER GROUP~\nDMG*D8*19800214*F~\nDTP*346*D8*20260101~\nEB*1*IND*30*PR*SAMPLE PPO~\nEB*C*IND*30***23*1500*****Y~\nEB*C*IND*30***29*600*****Y~\nEB*G*IND*30***23*6000*****Y~\nEB*G*IND*30***29*4100*****Y~\nEB*C*FAM*30***23*3000*****Y~\nEB*C*FAM*30***29*1800*****Y~\nEB*A*IND*30*****.2****Y~\nEB*C*IND*30***23*3000*****N~\nEB*C*IND*30***29*3000*****N~\nEB*A*IND*30*****.4****N~\nEB*B*IND*98****25*****Y~\nEB*A*IND*98*****.4****N~\nEB*1*IND*62********Y~\nSE*27*0001~\nGE*1*91~\nIEA*1*000000091~"
    }
  ],
  "date_of_service": "2026-10-20"
}
The answer (200)JSON
{
  "results": [
    {
      "coverage_index": 0,
      "patient_in_answer": 0,
      "verdict": "matches",
      "said": "The name and the date of birth match the patient’s, and no address on it differs.",
      "checks": [
        {
          "check": "last_name",
          "result": "same"
        },
        {
          "check": "first_name",
          "result": "same"
        }
      ],
      "coverage": {
        "state": "active",
        "active_on_date": "yes",
        "payer": {
          "name": "SAMPLE HEALTH PLAN",
          "id": "99999"
        },
        "member_id": "W123456789",
        "plan": "SAMPLE PPO"
      },
      "status_codes": [
        "1"
      ],
      "confirm_with_payer": false
    }
  ],
  "summary": {
    "coverages": 1,
    "matched": 1,
    "to_review": 0,
    "not_this_patient": 0,
    "confirm_with_payer": 0
  },
  "notes": [
    "A coverage that matches is the patient’s as far as these answers show; discovery says nothing of which plan pays first: order them with POST /v1/eligibility/coordination, or from a payer’s coordination denial."
  ],
  "scope": "Checked against the answers and the patient you sent: Payerworks finds no coverage itself and keeps nothing."
}

Shortened here: lists show their first two items and long strings are cut. The whole answer is this route’s example in /openapi.json.

Field, Type, What it is
FieldTypeWhat it is
resultslist of object
summaryobject
noteslist of string
scopestring

Reading the answer

  • Read members[].coverage.state first: active, inactive or not stated, for the day in asked. A figure the payer did not state is null, never 0.
  • Each service type is under services[], with its figures split by in_network and out_of_network, then individual and family. A remaining amount is never read as a total: the payer’s own EB06 says which a figure is.
  • When the plan has more than one period or insurance type, the one covering the day asked about is said first and the rest are kept under other_periods or by_insurance_type, so nothing is dropped.
  • A refusal (an AAA segment) comes back as every reason in words with the field to fix, or when to ask again. The payer’s own follow-up code decides that advice before the reason does.

Troubleshooting

  • invalid_requests (422): One question would be refused, so nothing was written. Each question’s errors are listed with the field; fix those and send again.
  • test_key_production_file (403): A test key writes test files only (ISA15 T). Leave usage out, or use a live key.
  • A member ID that begins with 80840 has the card issuer number in front of it: the answer warns, because most payers want the member ID alone.
  • Asking about a later day is allowed: a check before an appointment is about that day. With no day named, the payer answers for its own today.
  • not_a_271 (422): POST /v1/eligibility/explain reads a 271 with its ISA envelope. A 270 sent there by mistake is refused here.
  • After the payer could not match the patient (reject reasons such as 72, 73 or 75), POST /v1/eligibility/identity-retry plans the next questions from what you hold. It never guesses a name.

Every refusal these routes may answer, with its status. Any route may also answer the codes every route shares, on Every error.

Code, Status, What it means
CodeStatusWhat it means
invalid_aaa_code400A reject reason code (AAA03) is one or two letters or digits, such as 72 or T4.
invalid_asked400asked is { service_type_codes: [codes], date_of_service: YYYYMMDD }, each optional.
invalid_control_number400control_number is the interchange control number you track, a whole number from 1 to 999999999.
invalid_discovery_verify400Send { patient: { first_name, last_name, date_of_birth, address?, previous_addresses?, gender? }, coverages: [answers, x12 or json], date_of_service? }.
invalid_include400include names what to add to the answer: answer (the normalized read of the 271).
invalid_known400known holds only text facts the caller gives: legal_first_name, previous_last_name, dependent_member_id, each at most 80 characters.
invalid_loop400loop is the loop the reason was returned in, such as 2100C or 2110D.
invalid_reasons400reasons is a list of { code, loop }: code an AAA reject reason (such as 75), loop where it was returned (such as 2100C), at most 20.
invalid_receiver_id400receiver_id is the receiver's interchange id: 2 to 15 letters, digits or spaces.
invalid_sender_id400sender_id is your submitter id at the receiver: 2 to 15 letters, digits or spaces.
invalid_service_type400A service type code is one to three letters or digits, such as 30 or 98.
invalid_service_type_test400Send { baseline, specific, service_type_code, baseline_service_type }: two answers (x12 or json), the specific service type code asked, and the baseline asked (30, or 35 for dental).
invalid_shape400shape names how the answer that include=answer adds is written: normalized (the default) or plans (the grouped JSON shape).
no_answer400Send the 271 as x12 (the file's text) or as json (the JSON answer).
no_answer_to_read400Send the 271 as x12 (the file’s text) or as json (either JSON eligibility answer).
no_identity_request400Send request (the eligibility request the payer refused, in the JSON POST /v1/eligibility/270 takes) and the reasons: reasons [{ code, loop }] or the refusing answer (x12 or json).
no_request400The body has no eligibility request: send requests (a list), request, or the request itself in the JSON shape the docs describe.
too_many_coverages400Verify at most 10 coverages a call.
too_many_requests400At most 100 eligibility requests a call.
test_key_production_file403A test key writes test interchanges only (ISA15 = T). Use a live key for a production file.
answer_too_large413This answer is larger than one response can carry: send less in each call (split the file, or send fewer claims or requests).
file_too_large413This file holds more than one call can read: the error names what was counted (unit) and the most one call reads (limit). Split it and send each part in its own call.
answer_not_read422An answer could not be read as an eligibility answer: send an X12 271 with its envelope, or a JSON eligibility answer.
duplicate_trace422Two requests in one file carry the same trace (controlNumber); each must be unique so its answer matches back.
invalid_coordination422The coordination question would not be answered as sent: each problem is listed.
invalid_identity_request422The request would be refused as a 270 before any retry is planned: its errors are listed.
invalid_requests422One or more eligibility requests would be refused; nothing was written. Each request's errors are listed.
mixed_functional_groups422This interchange holds functional groups of different kinds: send each kind in its own interchange, so no part of it goes unread.
no_reasons422Neither reasons nor the answer names a reject reason to plan from.
not_a_271422This is not an X12 271 eligibility answer: send the 271 with its ISA envelope.
not_a_271_json422This JSON is not an eligibility answer: it holds no plans, benefitsInformation, errors or subscriber.

Code lists

Any one code in words: GET /v1/codes/service-type/{code}, GET /v1/codes/aaa/{code}.

The eligibility sandbox’s member ID endings

Ending, Outcome, What it answers
EndingOutcomeWhat it answers
NOTFOUNDsubscriber_not_foundA member ID ending in NOTFOUND is not found: the 271 refuses in the subscriber’s loop with AAA 75 and states no benefits.
BADDOBbirth_date_mismatchA member ID ending in BADDOB is found, but the birth date does not match: AAA 71 in the patient’s loop, and no benefits.
DOWNpayer_unavailableA member ID ending in DOWN reaches a payer that cannot answer: AAA 42 in the payer’s loop, follow-up R. Ask again later, unchanged.
INACTIVEinactiveA member ID ending in INACTIVE answers with coverage that ended last year: EB 6 on service type 30, with last year’s plan dates.
OONout_of_network_onlyA member ID ending in OON is active with out-of-network benefits only: every figure is stated for the out-of-network tier.
AUTHprior_auth_requiredA member ID ending in AUTH is active, and each service asked about requires prior authorization (EB11 Y).
METdeductible_metA member ID ending in MET is active with the deductible met: the remaining deductible is zero.
OTHERother_payer_primaryA member ID ending in OTHER is active, and another payer is primary: an EB R line names it in a 2120 loop.
``activeAny other member ID is active, with the full grid: individual and family deductible and out-of-pocket limit for the calendar year, in and out of network, each with what remains, plus coinsurance and a copay per visit on each service asked.

Example

eligibility.mjs runs every step below against the API with a test key. It needs payerworks.mjs and sample-data.mjs beside it.

eligibility.mjsJavaScript
// Payerworks example: eligibility, a 270 written and a 271 explained. Docs: https://payerworks.com/docs/eligibility
//
// Run it with Node 18 or later, with the JavaScript client (https://payerworks.com/sdk/payerworks.mjs)
// and sample-data.mjs beside this file:
//   PAYERWORKS_API_KEY=pw_test_... PAYERWORKS_BASE_URL=<the base URL shown with your key> node eligibility.mjs
// A test key and invented data only. Payerworks sends nothing to a payer: every file it writes is yours.
import { pathToFileURL } from 'node:url';
import { Payerworks, PayerworksError } from './payerworks.mjs';
import { ELIGIBILITY_QUESTION, SENDER_ID, PAYER_ID, copy, endingIn, refusalOf } from './sample-data.mjs';

/** @param {Payerworks} pw */
export async function run(pw) {
  // 1. Write the question as a 270. The file is yours to send on your own connection.
  const written = await pw.postEligibility270({ sender_id: SENDER_ID, receiver_id: PAYER_ID, control_number: 12, request: copy(ELIGIBILITY_QUESTION) });

  // 2. The eligibility sandbox answers the same question as a payer would: a 270 and its 271.
  const sandbox = await pw.postSandboxEligibility({ request: copy(ELIGIBILITY_QUESTION) });
  const the271 = sandbox.files.eligibility_271;

  // 3. Explain the 271 per patient, for the services and the day you asked about.
  const explained = await pw.postEligibilityExplain({ x12: the271, asked: { service_type_codes: ['30'], date_of_service: '2026-10-20' } });
  const member = explained.members[0];

  // 4. A member ID ending in NOTFOUND: the payer cannot find the subscriber, and the answer says why in words.
  const notFound = await pw.postSandboxEligibility({ request: endingIn(ELIGIBILITY_QUESTION, 'NOTFOUND') });

  // 5. The failure path: an NPI that fails its check digit is refused before anything is written.
  const refused = await refusalOf(() => pw.postEligibility270({
    sender_id: SENDER_ID, receiver_id: PAYER_ID, control_number: 13,
    request: { ...copy(ELIGIBILITY_QUESTION), provider: { organizationName: 'SAMPLE FAMILY MEDICINE', npi: '1234567890' } },
  }), { PayerworksError });

  return {
    written: { transmitted: written.transmitted, starts: written.x12.slice(0, 3) },
    coverage: member.coverage.state,
    not_found: notFound.outcomes[0].outcome,
    refused,
  };
}

// Run as a script: node <this file>, with PAYERWORKS_API_KEY and PAYERWORKS_BASE_URL set.
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
  console.log(JSON.stringify(await run(new Payerworks()), null, 2));
}
What it answered, run against the API in Payerworks’ own tests (your request ids will differ)JSON
{
  "written": {
    "transmitted": false,
    "starts": "ISA"
  },
  "coverage": "active",
  "not_found": "subscriber_not_found",
  "refused": {
    "status": 422,
    "code": "invalid_requests",
    "request_id": "req_5"
  }
}

Talk to us

What happens next

  1. 1

    A person at Payerworks reads what you sent, usually within a business day.

  2. 2

    One reply by email names the next step: our trading partner details, where each of your payers stands, or a key for the API.

  3. 3

    Nothing follows unless you answer. There is no sequence behind this form.

Or write to hello@payerworks.com.

Talk to Payerworks

Who you are and what you want to connect. A person replies by email, usually within a business day, and there is no follow-up sequence.

Used to reply to you and for nothing else.