Skip to the page
Chapters

Guides

Claims

Send a dental claim as JSON, read its validation findings, follow it through the 997 and 277 on its timeline, correct and resubmit it, void it, and read the 837D it went out in.

A claim is sent as JSON. Claim House checks it before anything leaves, writes it into a standard 837D file with the other claims of your organization, sends the file to the dental network in a batch, reads the acknowledgement (997) and the claim status (277) that come back, and keeps a timeline of every step. A claim has one ID for life: a correction and a resubmission keep it.

With a test key, files go to the sandbox network, which answers each file with a 997 after 30 seconds and a 277 after 60 seconds. Live claims are not open yet: a live key gets 503 CLAIMS_UNAVAILABLE. See Going live.

A key needs submit permission to send or change a claim and read permission to read one. A key with submit only changes only the claims it made, and gets a claim back only in the answers to its own writes (every GET needs read).

Before the first claim: a provider#

Every claim names the dentist who did the work, its rendering provider. Add one (the NPI must have a valid check digit):

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/providers" \
  -H "Authorization: Bearer $CLAIMHOUSE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "first_name": "Riley",
  "last_name": "Example",
  "credential": "DDS",
  "npi": "1999990017",
  "license": { "number": "GA-000000", "state": "GA" }
}
EOF

The office bills the claim: its NPI, tax ID, phone and full nine-digit ZIP go on the file. With OFFICE_ID and PAYER_ID set as in the quickstart and PROVIDER_ID from the answer above, you can send a claim.

Send a claim#

POST /claims takes the office, the payer, the rendering provider, the subscriber (and the patient when it is a dependent), and one line per procedure:

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims" \
  -H "Authorization: Bearer $CLAIMHOUSE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "office_id": "$OFFICE_ID",
  "payer_id": "$PAYER_ID",
  "rendering_provider_id": "$PROVIDER_ID",
  "subscriber": {
    "first_name": "Sam",
    "last_name": "Sample",
    "date_of_birth": "1980-02-02",
    "gender": "F",
    "member_id": "CH-ACTIVE-FULL",
    "address": { "line1": "1 Sample Street", "city": "Sampleville", "state": "GA", "postal_code": "30301" }
  },
  "lines": [{ "cdt": "D2740", "fee": 1140.00, "service_date": "2026-09-24", "tooth": "3" }],
  "metadata": { "pms_claim_number": "PMS-1042" }
}
EOF

The answer is 201 with the claim. By default (submit true) it is validated at once and, with no errors, queued for the next batch:

JSON
{
  "id": "clm_...",
  "object": "claim",
  "state": "queued",
  "next_step": "This claim is queued and goes out in the next batch.",
  "frequency": "original",
  "patient_control_number": "CH4Q7M2K9TRB",
  "office": { "id": "off_...", "name": "..." },
  "payer": { "id": "pyr_...", "payer_id": "...", "name": "..." },
  "rendering_provider": { "id": "prv_...", "first_name": "Riley", "last_name": "Example", "npi": "1999990017" },
  "kind": "claim",
  "totals": { "charge": "1140.00", "lines": 1 },
  "validation": { "validated_at": "...", "errors": 0, "warnings": 1, "findings": [{ "field": "attachments", "code": "ATTACHMENT_RECOMMENDED", "message": "Payers often ask for X-rays, a chart or a narrative before they pay for crowns. Attaching them now saves a request later.", "severity": "warning" }] },
  "attachments": [],
  "network": { "submitted_at": null, "clearinghouse_claim_id": null, "payer_claim_number": null, "rejection": null },
  "metadata": { "pms_claim_number": "PMS-1042" }
}
  • patient_control_number is your identifier for the claim on the wire (CLM01): send your own (1 to 20 letters, digits or hyphens, unique in your organization; never build one from patient details such as a name or a birth date, since it travels on every file) or let Claim House make one (CH and 10 random characters). It comes back on the payer's status and later on the remittance.
  • Send "submit": false to save a draft, and submit it later with POST /claims/{id}/submit.
  • A line's fee is the charge for the whole line, in dollars with at most two decimals, as the claim file sends it (you send a number, or a decimal string such as "1140.00", read exactly; the claim object shows every amount, fee and totals.charge too, as a decimal string such as "1140.00", so nothing is rounded on the way); quantity (default 1) is a count of units and never multiplies the fee. The claim's charge is the sum of its lines' fees. metadata holds up to 20 pairs of your own; it is never sent to the payer.
  • remarks go to the payer as up to five notes of 80 characters, split between words: about 370 characters of ordinary text, never more than 400. A remark that does not fit is a TOO_LONG finding on remarks; nothing is cut. A remark that starts NEA# and a number (an attachment number made elsewhere, as offices type it) also writes the attachment reference segment (PWK) the payer looks for, unless the claim carries attachments. A number of an attachment made through Claim House must be one of the claim's own attachments (REMARKS_NAME_ATTACHMENT): put the attachment on the claim instead.
  • attachments names the closed attachments the claim carries (att_...), in order, at most five: the claim file writes a PWK and a note for each, and their notes share the five notes with the remarks. See Attachments for how to make one, and the findings ATTACHMENT_REQUIRED, ATTACHMENT_OPEN and ATTACHMENT_RECOMMENDED (a warning for crowns, bridges, implants, scaling and root planing and surgical extractions with no attachment).
  • The Idempotency-Key makes a retry safe: the same key and body return the same claim.

