Guides
Eligibility
Run a dental eligibility check, read the answer, understand a rejection and the billing reason, and know when an answer is reused.
An eligibility check asks a patient's dental payer what coverage the patient has: whether it is active, the plan, the maximums and deductibles, what is covered by category, and the detail for the procedure codes you ask about. Claim House turns the payer's answer into one readable object that is the same for every payer.
With a test key, checks are answered by the sandbox. Live checks are not open yet: a live key gets 503 ELIGIBILITY_UNAVAILABLE. See Going live.
A key needs submit permission to run a check and read permission to read one.
Run a check#
POST /eligibility takes the office the check is for, the payer, and the person.
With OFFICE_ID and PAYER_ID set as in the quickstart:
curl "https://sandbox.myclaimhouse.com/api/v1/eligibility" \
-H "Authorization: Bearer $CLAIMHOUSE_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d @- <<EOF
{
"office_id": "$OFFICE_ID",
"payer_id": "$PAYER_ID",
"subscriber": {
"first_name": "Sam",
"last_name": "Sample",
"date_of_birth": "1990-01-01",
"member_id": "CH-MAX-NEARLY-USED",
"group_number": "GRP-100"
},
"procedure_codes": ["D0120", "D2740"],
"tenant_reference": "visit-1042"
}
EOFThe service date is left out here, so it is today in the office's time zone.
office_idis the office the check is for (off_...), from List offices. Its mode must match the key's.payer_idis a Claim House payer ID (pyr_...), a payer ID, or an alias from the payer directory. See Payers.subscriberis the plan holder: first name, last name, date of birth and member ID are required; the group number is optional.patientis only for a dependent. Leave it out when the subscriber is the person being checked. It needs a first name, last name, date of birth andrelationship(spouse,childorother). Sending a patient that is the subscriber is a finding,PATIENT_IS_SUBSCRIBER.service_dateis the day the treatment is planned. It defaults to today in the office's time zone, and must be within 12 months of today.procedure_codesare up to 10 CDT codes (Dand four digits) to get detail for. They are stored upper case, without duplicates, so the same codes in any order are the same request.detail_levelisstandard(the default) orenhanced. Enhanced asks for every procedure row the payer returns, not only the codes you listed.tenant_referenceis your own reference for the check (up to 255 characters). It comes back on the check, in lists and in events. Do not put patient information in it.parent_idis the rejected check this one corrects. See Fix and resend.
The request is checked before anything is sent. If anything is wrong, the reply is 422 INVALID_REQUEST with every problem in errors, so you can fix them in one go. Nothing is recorded or billed. The finding codes are OFFICE_NOT_FOUND, MODE_MISMATCH, PAYER_NOT_FOUND, PAYER_NOT_SUPPORTED_FOR_ELIGIBILITY, SUBSCRIBER_NAME_REQUIRED, SUBSCRIBER_MEMBER_ID_REQUIRED, DATE_OF_BIRTH_INVALID, DATE_OF_BIRTH_IN_FUTURE, PATIENT_NAME_REQUIRED, PATIENT_RELATIONSHIP_REQUIRED, PATIENT_RELATIONSHIP_INVALID, PATIENT_IS_SUBSCRIBER, SERVICE_DATE_INVALID, TOO_MANY_PROCEDURE_CODES, PROCEDURE_CODE_INVALID, PARENT_NOT_FOUND, PARENT_NOT_RESENDABLE.
The answer#
When the payer answers (coverage is active or inactive), the reply is 200 with the check. These are the main fields; the reference lists every one:
| Field | What it holds |
|---|---|
id | The check's ID (elg_...). Read it again with Get an eligibility check. |
state, outcome | state is pending while the check runs and completed when it ends. outcome is how it ended: answered, rejected, payer_unavailable or error. |
status | The coverage: active, inactive or unknown. null when there is no answer. |
billing | Whether the check was charged and why. See Billing. |
cache | fresh, or hit when an earlier answer was reused. See Reused answers. |
request, parent_id, request_id, idempotency_key, tenant_reference | What was sent, the check this one corrects, and the identifiers of the request that made it. |
source | Which network answered, how long it took (latency_ms) and when (completed_at). |
coverage_effective_date, coverage_termination_date, plan, network_status | The plan's dates, its name, type, group number and benefit period, and in_network, out_of_network or unknown. |
maximums, deductibles | Each with level (individual or family), period, amount, used and remaining, and what it applies_to. |
coverage | One row for each category (preventive, diagnostic, basic, endodontics, periodontics, oral surgery, major, prosthodontics, implants, orthodontics) with percent_paid, covered, frequency and notes. |
procedures | One row for each procedure code: description, percent_paid, covered, frequency, max_age, last_service_date, next_eligible_date, and a status. |
plan_rules | Missing tooth clause, waiting period, pre-authorization, coordination of benefits, and whether major services are paid on the preparation or the seat date. |
payer_notes | Notes the payer sent that do not fit a field. |
not_returned | The names of the values the payer did not return. |
rejection | null for an answer. See Rejections. |
Money (amount, used, remaining) is a decimal string such as "2000.00", never a floating point number. Dates are YYYY-MM-DD; moments are ISO 8601 with an offset.
A procedure's status is one of covered_now, not_due_yet (a frequency limit applies and the next eligible date is in next_eligible_date), downgraded (paid at the level of a cheaper procedure), age_limit and not_covered.
Values that are not returned#
Payers differ in what they return. A value a payer did not return is null in the answer, and its name is in not_returned, so you can tell "the payer did not say" from "the answer is zero". Try it with the sandbox member ID CH-GENERAL-ONLY.
Rejections#
A payer can refuse a check. Then the reply is not a 200: it is an error that carries the check, so you always have the check's ID. The error is one of:
| Reply | When |
|---|---|
422 PAYER_REJECTED | The payer rejected the check. check.rejection says why. |
502 PAYER_UNAVAILABLE | The payer's system is not answering. Nothing needs to change. |
502 NETWORK_ERROR | The payer network failed or its answer could not be read. |
500 INTERNAL | Claim House could not complete the check. |
{
"error": "PAYER_REJECTED",
"message": "The payer rejected the check. The check object says what to change, and whether resending can help.",
"errors": [],
"request_id": "req_...",
"check": { "id": "elg_...", "outcome": "rejected", "rejection": { "kind": "fix_and_resend", "payer_code": "75", "reasons": ["..."] } }
}rejection.kind tells you what to do next:
fix_and_resend: change something and send again. Each reason names thefieldto change and thefix, and sometimes asuggested_value.payer_unavailable: nothing to change. Try again later.rejected: resending will not change it.
Each reason in rejection.reasons has a code, an about (whose side it is on: subscriber, patient, provider, request, clearinghouse or payer), a field, a message and a fix. The reason codes are MEMBER_NOT_FOUND, PATIENT_IS_DEPENDENT, DATE_OF_BIRTH_MISMATCH, MEMBER_ID_INVALID, MEMBER_ID_DUPLICATE, NAME_MISMATCH, SERVICE_DATE_INVALID, REQUEST_INVALID, REQUEST_NOT_ACCEPTED, PROVIDER_NOT_ON_FILE, PROVIDER_NOT_ELIGIBLE, PATIENT_NOT_ELIGIBLE, PATIENT_DETAILS_REQUIRED, PAYER_NOT_ANSWERING, PAYER_REJECTED_OTHER. payer_code is the payer's own code.
Fix and resend#
To correct a fix_and_resend rejection, send the corrected request with parent_id set to the rejected check's id. The new check carries parent_id, and GET /eligibility?parent_id=<id> lists the corrections of a check. Only a check that was rejected with something to fix can be corrected: anything else is refused with the finding PARENT_NOT_RESENDABLE. A resend is a new check, billed by the same rules as any other, and it needs a new Idempotency-Key.
Billing#
Every completed check says whether it was charged, in billing:
{ "charged": false, "state": "not_billable", "reason": "test_mode", "label": "Not billed: test mode" }billing is null while the check is pending. The reason is one of these:
| billing.reason | What it means |
|---|---|
test_mode | Not billed: test mode |
answered | Billed: the payer answered |
payer_rejected | Billed: rejected because of the member, the provider or the request |
payer_side_rejection | Not billed: rejected for a reason the caller could not fix |
payer_unavailable | Not billed: payer unavailable |
network_error | Not billed: network error |
internal_error | Not billed: Claim House could not complete it |
cache_hit | Not billed: answer reused from a recent check |
In short: a check is billed when the payer answers it, or rejects it because of something the caller could have got right (the member, the provider or the request). It is free in test mode, when the payer is down, when the payer or Claim House is at fault, and when a recent answer is reused. Prices and the monthly minimum are in Pricing and usage.
Reused answers#
An identical check made within 60 minutes of one the payer answered gets the saved answer instead of a new call to the payer. "Identical" means the same organization, mode, office, payer, service date and request. The reply says so, with cache.state: "hit" and cache.source_id naming the check it was taken from, and it is not billed (billing.reason: "cache_hit").
To get a fresh answer from the payer, send Cache-Control: no-cache:
curl "https://sandbox.myclaimhouse.com/api/v1/eligibility" \
-H "Authorization: Bearer $CLAIMHOUSE_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Cache-Control: no-cache" \
-H "Content-Type: application/json" \
-d @request.jsonOnly an answer is reused. A rejection, an unavailable payer or an error is never reused, so a retry always asks the payer.
Read checks#
GET /eligibility/{id}reads one check: the request it was made from, and the answer or the rejection.GET /eligibilitylists checks as summaries (no request and no benefits), newest first, in pages. Filters:office_id,status,outcome,created_after,request_id,idempotency_keyandparent_id.
curl "https://sandbox.myclaimhouse.com/api/v1/eligibility?status=active&limit=5" -H "Authorization: Bearer $CLAIMHOUSE_KEY"A check is seen only by the organization and in the mode that made it.
Get told when a check completes#
Register a webhook endpoint and Claim House sends an eligibility.completed event for every completed check, with the check's ID, outcome, status and billing. The event holds no patient name, date of birth or member ID: read the check by its ID for the answer.