Skip to the page
Chapters

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:

Shell
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"
}
EOF

The service date is left out here, so it is today in the office's time zone.

  • office_id is the office the check is for (off_...), from List offices. Its mode must match the key's.
  • payer_id is a Claim House payer ID (pyr_...), a payer ID, or an alias from the payer directory. See Payers.
  • subscriber is the plan holder: first name, last name, date of birth and member ID are required; the group number is optional.
  • patient is 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 and relationship (spouse, child or other). Sending a patient that is the subscriber is a finding, PATIENT_IS_SUBSCRIBER.
  • service_date is 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_codes are up to 10 CDT codes (D and 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_level is standard (the default) or enhanced. Enhanced asks for every procedure row the payer returns, not only the codes you listed.
  • tenant_reference is 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_id is 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:

FieldWhat it holds
idThe check's ID (elg_...). Read it again with Get an eligibility check.
state, outcomestate is pending while the check runs and completed when it ends. outcome is how it ended: answered, rejected, payer_unavailable or error.
statusThe coverage: active, inactive or unknown. null when there is no answer.
billingWhether the check was charged and why. See Billing.
cachefresh, or hit when an earlier answer was reused. See Reused answers.
request, parent_id, request_id, idempotency_key, tenant_referenceWhat was sent, the check this one corrects, and the identifiers of the request that made it.
sourceWhich network answered, how long it took (latency_ms) and when (completed_at).
coverage_effective_date, coverage_termination_date, plan, network_statusThe plan's dates, its name, type, group number and benefit period, and in_network, out_of_network or unknown.
maximums, deductiblesEach with level (individual or family), period, amount, used and remaining, and what it applies_to.
coverageOne row for each category (preventive, diagnostic, basic, endodontics, periodontics, oral surgery, major, prosthodontics, implants, orthodontics) with percent_paid, covered, frequency and notes.
proceduresOne row for each procedure code: description, percent_paid, covered, frequency, max_age, last_service_date, next_eligible_date, and a status.
plan_rulesMissing tooth clause, waiting period, pre-authorization, coordination of benefits, and whether major services are paid on the preparation or the seat date.
payer_notesNotes the payer sent that do not fit a field.
not_returnedThe names of the values the payer did not return.
rejectionnull 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:

ReplyWhen
422 PAYER_REJECTEDThe payer rejected the check. check.rejection says why.
502 PAYER_UNAVAILABLEThe payer's system is not answering. Nothing needs to change.
502 NETWORK_ERRORThe payer network failed or its answer could not be read.
500 INTERNALClaim House could not complete the check.
JSON
{
  "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 the field to change and the fix, and sometimes a suggested_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:

JSON
{ "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.reasonWhat it means
test_modeNot billed: test mode
answeredBilled: the payer answered
payer_rejectedBilled: rejected because of the member, the provider or the request
payer_side_rejectionNot billed: rejected for a reason the caller could not fix
payer_unavailableNot billed: payer unavailable
network_errorNot billed: network error
internal_errorNot billed: Claim House could not complete it
cache_hitNot 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:

Shell
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.json

Only 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 /eligibility lists checks as summaries (no request and no benefits), newest first, in pages. Filters: office_id, status, outcome, created_after, request_id, idempotency_key and parent_id.
Shell
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.