Validation findings#

Every check runs together, and every finding has the field it is about (a dotted path such as lines[0].tooth), a stable code, a plain message and a severity:

  • an error keeps the claim from being sent: the claim is kept, in state needs_attention, and still answered with 201;
  • a warning is worth reading but does not stop the claim (an old service date, a provider with no license on file, a payer that needs claims enrollment the provider does not have yet (ENROLLMENT_NOT_ACTIVE, see Payer enrollment), a claim that looks like a duplicate of another open one).

The checks cover the shape of the claim, the CDT codes (format, and what each range needs: a tooth and surfaces for a filling, a tooth for a crown or an extraction, a quadrant for scaling and root planing, an arch for a night guard), the office and provider identifiers, the payer, and finally whether the claim can be written as an 837D at all. The codes are REQUIRED, TOO_SHORT, TOO_LONG, FORMAT_INVALID, IDENTIFIER_DELIMITER, IDENTIFIER_CHARACTER, IDENTIFIER_CONTROL_CHARACTER, IDENTIFIER_SPACING, TEXT_CHARACTER_REPLACED, TEXT_CHARACTER_UNSENDABLE, TEXT_SHORTENED, TEXT_EMPTY_AFTER_CLEANING, DATE_INVALID, DATE_IN_FUTURE, DATE_OF_BIRTH_INVALID, DATE_OF_BIRTH_AFTER_SERVICE, SERVICE_DATE_IN_FUTURE, SERVICE_DATE_NOT_ALLOWED, SERVICE_DATE_OLD, CONTROL_NUMBER_INVALID, PLACE_OF_SERVICE_INVALID, NO_LINES, TOO_MANY_LINES, CDT_FORMAT, CDT_RANGE, FEE_INVALID, FEE_NOT_POSITIVE, QUANTITY_INVALID, TOOTH_INVALID, TOOTH_REQUIRED, SURFACES_NEED_TOOTH, SURFACES_REQUIRED, SURFACE_INVALID, SURFACE_REPEATED, TOO_MANY_SURFACES, AREA_INVALID, AREA_REQUIRED, AREA_NOT_QUADRANT, AREA_NOT_ARCH, TOTAL_MISMATCH, ORIGINAL_REFERENCE_MISSING, OFFICE_NOT_FOUND, OFFICE_NPI_INVALID, OFFICE_TAX_ID_INVALID, OFFICE_ADDRESS_INCOMPLETE, OFFICE_ZIP_PLUS4_REQUIRED, OFFICE_PHONE_INVALID, PROVIDER_NOT_FOUND, PROVIDER_INACTIVE, PROVIDER_NPI_INVALID, PROVIDER_LICENSE_MISSING, PAYER_NOT_FOUND, PAYER_INACTIVE, PAYER_NOT_SUPPORTED_FOR_CLAIMS, ENROLLMENT_NOT_ACTIVE, DUPLICATE_CLAIM, X12_CANNOT_BUILD, OFFICE_ADDRESS_PO_BOX, FILE_NOT_SENT, FILE_REJECTED, ATTACHMENT_REQUIRED, ATTACHMENT_OPEN, ATTACHMENT_RECOMMENDED, ATTACHMENT_NOT_FOUND, ATTACHMENT_NOT_USABLE, REMARKS_NAME_ATTACHMENT, ATTACHMENT_PATIENT_DIFFERS.

POST /claims/validate runs the same checks and stores nothing:

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims/validate" \
  -H "Authorization: Bearer $CLAIMHOUSE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "office_id": "$OFFICE_ID",
  "payer_id": "$PAYER_ID",
  "rendering_provider_id": "$PROVIDER_ID",
  "subscriber": { "first_name": "Sam", "last_name": "Sample", "date_of_birth": "1980-02-02", "gender": "F", "member_id": "CH-ACTIVE-FULL",
    "address": { "line1": "1 Sample Street", "city": "Sampleville", "state": "GA", "postal_code": "30301" } },
  "lines": [{ "cdt": "D2740", "fee": 1140.00, "service_date": "2026-09-24" }]
}
EOF

That crown has no tooth, so the answer has "valid": false and a TOOTH_REQUIRED error on lines[0].tooth.

States and next_step#

A claim is always in one state, and next_step says in one sentence what happens next or what you must do:

statenext_step
draftThis claim is a draft: it has not been checked or sent. Validate it, then queue it to send it.
validatedThis claim passed validation and has not been queued. Queue it to send it in the next batch.
needs_attentionThis claim has errors, so it was not queued. Fix the errors in its validation results, then validate it again.
queuedThis claim is queued and goes out in the next batch.
submittedThis claim was sent to the dental network. Next, the clearinghouse acknowledges the file.
accepted_clearinghouseThe clearinghouse accepted the file. Next, the payer's front end accepts or rejects the claim.
accepted_payerThe payer accepted the claim. Nothing is needed from you while it is processed.
rejectedThis claim was rejected. Fix what the rejection names, then resubmit it.
voidedThis claim was voided. Nothing more will happen to it.
paidThe payer paid this claim (see its payment). Post the payment in your system, then mark the claim reconciled.
deniedThe payer denied this claim (see the reasons in its payment). Correct it and resubmit it as a replacement, or mark it reconciled once posted.
reconciledThe payment is posted. Nothing is needed unless the payer changes or reverses it.
returnedThe payer returned its estimate for this predetermination (see the estimate). Once the treatment is done, convert it into a claim and add the service dates.

needs_attention and rejected are the states you act on: correct the claim and send it again. paid and denied come from the payer's remittance (see ERAs and posting): post the payment in your system, then reconcile the claim (reconciled), or correct a denied claim and send it again. returned is a predetermination's: its estimate arrived (see Predeterminations). The rest move on their own.

Every claim has a kind: claim, or predetermination for one sent before treatment to ask what the payer would pay (no service dates, answered with an estimate). GET /claims lists claims only, unless you ask for kind=predetermination. A claim made from a predetermination (POST /claims/{id}/convert) starts as a draft whose lines have no service_date yet: add them before you send it.

The batch, and what comes back#

Queued claims go out in batches: one 837D file per organization and mode, at most 500 claims each (more go in more files in the same run; the network itself takes up to 25,000 a file). In test mode the batch runs on a schedule (every 30 seconds on a developer's machine), or at once with Send batch now on the dashboard. When the file is sent, each claim in it becomes submitted, and its timeline names the file and the claim's place in it.

  • The 997 acknowledges the file. Accepted: every claim in it becomes accepted_clearinghouse. Rejected (the whole file, at the envelope): every claim in it goes back to queued and out again in the next batch; you do nothing.
  • The 277 gives each claim's status at the payer's front end. Accepted: accepted_payer, with the payer's own claim number (network.payer_claim_number). Rejected: rejected, with the reason in plain words and, when it is known, what to fix (network.rejection). A status that is neither (pending at the payer, say) is added to the timeline and the state stays.
  • The 835 (the remittance) says what the payer paid: the claim becomes paid or denied, with its figures in payment. See ERAs and posting.
  • A status that arrives late, after a later one, or that answers an earlier submission of a claim you corrected and sent again, is recorded on the timeline and changes nothing. A status that cannot be tied to one of your claims and one submission of it is not applied.

A claim is billed once, when its file is handed over to the network: a test-mode claim never is, a claim rejected before it is sent never is, and a claim whose file the network would not take never is. A correction sent again, or a claim sent again after its file was rejected, is not billed a second time. A void transaction is a claim of its own, billed once when it is sent. See Pricing and usage for the price and what is still to be confirmed.

The timeline#

GET /claims/{id}/timeline lists what happened, oldest first:

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims/$CLAIM_ID/timeline" -H "Authorization: Bearer $CLAIMHOUSE_KEY"
JSON
{
  "object": "claim_timeline",
  "claim_id": "clm_...",
  "data": [
    { "type": "created", "occurred_at": "...", "summary": "Claim created", "detail": { "via": "api_key" }, "actor": { "kind": "api_key", "id": "key_..." } },
    { "type": "validated", "occurred_at": "...", "summary": "Validated · 0 errors, 1 warning", "detail": { "errors": 0, "warnings": 1 }, "actor": null },
    { "type": "queued", "occurred_at": "...", "summary": "Queued for the next batch", "detail": {}, "actor": null },
    { "type": "submitted", "occurred_at": "...", "summary": "Submitted to network · batch 20260924210030000.837, claim 1 of 1", "detail": { "file_name": "20260924210030000.837", "position": 1 }, "actor": null },
    { "type": "accepted_clearinghouse", "occurred_at": "...", "summary": "Accepted by clearinghouse · 997", "detail": {}, "actor": null },
    { "type": "accepted_payer", "occurred_at": "...", "summary": "Accepted by payer front end · 277 · STC A1:20", "detail": { "payer_claim_number": "..." }, "actor": null }
  ]
}

The same steps are sent as events: claim.submitted, claim.accepted, claim.rejected and the others, each with the claim's ID and state only. Read the claim by its ID for the rest.

To find claims, GET /claims filters by state, office, payer, creation time, the request that made a claim, or its patient control number:

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims?state=queued&limit=10" -H "Authorization: Bearer $CLAIMHOUSE_KEY"

Try the scenarios#

In test mode the subscriber's member_id picks what the network does:

Member IDWhat happens
CH-REJECT-A7Accepted in the 997, then rejected by the payer in the 277 with A7:33 (subscriber not found)
CH-REJECT-A3Accepted in the 997, then returned by the payer as unprocessable in the 277 with A3:21
CH-997-REJECTThe whole file is rejected in the 997: its claims return to the queue, go out in the next batch and are accepted
CH-ATTACH-REQUIREDA claim fails validation with an attachment-required error until a closed attachment (an X-ray) is on it; then it is accepted
CH-SOLICITEDThe payer accepts the claim, then asks for a narrative and an X-ray 1 minute later (a payer request)
CH-PAID-ERAAccepted by the payer, then paid in an 835 2 minutes later: 80% of an allowed amount below the charge on each line (a contractual adjustment and coinsurance)
CH-DENIEDAccepted by the payer, then denied in an 835 2 minutes later: the benefit maximum was reached

Any other member ID is accepted by the clearinghouse (997 after 30 seconds) and by the payer (277 after 60 seconds, A1:20).

Correct and resubmit#

A claim that needs attention, or that was rejected, is corrected under the same ID. PATCH /claims/{id} changes only the fields you send (the subscriber and the patient field by field; the lines as a whole) and checks the result again. POST /claims/{id}/resubmit does the same and queues the claim when it has no errors.

Start with a claim that needs attention, a crown without its tooth:

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims" \
  -H "Authorization: Bearer $CLAIMHOUSE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d @- <<EOF
{
  "office_id": "$OFFICE_ID",
  "payer_id": "$PAYER_ID",
  "rendering_provider_id": "$PROVIDER_ID",
  "subscriber": { "first_name": "Sam", "last_name": "Sample", "date_of_birth": "1980-02-02", "gender": "F", "member_id": "CH-REJECT-A7",
    "address": { "line1": "1 Sample Street", "city": "Sampleville", "state": "GA", "postal_code": "30301" } },
  "lines": [{ "cdt": "D2740", "fee": 1140.00, "service_date": "2026-09-24" }]
}
EOF

Then give the line its tooth and send it on:

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims/$FIX_ID/resubmit" \
  -H "Authorization: Bearer $CLAIMHOUSE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "lines": [{ "cdt": "D2740", "fee": 1140.00, "service_date": "2026-09-24", "tooth": "3" }] }'

A rejected claim the payer already has (it has a payer_claim_number) goes out again as a replacement that names that number; one the payer never had goes out as an original. A claim the payer accepted, paid or denied is corrected the same way, with resubmit: the correction is checked first (one with errors is refused with them, and the claim stays as it was), then it goes out again as a replacement, under the same claim ID and with its attachments. A reconciled claim is not (you posted it), and a claim with a void in progress cannot be corrected, validated or sent until the payer answers the void.

Changed your mind before the replacement went out? POST /claims/{id}/cancel_correction puts the claim back as the payer has it: its content, lines and attachments as they were before the correction (your edits are undone; an attachment that went to another claim meanwhile, or no longer fits the claim, is named on the timeline), the frequency it had, and the state its payments leave it in (accepted_payer, paid or denied), while the correction needs attention, is validated or is queued (also after the clearinghouse rejected the replacement's file, or the network would not take it). Once the replacement has gone to the payer, it can no longer be cancelled; with no correction open it answers 409 CONFLICT too.

Void a claim#

POST /claims/{id}/void voids a claim the payer has not seen (a draft, a validated or queued claim, one that needs attention, or one rejected before the payer numbered it) at once. For a claim the payer has (it has a payer_claim_number, or it is a correction of one), it queues a void transaction, a new claim linked to it (void_transaction), and the original becomes voided when the payer accepts the void; that holds for a claim opened for a correction too, so the payer is always told. A claim between being sent and the payer's answer cannot be voided until the answer arrives, and neither can a replacement queued to go out (cancel the correction first). A predetermination that was converted into a claim is not voided: void the claim made from it.

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims/$FIX_ID/void" \
  -H "Authorization: Bearer $CLAIMHOUSE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'

Read the 837D#

GET /claims/{id}/x12 returns the claim as it was sent, as text: the envelope of its file (ISA, GS, ST and the header), the levels above the claim (the billing office, the subscriber and, for a dependent, the patient), the claim loop (CLM, the rendering provider, one LX, SV3, TOO, DTP and REF*6R per line), and the trailers, one segment per line. The other claims of the file are left out, and SE, GE and IEA are counted for what is shown. Until the claim is in a file, it is not found:

Shell
curl "https://sandbox.myclaimhouse.com/api/v1/claims/$FIX_ID/x12" -H "Authorization: Bearer $CLAIMHOUSE_KEY"

A few things worth knowing when you read one: the claim's patient control number is CLM01 and, with the line number, each line's REF6R; the place of service and frequency are in CLM05 (11:B:1 is an original at an office, :7 a replacement, :8 a void, with REFF8 naming the payer claim number); a tooth and its surfaces are in TOO; ISA15 is T in test mode, and in test mode the receiver (1000B) is CLAIM HOUSE SANDBOX, the sandbox network the file went to. The reference shows a whole example.