Claim House API

v1 · 82 endpoints · https://api.claimhouse.ai

Getting started

Overview

One API for dental claims, attachments, eligibility and payments, with the same rules as the dashboard.

Claim House is one API for the whole dental claim: send the claim, attach the documents the payer needs, check eligibility before the visit, and follow the claim through acknowledgments to payment.

Everything your staff can do in the Claim House dashboard is available here. The dashboard calls the same /v1 routes with the same permissions, office scope, edits, holds and one-attempt rules, and writes to the same ledger.

InfoClaim House is in early access. API keys are issued as part of onboarding — until your account is provisioned, the API responds with a release notice. To request access, contact the Claim House team.
AreaWhat you doStart with
ClaimsSubmit a finished 837D or a JSON claim, then track it from queued to paidPOST /v1/submissions
Claim draftsStage a claim, review findings and the rendered 837D, then send it oncePOST /v1/claims/drafts
AttachmentsStore x-rays, charts and narratives and send them to the attachment network oncePOST /v1/attachment-drafts
EligibilityRun one real-time eligibility check and download it as a PDFPOST /v1/eligibility
PaymentsRead 835 remittances and what each claim was paidGET /v1/remittances
Events and webhooksKeep your system in sync without polling every claimGET /v1/events

Base URL

https://api.claimhouse.ai/v1
  • Requests and responses are JSON over HTTPS.
  • Every response carries an X-Request-Id header. Log it and quote it when you contact Claim House.
  • Claim House owns every clearinghouse and network credential. You never hold a vendor login.
InfoAll ids, keys and patient values in these docs are synthetic. Replace key_test_EXAMPLE.secret_EXAMPLE with the key Claim House issues you.
Getting started

Quickstart

Send your first test claim in four calls with curl.

You need a test API key with read and submit, a test office (binding sandbox), and payer enrollment for that office. Claim House sets up the first key and enrollment during onboarding.

export CH_BASE="https://api.claimhouse.ai"
export CH_KEY="key_test_EXAMPLE.secret_EXAMPLE"
  1. Confirm your key with GET /v1/me: mode is test, scope.permissions includes submit, and reachable_facility_ids lists your office.
  2. Find your office and payer. The claim must carry the office's billing NPI and TIN, and the payer's primaryPayerId.
  3. Submit the 837D with a fresh Idempotency-Key stored before you send. A 202 means intake ran, not that the claim passed: QUEUED waits for a transport window, HOLD is under review (do not resubmit), REJECTED_PRE_TRANSPORT failed an edit (fix it and use a new key).
  4. Track the claim. Read state for logic and status for people. Claims move when a transport window runs (about every 5 minutes), so poll no faster than once a minute, or use webhooks.

1. Confirm your key

curl -s "$CH_BASE/v1/me" \
  -H "Authorization: Bearer $CH_KEY"

2. Find your office and payer

curl -s "$CH_BASE/v1/facilities" \
  -H "Authorization: Bearer $CH_KEY"

curl -s "$CH_BASE/v1/payers/search?q=sample" \
  -H "Authorization: Bearer $CH_KEY"

3. Submit an 837D

curl -s -X POST "$CH_BASE/v1/submissions" \
  -H "Authorization: Bearer $CH_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f9a7c1e-EXAMPLE-0001" \
  -d '{
    "facility_id": "fac_EXAMPLE0000000000001",
    "mode": "test",
    "x12": "ISA*00*          *00*          *ZZ*SENDEREXAMPLE  *ZZ*RECEIVEREXAMPLE*...~",
    "tenant_reference": "batch-EXAMPLE-001"
  }'
{
  "request_id": "evt_EXAMPLE0000000000001",
  "submission_id": "sub_EXAMPLE0000000000001",
  "state": "QUEUED",
  "idempotent_replay": false,
  "claims": [
    {
      "claim_id": "clm_EXAMPLE0000000000001",
      "tenant_claim_id": "SAMPLE0001",
      "pcn": "EXA-SAMPLE0001",
      "payer_id": "SYNPAYER01",
      "state": "QUEUED",
      "was_tokenized": false
    }
  ],
  "validation": {
    "status": "ACCEPTED",
    "errors": []
  },
  "hold_reason": null,
  "dispatch": {
    "window_id": null,
    "expected_transport_by": "2026-09-14T18:05:00+00:00"
  }
}

4. Track the claim

curl -s "$CH_BASE/v1/claims/clm_EXAMPLE0000000000001/tracking" \
  -H "Authorization: Bearer $CH_KEY"
Getting started

Authentication and access

Authenticate with an API key, then permissions, office scope and mode decide what the call may do.

Send your key in the Authorization header on every request. The gateway then checks four things, in order: who is calling, what they may do, which offices they reach, and which environment they are in.

curl -s "https://api.claimhouse.ai/v1/me" \
  -H "Authorization: Bearer key_test_EXAMPLE.secret_EXAMPLE"

API keys

  • A key is key_ plus 20 characters, a dot, and a secret. The secret is shown once when the key is created and can never be read back.
  • Claim House issues your first key. A key with admin can mint equal or weaker keys with POST /v1/keys and revoke them with DELETE /v1/keys/{key_id}.
  • Unknown, malformed, wrong and revoked keys all return the same 401 UNAUTHORIZED. Five failures for one key from one address in 60 seconds block that key for 60 seconds.
  • The dashboard signs people in with a session token instead of a key. The session gets the same permission and office checks.

Permissions

PermissionUnlocks
readEvery read: claims, tracking, submissions, events, remittances, artifacts, offices, payers, eligibility results, drafts, keys, usage, GET /v1/me.
submitClaim submissions and drafts, eligibility checks and PDFs, attachment packets, drafts and sends.
webhooksCreate webhook endpoints, rotate secrets, list and replay deliveries.
adminMint and revoke keys, create and import offices, set claim naming, register an office with NEA, confirm attachment provider profiles.

Permissions do not imply each other: an admin only key cannot read claims. Eligibility checks need submit because they call a vendor. A missing permission returns 403 PERMISSION_DENIED.

Office scope

ScopeReaches
sellerEvery office in your organization.
groupEvery office in the granted groups, resolved on each request.
facilityExactly the granted offices.

Naming an office outside your scope returns 403 FACILITY_NOT_GRANTED. An id that belongs to another organization, or to an office you cannot reach, returns 404 NOT_FOUND so ids cannot be probed. Keys, usage, payers and webhook endpoints are organization-wide regardless of scope.

InfoAlways pass explicit scopeIds when you mint a group or facility key. A key minted without them reaches no office.
Getting started

Test mode

Test keys work only against sandbox offices, and production keys only against production offices.

Every key has a mode, test or production, stored with the key and shown on GET /v1/me. Every office has a transport binding. The mode of the call must match the office.

Office binding_kindAccepts
sandboxTest keys
productionProduction keys
  • Offices you create with POST /v1/facilities are always sandbox. Moving an office to production is a Claim House action, not an API call.
  • Test claims are not simulated. When Claim House arms transport for a sandbox office, they go to the clearinghouse test inbox marked as test files. Until then they wait in QUEUED, which is expected.
  • Claim drafts and the attachment workspace are partitioned by mode. A key only sees drafts of its own mode.
RouteMode ruleRefusal
POST /v1/submissionsBody mode must equal the key mode and the office binding403 MODE_MISMATCH
POST /v1/dental-claims/submissionusageIndicator T needs a test key, P a production key403 MODE_MISMATCH
POST /v1/dental-claims/raw-x12-submissionThe key mode must match the office binding403 MODE_MISMATCH
Claim draftsThe draft takes the key mode at create403 DRAFT_ACCESS_DENIED
Attachment drafts and providersThe office must be active in the key mode403 MODE_MISMATCH
ImportantEligibility checks and attachment packet sends do not enforce mode today. A test key can trigger a real vendor call on those rails for any office it reaches.
Getting started

Sending once

Repeat a request safely with the same Idempotency-Key, and never resend an action whose outcome is unknown.

Two promises protect every claim. You can safely repeat a request: the same Idempotency-Key with the same content returns the original answer, not a second claim. And Claim House contacts a vendor exactly once per action: if the outcome is uncertain it says so and stops.

RouteIdempotency-Key
POST /v1/submissionsRequired
POST /v1/dental-claims/submissionRequired, 1 to 255 characters
POST /v1/dental-claims/raw-x12-submissionRequired, 1 to 255 characters
POST /v1/eligibilityRequired
POST /v1/claims/drafts/{draft_id}/submitOptional. The draft has its own key from create

What a repeat returns

You sendResult
Same key, same contentThe stored answer with idempotent_replay: true. No new claim.
Same key, different content422 IDEMPOTENCY_KEY_REUSED
Same key while the first is still running409 IDEMPOTENCY_IN_PROGRESS, retry after 5 seconds
New key, identical content, same office, within 24 hours409 DUPLICATE_CONTENT naming the original submission

Keys belong to your organization, not to one API key. Use a random value such as a UUID and never derive it from patient or claim data. A replay tells you what intake decided, not where the claim is now: read tracking for the current state.

When the outcome is unknown

  1. Your HTTP call timed out or returned a plain 500: send the same body with the same Idempotency-Key. You get the stored answer, IDEMPOTENCY_IN_PROGRESS, or a normal first answer if the first request never arrived.
  2. A claim is TRANSPORT_AMBIGUOUS or HOLD: do nothing. Claim House resolves it and you receive claim.operator_resolved or the next claim event.
  3. An attachment send returned outcome: AMBIGUOUS or 503 ATTACHMENT_SEND_UNCERTAIN: do not build a new packet for the same claim. Claim House reconciles it with the network.
  4. An eligibility check returned 502 VENDOR_ERROR: Claim House will not retry. Run a new check with a new key only when you decide to.
ImportantRetry-After only permits retrying the local refusal that carried it (rate limit, quota, in-progress replay, auth block). It never authorizes resending a vendor action.
ImportantKeys and content hashes stay unique in storage beyond the 24-hour window. Reusing a key, or resending identical content for the same office, after 24 hours currently fails with a plain 500. Always use a new key for a new submission.
Getting started

Errors

Every refusal uses one JSON envelope with a stable machine code.

Branch on error, never on message. Wording can change; codes do not.

{
  "error": "FACILITY_NOT_GRANTED",
  "message": "this key's grants do not cover that facility",
  "errors": [
    {
      "facility_id": "fac_EXAMPLE0000000000001"
    }
  ],
  "request_id": "evt_EXAMPLE0000000000009"
}
FieldTypeDescription
errorstringStable code.
messagestringHuman-readable sentence.
errorsarrayStructured detail. Shape depends on the code. Never echoes a claim or patient value.
request_idstringSame value as the X-Request-Id header.

Most important codes

StatusCodeWhat to do
400IDEMPOTENCY_KEY_REQUIREDAdd an Idempotency-Key. Nothing was recorded.
400X12_PARSE_ERRORFix the 837D structure and resubmit.
401UNAUTHORIZEDCheck the key. Do not loop: five failures in 60 seconds block the key for 60 seconds.
403PERMISSION_DENIEDUse a key with the permission named in errors[0].permission.
403FACILITY_NOT_GRANTEDUse an office from reachable_facility_ids on GET /v1/me.
403MODE_MISMATCHUse a test key for sandbox offices and a production key for production offices.
403BILLING_IDENTITY_MISMATCHSend the office's registered billing NPI and TIN.
404NOT_FOUNDCheck the id and your key's scope.
409DUPLICATE_CONTENTDo not resend. Track the original submission.
409PAYER_NOT_ENROLLEDComplete enrollment with Claim House, then submit.
422INVALID_REQUESTFix the fields named in errors[].location.
422IDEMPOTENCY_KEY_REUSEDUse a new key for new content.
429TOO_MANY_REQUESTSBack off for Retry-After seconds and retry.
502VENDOR_ERROROne attempt was made. Decide whether to run a new check.

Validation errors

A 422 INVALID_REQUEST lists one item per problem with code, location (a dotted path such as body.claimInformation.serviceLines), message, and value_redacted: true. Most request models refuse unknown fields, so send only documented fields.

Results that are not errors

  • POST /v1/submissions always answers 202 when intake ran. Read state: QUEUED, HOLD or REJECTED_PRE_TRANSPORT.
  • The JSON and raw X12 claim routes answer 200 with status ACCEPTED or HOLD, or 400 with status REJECTED and findings in errors[].
  • Attachment packet sends answer 200 with outcome OK, FAILED or AMBIGUOUS.
InfoAn unmapped server fault returns a plain text 500 with no envelope. Treat it like a timeout: look the resource up, or repeat with the same Idempotency-Key.
Getting started

Pagination and rate limits

Follow the cursor each list route returns, and stay inside the per-key rate and concurrency limits.

List routes grew with the rails they serve, so paging style varies. Always pass back exactly the cursor the previous page returned and treat it as opaque.

RouteStylePage size (default / max)Order
GET /v1/claimscursor, next_cursor, has_more100 / 500Last updated, newest first
GET /v1/remittancescursor, next_cursor, has_more100 / 500Received, newest first
GET /v1/eventssince_cursor (integer), next_cursor, has_more100 / 500Sequence, oldest first
GET /v1/payerspageToken, nextPageToken50 / 50Payer id
GET /v1/claims/draftscursor, next_cursor, has_more50 / 200Newest first
GET /v1/attachment-draftscursor (last draft id), next_cursor100 / 100Draft id, descending
GET /v1/facilities/{facility_id}/eligibilitysince and limit, no cursor100 / 500Newest first
InfoGET /v1/claims orders by updated_at, which changes on every claim event. A claim that moves while you page can be skipped or repeated. For a complete sync, read GET /v1/events from your stored next_cursor.

Rate limits

LimitValueOn breach
Submit bucket20 requests per second per key429 TOO_MANY_REQUESTS, Retry-After: 1
Read bucket (also webhooks and admin routes)100 requests per second per key429 TOO_MANY_REQUESTS, Retry-After: 1
In flight20 concurrent requests per key429 TOO_MANY_REQUESTS, Retry-After: 1
Daily claim quota50,000 claims per organization per UTC day429 QUOTA_EXCEEDED, Retry-After until UTC midnight
Failed authentication5 failures in 60 seconds per key and address401 UNAUTHORIZED, Retry-After: 60
Payload6 MB of X12413 PAYLOAD_TOO_LARGE
  • A 429 from the rate guard is returned before any work, so the identical request is safe to retry after Retry-After.
  • Keys of the same organization have separate buckets but share the daily quota and the idempotency namespace.
  • Keep concurrency at or below 10 per key, add jitter, and prefer webhooks or GET /v1/events over tight polling.
Getting started

Claim lifecycle

How a claim moves from queued to paid, and what each clearinghouse and payer response means.

Every move is recorded as an event on the claim's ladder. Later rungs win: a 277 that arrives before its 997 still advances the claim, and a late 997 never moves it back.

QUEUED -> TRANSPORTED -> ACK_997_ACCEPTED -> STATUS_277_RECEIVED / ACCEPTED / PENDING / FINAL -> PAID / DENIED / REVERSED
StateMeaningWhat you do
QUEUEDPassed every Claim House edit, waiting for a transport window.Wait.
TRANSPORTEDThe file reached the clearinghouse and the upload was verified.Wait for the 997.
ACK_997_ACCEPTEDThe clearinghouse accepted the file (997 or 999).Wait for the payer's 277.
STATUS_277_*The payer reported received, accepted, pending or final status.Wait for the 835.
PAID, DENIED, REVERSEDAn 835 adjudicated the claim.Read the remittance.
HOLDStored but held for Claim House review. Never sent while held.Do not resubmit.
REJECTED_PRE_TRANSPORTFailed a Claim House edit at submission.Fix it and resubmit with a new key.
TRANSPORT_AMBIGUOUSClaim House cannot prove the file arrived.Do not resubmit. An operator resolves it.
NEEDS_CORRECTIONA 997, 999 or 277 rejected the claim.Correct and resubmit as a new claim.
STALLED_997, STALLED_277An expected response is late (997 after 4 hours, 277 after 2 days by default).Nothing yet. Claim House is checking.

What each response file means

FileFromAnswersDoes not mean
Gateway 999Claim House, in the submission responseThe file passed Claim House structural edits.Nothing has been sent yet.
999 / 997ClearinghouseThe file or transaction set was accepted or rejected.The payer has seen the claim.
277CAClearinghouse or payerThe claim was accepted into the payer's system.A payment decision.
277PayerClaim status: received, accepted, pending, finalized or rejected.Money. A final 277 is not a payment.
835PayerPaid amount, patient responsibility, adjustments and the payer claim number.

Claim House sends claims in transport windows, about every 5 minutes. dispatch.expected_transport_by on the submission response is an estimate. An office whose transport is not armed keeps its claims in QUEUED.

InfoWhen an office has claim naming set, each claim gets a human reference such as SAMPLE-20260914-0001 on first transport. You can search for it with GET /v1/claims?tenant_claim_id=.
Getting started

Webhooks

Receive signed claim events at your HTTPS endpoint instead of polling.

Register an HTTPS endpoint with POST /v1/webhook-endpoints and the exact event types you want. The response includes a signing_secret (starts with whsec_) that is shown once. An endpoint receives events for every office in your organization, up to 4 endpoints per organization.

EventMeaning
claim.queuedPassed intake, waiting for a transport window.
claim.receivedStored on hold at intake.
submission.rejected_pre_transportFailed Claim House edits. It will never be sent.
claim.transportedThe file reached the clearinghouse, verified.
claim.transport_ambiguousClaim House cannot prove the file arrived. Do not resubmit.
claim.operator_resolvedAn operator recorded the outcome of an ambiguous file.
claim.ack_997, claim.ack_999A clearinghouse acknowledgment arrived.
claim.status_277A payer 277 or 277CA arrived.
claim.stalled_997, claim.stalled_277An expected response is late.
claim.paid, claim.denied, claim.reversedAn 835 adjudicated the claim.
remittance.receivedAn 835 carrying your claims was processed.
eligibility.checked, eligibility.pdf_generatedEligibility activity.
attachment.packet_created, attachment.sent, attachment.failedAttachment activity tied to a claim.

Payload

Each delivery is one POST with ids and codes only, never patient data or amounts. Read the claim or the event feed for details.

{
  "artifact_id": "art_EXAMPLE0000000000001",
  "claim_id": "clm_EXAMPLE0000000000001",
  "event_id": "evt_EXAMPLE0000000000020",
  "facility_id": "fac_EXAMPLE0000000000001",
  "occurred_at": "2026-09-15 12:04:05.123456+00:00",
  "seller_id": "sel_EXAMPLE0000000000001",
  "submission_id": "sub_EXAMPLE0000000000001",
  "summary": {
    "category_code": "A2",
    "entity_code": "PR",
    "status_code": "20"
  },
  "tenant_claim_id": "EXA-SAMPLE0001",
  "type": "claim.status_277"
}

Verify the signature

Every delivery carries X-BlueLine-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>. Each v1 is the HMAC-SHA256 of <t>.<raw body> keyed with your whole signing secret. During a secret rotation there are two v1 values; accept if any matches. Use a timestamp tolerance of at least 15 minutes.

const crypto = require("node:crypto");

const TOLERANCE_SECONDS = 900;

function verifyClaimHouseWebhook(rawBody, header, secret) {
  let timestamp = null;
  const candidates = [];
  for (const part of String(header || "").split(",")) {
    const index = part.indexOf("=");
    const name = part.slice(0, index).trim();
    const value = part.slice(index + 1).trim();
    if (name === "t") timestamp = /^\d+$/.test(value) ? Number(value) : NaN;
    else if (name === "v1") candidates.push(value);
  }
  if (!Number.isFinite(timestamp) || candidates.length === 0) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.`)
    .update(rawBody) // a Buffer: use express.raw({ type: "application/json" })
    .digest("hex");
  const expectedBuffer = Buffer.from(expected, "utf8");
  return candidates.some((candidate) => {
    const given = Buffer.from(candidate, "utf8");
    return given.length === expectedBuffer.length && crypto.timingSafeEqual(given, expectedBuffer);
  });
}
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 900


def verify_claimhouse_webhook(raw_body: bytes, header: str, secret: str) -> bool:
    timestamp = None
    candidates = []
    for part in (header or "").split(","):
        name, _, value = part.strip().partition("=")
        if name == "t":
            if not value.isdigit():
                return False
            timestamp = int(value)
        elif name == "v1":
            candidates.append(value)
    if timestamp is None or not candidates:
        return False
    if abs(time.time() - timestamp) > TOLERANCE_SECONDS:
        return False
    message = f"{timestamp}.".encode("ascii") + raw_body
    expected = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, candidate) for candidate in candidates)

Delivery

  • At least once: deduplicate on event_id and tolerate out-of-order events.
  • Answer 2xx within 5 seconds. Redirects are not followed. Failed attempts retry after 1, 5, 30, 120 and 600 seconds, then the delivery is PARKED.
  • A new endpoint receives every existing event of its subscribed types, so expect a backlog on first registration.
  • Fix your endpoint, then replay parked deliveries, or reconcile from GET /v1/events.
API reference

Claims

Submit dental claims as 837D or JSON, then track each claim from queued to paid.

PathRouteYou sendUse it when
Raw 837DPOST /v1/submissionsA finished 837D (up to 1,000 claims) in a JSON envelopeYour system already produces 837D. The primary partner path.
JSON claimPOST /v1/dental-claims/submissionOne claim as dental claim JSONYou hold claim fields and want Claim House to build the 837D.
Raw 837D, compat responsePOST /v1/dental-claims/raw-x12-submission{"x12": "..."}You are migrating an existing clearinghouse integration.
Draft, build, submitPOST /v1/claims/draftsX12, JSON or ADA form fieldsA person reviews findings before the one send. See Claim drafts.

Every path runs the same intake: office grant, parse, billing identity and mode, companion guide edits, holds (attachment packet parity, subscriber and patient identity), payer enrollment, then idempotency. None of them contacts the clearinghouse during the call. Passing claims are QUEUED and leave in the next transport window, about every 5 minutes.

OutcomeMeaningWhat you do
QUEUED / ACCEPTEDPassed every edit, waiting for a transport window.Track the claim.
HOLDStored but held for Claim House review (PACKET_PARITY, IDENTITY_HIERARCHY, or a builder hold).Do not resubmit. Watch events.
REJECTED_PRE_TRANSPORT / REJECTEDFailed an edit. Findings list the codes.Correct and resubmit with a new Idempotency-Key.
ImportantTest claims are not simulated. A test key submits to a sandbox office, and the claim goes to the clearinghouse test inbox once Claim House arms that office. Until then it waits in QUEUED.

After intake, read GET /v1/claims/{claim_id}/tracking for a plain sentence and the next expected event, GET /v1/claims/{claim_id} for the full ladder and payments, or subscribe to webhooks. See the Claim lifecycle guide for every state.

API reference

Submit an 837D file

POSThttps://api.claimhouse.ai/v1/submissions

Send one finished 837D file for one office and receive an intake receipt.

Accepts one 837D interchange (or bare ST..SE sets, up to 1,000 claims) for one office, validates it, and answers with a receipt. This is the primary path for partners whose systems already produce 837D.

The answer is always 202, whether the submission was queued, held or rejected. Read state and hold_reason to tell them apart.

Permission
submit
Idempotency
required
Side effects
Stores the submission, its claims, lines and intake events, including held and rejected ones. Never contacts the clearinghouse during the call; a later dispatch window transports queued files exactly once.
In the dashboard
Claims > Submit a claim > Drop a file

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Idempotency-Key*stringYour unique value per logical submission. Same key and same X12 within 24 hours replays the stored receipt.
e.g. idem-EXAMPLE-0001
Content-Type*stringMust be application/json. The 837D travels inside the JSON body.
e.g. application/json

Request body

NameTypeDescription
facility_id*stringThe office the file bills as. Must be granted to your key. 1 to 64 characters.
e.g. fac_tycrfy0cs0qc7sq95eme
mode*stringMust equal the key mode and the office binding.
one of: production · test
e.g. test
x12*stringThe 837D text, version 005010X224A2. At most 6 MB (UTF-8 bytes).
e.g. ISA*00*...~
formatstringReserved. Only x12_837d, the default.
one of: x12_837d
tenant_referencestringYour reference for the whole submission, stored on the submission (not on each claim). At most 200 characters.
e.g. batch-EXAMPLE-001
attachmentsarrayArray of strings. NEA numbers you expect in PWK06, with or without the NEA# prefix. When present, the file's PWK NEA set must equal this set exactly or the submission is rejected with PWK_PARITY.
attachment_packet_idstringA pkt_ attachment packet id. The file's PWK NEA set must equal the packet's recorded reference or the submission holds with PACKET_PARITY. At most 64 characters.
e.g. pkt_20fng953rtp1hwc3zdcn

Request example

{
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "mode": "test",
  "x12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*HC*TESTSENDER*262403888*20260905*1430*1*X*005010X224A2~ST*837*0001*005010X224A2~BHT*0019*00*TESTBATCH0001*20260905*1430*CH~NM1*41*2*TEST SUBMITTER*****46*TESTSENDER~PER*IC*TEST CONTACT*TE*5555550100~NM1*40*2*TEST RECEIVER*****46*262403888~HL*1**20*1~PRV*BI*PXC*1223G0001X~NM1*85*2*TEST BILLING OFFICE*****XX*9999999995~N3*1 TEST STREET~N4*TESTCITY*OH*44000~REF*EI*990000001~REF*0B*TESTLIC0001~HL*2*1*22*0~SBR*P*18*******CI~NM1*IL*1*TEST*PATIENT****MI*TESTMEMBER01~N3*2 TEST LANE~N4*TESTCITY*OH*44000~DMG*D8*19800101*F~NM1*PR*2*TEST PAYER*****PI*TESTPAYER1~CLM*SYN0002*150.00***11:B:1*Y*A*Y*Y~DTP*472*D8*20260901~HI*BK:K089~NM1*82*1*TEST*PROVIDER****XX*9999999995~LX*1~SV3*AD:D0120*150***1~DTP*472*D8*20260901~SE*27*0001~GE*1*1~IEA*1*000000001~",
  "tenant_reference": "batch-sample-001"
}

Response fields

NameTypeDescription
request_id*stringThe request id, also in X-Request-Id.
submission_id*stringThe submission id, sub_....
state*stringSubmission state at intake.
one of: QUEUED · HOLD · REJECTED_PRE_TRANSPORT
idempotent_replay*booleantrue when this is the stored answer to an earlier request.
claims*arrayOne entry per CLM in the file.
claim_id*stringThe claim id, clm_....
tenant_claim_id*stringYour original CLM01 as it appeared in the file.
pcn*stringCLM01 as transmitted: <office prefix>-<CLM01>, tokenized when longer than 17 characters.
payer_id*stringLoop 2010BB payer id from the file.
state*stringSame as the submission state at intake.
was_tokenized*booleantrue when the PCN is a 12-character token instead of your CLM01.
lines*arrayService lines.
line_control_number*stringREF*6R, filled by Claim House when absent.
procedure_code*stringCDT code.
validation*objectIntake findings.
status*stringREJECTED when any finding has severity error (a hold finding included), otherwise ACCEPTED.
one of: ACCEPTED · REJECTED
errors*arrayEvery finding, errors and warnings.
code*stringFinding code, for example CLM01_DUPLICATE, PWK_PARITY, LICENSE_RECOMMENDED, PACKET_PARITY.
severity*stringSeverity.
one of: error · warning
location*stringWhere the finding applies, for example transaction[0]/claim[1], GS08, interchange.
message*stringExplanation. Never contains a value from your file.
value_redacted*booleanAlways true.
acknowledgment_999*stringThe Claim House 999 for this intake decision. Never transported.
hold_reasonstringPACKET_PARITY or IDENTITY_HIERARCHY when held. Null otherwise.
dispatchobjectDispatch estimate. Null for held and rejected submissions.
window_idstringAlways null at intake.
expected_transport_by*datetimeEstimated transport time: received_at plus the intake window.

Responses

202Accepted (queued)
{
  "request_id": "evt_p9aqswwyr9nt6wattrtk",
  "submission_id": "sub_7arkec5jh0g25b3brgt3",
  "state": "QUEUED",
  "idempotent_replay": false,
  "claims": [
    {
      "claim_id": "clm_5nez6hnk7bpw5j7fwkha",
      "tenant_claim_id": "SYN0002",
      "pcn": "SBX-SYN0002",
      "payer_id": "TESTPAYER1",
      "state": "QUEUED",
      "was_tokenized": false,
      "lines": [
        {
          "line_control_number": "CLMHQ85EN97SS7PQ35GH8ZJ",
          "procedure_code": "D0120"
        }
      ]
    }
  ],
  "validation": {
    "status": "ACCEPTED",
    "errors": []
  },
  "acknowledgment_999": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*FA*TESTSENDER*262403888*20260905*1430*1*X*005010X231A1~ST*999*0001*005010X231A1~AK1*HC*1*005010X224A2~AK2*837*0001*005010X224A2~IK5*A~AK9*A*1*1*1~SE*6*0001~GE*1*1~IEA*1*000000001~",
  "hold_reason": null,
  "dispatch": {
    "window_id": null,
    "expected_transport_by": "2026-09-05T14:35:00+00:00"
  }
}
202Accepted (held)
{
  "request_id": "evt_qrspxxcadxgt581h8wkc",
  "submission_id": "sub_dqpvz0m7ww1eb839t8p1",
  "state": "HOLD",
  "idempotent_replay": false,
  "claims": [
    {
      "claim_id": "clm_mqzcgzj0a5xeksgf3k6s",
      "tenant_claim_id": "CLAIMEXAMPLE1",
      "pcn": "EXA-CLAIMEXAMPLE1",
      "payer_id": "PAYEREXAMPLE",
      "state": "HOLD",
      "was_tokenized": false,
      "lines": [
        {
          "line_control_number": "LINEEXAMPLE1",
          "procedure_code": "D1110"
        }
      ]
    }
  ],
  "validation": {
    "status": "REJECTED",
    "errors": [
      {
        "code": "PACKET_PARITY",
        "severity": "error",
        "location": "interchange",
        "message": "the PWK NEA references do not match the attachment packet; the submission is held",
        "value_redacted": true
      }
    ]
  },
  "acknowledgment_999": "ISA*00*...*999*...~",
  "hold_reason": "PACKET_PARITY",
  "dispatch": null
}
403Billing identity mismatch
{
  "error": "BILLING_IDENTITY_MISMATCH",
  "message": "billing identity mismatch",
  "errors": [
    {
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "field": "billing_npi"
    }
  ],
  "request_id": "evt_1rh01fbqrnwm6xfrbm03"
}
409Duplicate content
{
  "error": "DUPLICATE_CONTENT",
  "message": "identical content was already submitted for this facility",
  "errors": [
    {
      "submission_id": "sub_0n9j105ww2rjxwph6ef4"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
400IDEMPOTENCY_KEY_REQUIREDHeader missing or blank
400X12_PARSE_ERRORText does not parse as 837D
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route permission
403FACILITY_NOT_GRANTEDOffice unknown or outside your grants
403MODE_MISMATCHmode differs from the key or office binding
403BILLING_IDENTITY_MISMATCHA billing NPI or TIN is not the office's
409PAYER_NOT_ENROLLEDA payer is not live for this office
409IDEMPOTENCY_IN_PROGRESSSame key still being written (Retry-After: 5)
409DUPLICATE_CONTENTIdentical X12 under another key within 24 hours
413PAYLOAD_TOO_LARGEX12 over 6 MB
422IDEMPOTENCY_KEY_REUSEDSame key, different X12, within 24 hours
422INVALID_REQUESTBody or parameters do not fit the schema
429TOO_MANY_REQUESTSOver 20 in flight or rate bucket empty
429QUOTA_EXCEEDEDSeller daily claim quota used up
InfoValidation failures (for example CLM01_DUPLICATE, CLAIM_CAP_EXCEEDED, PWK_NEA_MISMATCH, PWK_PARITY, STRUCTURAL_BLOCK) are not HTTP errors. They come back in the 202 body with state: "REJECTED_PRE_TRANSPORT". Warnings such as LICENSE_RECOMMENDED never block.
ImportantA replay re-runs the intake checks before returning the stored receipt, so it can answer with a refusal (for example 409 PAYER_NOT_ENROLLED) if something changed.
ImportantKeys and content hashes stay unique forever, but replays are only recognized for 24 hours. Reusing a key or sending identical content for the same office after 24 hours currently fails with an unhandled server error, so use a fresh key and a new CLM01 when you intentionally resend.
InfoThe published OpenAPI file marks Idempotency-Key optional and lists only 202 and 422. The gateway requires the header.
InfoThe dashboard's Drop a file screen sends dropped 837D files through a claim draft (create, build, submit) rather than calling this route directly; both reach the same intake.
API reference

Submit a dental claim as JSON

POSThttps://api.claimhouse.ai/v1/dental-claims/submission

Send one dental claim as JSON and let Claim House build the 837D.

Claim House resolves the payer, builds the 837D with its own builder, runs the identity gate, and passes the file through the same intake as raw X12. One claim per call.

Every object refuses unknown keys, strings are trimmed, amounts are strings with exactly two decimals, and dates are real calendar dates in YYYYMMDD. Nothing is defaulted except predeterminationOfBenefits.

The answer is 200 for ACCEPTED and HOLD, 400 for REJECTED, with the same body shape.

Permission
submit
Idempotency
required
Side effects
Stores the submission, claim and intake events, including held and rejected ones, and reads the payer registry. Never contacts the clearinghouse during the call.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Idempotency-Key*string1 to 255 characters. Same key and same body (compared as canonical JSON) within 24 hours replays with meta.idempotentReplay: true.
e.g. idem-EXAMPLE-0001
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Query parameters

NameTypeDescription
includestringComma-separated extras. x12 returns the built 837D as renderedX12. At most 40 characters.
e.g. x12

Request body

NameTypeDescription
usageIndicator*stringISA15. T needs a test key and P a production key.
one of: T · P
e.g. T
tradingPartnerServiceId*stringThe payer's primary id, internal id or alias in the payer registry. Pattern ^[A-Za-z0-9.\-_ ]{1,80}$.
e.g. PAYEREXAMPLE
tradingPartnerName*stringPayer name for Loop 2010BB NM103, used as sent. 1 to 60 characters.
e.g. Example Dental Plan
facilityId*stringThe office the claim bills as. Starts with fac_, 1 to 40 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
tenantClaimIdstringYour own reference. Stored as the submission's tenant_reference. At most 80 characters.
e.g. your-claim-EXAMPLE-3
submitterobjectAccepted and ignored. Claim House writes Loop 1000A.
receiverobjectAccepted and ignored. Claim House writes Loop 1000B.
subscriber*objectThe policyholder (Loop 2010BA).
memberId*stringThe subscriber's member id on the plan. 2 to 80 characters.
e.g. SYN000123456
firstName*stringFirst name. 1 to 35 characters.
e.g. Sample
lastName*stringLast name. 1 to 60 characters.
e.g. Subscriber
middleNamestringMiddle name. At most 25 characters.
dateOfBirth*stringDate of birth as YYYYMMDD. Must be a real calendar date.
e.g. 20260901
gender*stringGender code.
one of: M · F · U
groupNumberstringGroup number. At most 30 characters.
address*objectHome address.
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
paymentResponsibilityLevelCode*stringSBR01. Whether this plan pays first, second or third.
one of: P · S · T
ssnstringSocial Security number, 9 digits. Optional.
dependentobjectThe patient when the patient is not the subscriber (Loop 2010CA). Omit when the subscriber is the patient.
memberIdstringAccepted but never written. Loop 2010CA carries no id. 2 to 80 characters.
firstName*stringFirst name. 1 to 35 characters.
e.g. Sample
lastName*stringLast name. 1 to 60 characters.
e.g. Patient
middleNamestringMiddle name. At most 25 characters.
dateOfBirth*stringDate of birth as YYYYMMDD. Must be a real calendar date.
e.g. 20260901
gender*stringGender code.
one of: M · F · U
groupNumberstringGroup number. At most 30 characters.
address*objectHome address.
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
paymentResponsibilityLevelCode*stringSBR01 of the covering subscriber.
one of: P · S · T
ssnstringSocial Security number, 9 digits. Optional.
relationshipToSubscriberCode*stringPAT01, the patient's relationship to the subscriber. 18 (self) is never a dependent and nothing is defaulted.
one of: 01 · 19 · 20 · 21 · 39 · 40 · 53 · G8
billing*objectThe billing provider (Loop 2010AA). NPI and tax id must equal the facility's registered values. Send organizationName for an organization or lastName and firstName for a person, never both. Send exactly one of employerId or ssn.
npi*stringBilling NPI, 10 digits with a valid check digit.
e.g. 1234567893
organizationNamestringOrganization name. At most 60 characters. Conditional, see above.
e.g. Sample Dental Group
lastNamestringLast name for a person billing provider. At most 60 characters.
firstNamestringFirst name for a person billing provider. At most 35 characters.
taxonomyCodestringProvider taxonomy, pattern ^[0-9A-Z]{9}X$.
employerIdstringEmployer identification number, 9 digits. Conditional, see above.
e.g. 000000000
ssnstringTax SSN, 9 digits. Conditional, see above.
address*objectBilling provider street address.
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
contactInformationobjectPER contact.
namestringContact name. At most 60 characters.
phoneNumber*string10 digits, no punctuation. Required when contactInformation is sent.
emailstringEmail. At most 256 characters.
stateLicenseNumberstringREF*0B license number. At most 50 characters. Leaving it out produces the LICENSE_RECOMMENDED warning.
payToAddressobjectPay-to address (Loop 2010AB).
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
billingPayToAddressNamestringPay-to name. At most 60 characters.
renderingobjectThe rendering provider (Loop 2310B).
npi*stringRendering provider NPI, 10 digits with a valid check digit.
e.g. 1234567893
firstName*stringFirst name. 1 to 35 characters.
e.g. Sample
lastName*stringLast name. 1 to 60 characters.
e.g. Provider
middleNamestringMiddle name. At most 25 characters.
taxonomyCodestringProvider taxonomy, pattern ^[0-9A-Z]{9}X$.
stateLicenseNumberstringState license number. At most 50 characters.
payerAddressobjectPayer address (Loop 2010BB N3/N4).
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
billingPayToAddressNamestringPay-to name. At most 60 characters. Claim House moves it under billing.
claimInformation*objectThe claim (Loop 2300 and below).
patientControlNumberstringCLM01, pattern ^[A-Za-z0-9.\-]{1,17}$. When omitted Claim House allocates 12 random characters. Always transmitted with your facility prefix.
claimChargeAmount*stringCLM02 total charge. Amount as a string with exactly two decimals, for example 150.00.
e.g. 150.00
claimFrequencyCode*stringCLM05-3: 1 original, 7 replacement, 8 void.
one of: 1 · 7 · 8
e.g. 1
placeOfServiceCode*stringCLM05-1 place of service, 2 digits.
e.g. 11
signatureIndicator*stringCLM06 provider signature on file.
one of: Y · N
planParticipationCode*stringCLM07 assignment or plan participation.
one of: A · B · C
benefitsAssignmentCertificationIndicator*stringCLM08 benefits assignment.
one of: N · W · Y
releaseInformationCode*stringCLM09 release of information.
one of: Y · I
claimFilingCode*stringSBR09 claim filing indicator.
one of: 09 · 11 · 12 · 13 · 14 · 15 · 16 · 17 · AM · BL · CH · CI · DS · FI · HM · LM · MA · MB · MC · OF · TV · VA · WC · ZZ
e.g. CI
predeterminationOfBenefitsbooleanCLM19 PB. true makes this a predetermination, which must not carry service dates. Defaults to false.
patientAmountPaidstringAmount the patient already paid. Amount as a string with exactly two decimals, for example 150.00.
orthodonticTotalMonthsOfTreatmentintegerTotal months of orthodontic treatment, 0 to 99.
orthodonticTreatmentMonthsCountintegerDN102 months remaining, 0 to 99, not more than the total.
orthodonticTreatmentIndicatorbooleanDN104 orthodontic treatment indicator.
toothStatusarrayMissing, extracted or impacted teeth. At most 35.
toothNumber*stringTooth: 1 to 32, 51 to 82, A to T, or AS to TS.
toothStatusCode*stringStatus code.
one of: E · I · M
healthCareCodeInformationarrayDiagnosis codes. At most 4. The first must be ABK and only one ABK is allowed.
diagnosisTypeCode*stringABK principal or ABF other.
one of: ABK · ABF
diagnosisCode*stringICD-10-CM code without the decimal, 3 to 8 characters.
claimDateInformationobjectClaim-level dates.
serviceDatestringYYYYMMDD. Must equal the earliest line serviceDate. Not allowed on a predetermination.
appliancePlacementDatestringYYYYMMDD.
accidentDatestringYYYYMMDD. Required exactly when relatedCausesCode is sent.
claimNotesarrayNTE claim notes. At most 5.
noteReferenceCode*stringNote type.
one of: ADD · CER · DCP · DGN · TPO
description*stringNote text. 1 to 400 characters.
relatedCausesCodearrayArray of strings. At most 3 of AA, EM, OA. Requires accidentDate.
autoAccidentStateCodestringCLM11-4, 2 letters. Required with AA, not allowed without it.
autoAccidentCountryCodestringCLM11-5, 2 or 3 letters. Only with AA.
serviceFacilityLocationobjectService facility (Loop 2310C).
organizationName*stringFacility name. 1 to 60 characters. Required when the block is sent.
npistringFacility NPI with a valid check digit.
address*objectFacility address.
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
claimSupplementalInformationobjectReference numbers and attachment reports.
priorAuthorizationNumberstringPrior authorization number. At most 50 characters.
claimControlNumberstringThe payer's claim control number. At most 50 characters. Required for frequency 7 and 8, not allowed for 1.
predeterminationIdentifierstringREF*G3 predetermination id. At most 50 characters.
reportInformationobjectOne PWK attachment report.
attachmentReportTypeCode*stringPWK01 report type code (for example OZ, RB radiographs, DA dental models). NEA attachments use OZ.
e.g. OZ
attachmentTransmissionCode*stringPWK02 transmission code. AA carries no control number. NEA uses EL.
one of: AA · BM · EL · EM · FT · FX
e.g. EL
attachmentControlNumberstringThe NEA number you already hold. 1 to 50 characters. Not allowed together with attachmentId.
attachmentIdstringA pkt_ attachment packet id that Claim House resolves to its NEA number. 1 to 40 characters. Not allowed together with attachmentControlNumber.
reportInformationsarrayMore PWK attachment reports. At most 10.
attachmentReportTypeCode*stringPWK01 report type code (for example OZ, RB radiographs, DA dental models). NEA attachments use OZ.
e.g. OZ
attachmentTransmissionCode*stringPWK02 transmission code. AA carries no control number. NEA uses EL.
one of: AA · BM · EL · EM · FT · FX
e.g. EL
attachmentControlNumberstringThe NEA number you already hold. 1 to 50 characters. Not allowed together with attachmentId.
attachmentIdstringA pkt_ attachment packet id that Claim House resolves to its NEA number. 1 to 40 characters. Not allowed together with attachmentControlNumber.
otherSubscriberInformationarrayOther coverage (Loop 2320/2330). At most 10. Rendered, then the claim holds with COB_TRANSPORT_GATED.
paymentResponsibilityLevelCode*stringPayer order.
one of: P · S · T
individualRelationshipCode*stringSBR02 relationship.
one of: 01 · 18 · 19 · 20 · 21 · 39 · 40 · 53 · G8
groupNumberstringGroup number. At most 30 characters.
claimFilingIndicatorCode*stringClaim filing indicator, same list as claimFilingCode.
one of: 09 · 11 · 12 · 13 · 14 · 15 · 16 · 17 · AM · BL · CH · CI · DS · FI · HM · LM · MA · MB · MC · OF · TV · VA · WC · ZZ
benefitsAssignmentCertificationIndicator*stringBenefits assignment.
one of: N · W · Y
releaseInformationCode*stringRelease of information.
one of: Y · I
payerPaidAmountstringAmount the other payer paid. Amount as a string with exactly two decimals, for example 150.00.
otherSubscriberName*objectThe other policyholder.
firstName*string1 to 35 characters.
lastName*string1 to 60 characters.
memberId*string2 to 80 characters.
addressobjectAddress.
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
otherPayerName*objectThe other payer.
payerId*string2 to 80 characters.
organizationName*string1 to 60 characters.
addressobjectAddress.
address1*stringStreet line. 1 to 55 characters.
e.g. 100 Sample Street
address2stringSecond street line. At most 55 characters. Null when absent.
city*stringCity. 2 to 30 characters.
e.g. Sampletown
state*stringTwo-letter state code, uppercased.
e.g. OH
postalCode*stringZIP code, 5 or 9 digits.
e.g. 44000
serviceLines*arrayService lines, 1 to 1,000. Claims over 50 lines are split into parts with suffixed control numbers (CLAIM_SPLIT finding).
serviceDatestringDTP*472 YYYYMMDD. Required on every line of a claim, not allowed on a predetermination.
e.g. 20260901
providerControlNumberstringREF*6R line control number, pattern ^[A-Za-z0-9.\-]{1,30}$. Filled by Claim House when absent.
renderingProviderobjectLine rendering provider. Folded when it equals the claim's rendering, otherwise the claim holds with LINE_RENDERING_UNSUPPORTED.
npi*stringRendering provider NPI, 10 digits with a valid check digit.
e.g. 1234567893
firstName*stringFirst name. 1 to 35 characters.
e.g. Sample
lastName*stringLast name. 1 to 60 characters.
e.g. Provider
middleNamestringMiddle name. At most 25 characters.
taxonomyCodestringProvider taxonomy, pattern ^[0-9A-Z]{9}X$.
stateLicenseNumberstringState license number. At most 50 characters.
dentalService*objectThe procedure (SV3).
procedureCode*stringSV301 CDT code, D plus 4 digits.
e.g. D1110
lineItemChargeAmount*stringSV302 line charge. Amount as a string with exactly two decimals, for example 150.00.
e.g. 150.00
placeOfServiceCodestringLine place of service, 2 digits.
procedureCountintegerUnits, 0 to 99.
oralCavityDesignationarrayArray of strings. At most 5 of 00 01 02 10 20 30 40.
prosthesisCrownOrInlayCodestringSV305 initial or replacement.
one of: I · R
procedureModifierarrayArray of strings. At most 4 modifiers.
descriptionstringProcedure description. At most 80 characters.
compositeDiagnosisCodePointersobjectDiagnosis pointers. A bare integer array is also accepted here.
diagnosisCodePointersarrayArray of integers. At most 4.
teethInformationarrayTeeth and surfaces. At most 32.
toothCode*stringTooth: 1 to 32, 51 to 82, A to T, or AS to TS.
e.g. 3
toothSurfaceCodesarrayArray of strings. At most 7 of B D F I L M O. No O on anterior teeth, no I on posterior teeth.
serviceLineDateInformationobjectLine dates.
priorPlacementDatestringDTP*441 YYYYMMDD.
appliancePlacementDatestringDTP*452 YYYYMMDD.
treatmentStartDatestringYYYYMMDD.
treatmentCompletionDatestringYYYYMMDD.
serviceLineSupplementalInformationarrayLine attachment reports (array or one object). At most 10. Folded to the claim level with a LINE_ATTACHMENT_FOLDED finding.
attachmentReportTypeCode*stringPWK01 report type code (for example OZ, RB radiographs, DA dental models). NEA attachments use OZ.
e.g. OZ
attachmentTransmissionCode*stringPWK02 transmission code. AA carries no control number. NEA uses EL.
one of: AA · BM · EL · EM · FT · FX
e.g. EL
attachmentControlNumberstringThe NEA number you already hold. 1 to 50 characters. Not allowed together with attachmentId.
attachmentIdstringA pkt_ attachment packet id that Claim House resolves to its NEA number. 1 to 40 characters. Not allowed together with attachmentControlNumber.
lineAdjudicationInformationarrayOther payer line adjudication (Loop 2430). At most 15. The claim holds with LINE_ADJUDICATION_UNSUPPORTED.
otherPayerPrimaryIdentifier*string2 to 80 characters.
serviceLinePaidAmount*stringAmount as a string with exactly two decimals, for example 150.00.
procedureCode*stringD plus 4 digits.
paidServiceUnitCountinteger0 to 99.

Request example

{
  "usageIndicator": "T",
  "tradingPartnerServiceId": "TESTPAYER1",
  "tradingPartnerName": "Example Dental Plan",
  "facilityId": "fac_tycrfy0cs0qc7sq95eme",
  "tenantClaimId": "your-claim-EXAMPLE-3",
  "subscriber": {
    "memberId": "SYN000123456",
    "firstName": "Sample",
    "lastName": "Subscriber",
    "dateOfBirth": "20260901",
    "gender": "U",
    "paymentResponsibilityLevelCode": "P",
    "address": {
      "address1": "100 Sample Street",
      "city": "Sampletown",
      "state": "OH",
      "postalCode": "44000"
    }
  },
  "dependent": {
    "firstName": "Sample",
    "lastName": "Patient",
    "dateOfBirth": "20260902",
    "gender": "U",
    "paymentResponsibilityLevelCode": "P",
    "relationshipToSubscriberCode": "19",
    "address": {
      "address1": "100 Sample Street",
      "city": "Sampletown",
      "state": "OH",
      "postalCode": "44000"
    }
  },
  "billing": {
    "npi": "9999999995",
    "organizationName": "Sample Dental Group",
    "employerId": "990000001",
    "address": {
      "address1": "200 Sample Avenue",
      "city": "Sampletown",
      "state": "OH",
      "postalCode": "44000"
    }
  },
  "rendering": {
    "npi": "9999999995",
    "firstName": "Sample",
    "lastName": "Provider"
  },
  "claimInformation": {
    "claimChargeAmount": "150.00",
    "claimFrequencyCode": "1",
    "placeOfServiceCode": "11",
    "signatureIndicator": "Y",
    "planParticipationCode": "A",
    "benefitsAssignmentCertificationIndicator": "Y",
    "releaseInformationCode": "Y",
    "claimFilingCode": "CI",
    "claimSupplementalInformation": {
      "reportInformation": {
        "attachmentReportTypeCode": "OZ",
        "attachmentTransmissionCode": "EL",
        "attachmentId": "pkt_2g7hcpgd99p0p0hnhnaa"
      }
    },
    "serviceLines": [
      {
        "serviceDate": "20260901",
        "dentalService": {
          "procedureCode": "D1110",
          "lineItemChargeAmount": "150.00"
        },
        "teethInformation": [
          {
            "toothCode": "3"
          }
        ]
      }
    ]
  }
}

Response fields

NameTypeDescription
status*stringACCEPTED (queued for transport), HOLD (held for operator review) or REJECTED (stopped by Claim House edits).
one of: ACCEPTED · HOLD · REJECTED
controlNumberstringST02 of the built transaction.
tradingPartnerServiceIdstringYour value as sent.
claimReference*objectWhere to find the claim.
correlationIdstringThe sub_ submission id. Null on a PAYER_NOT_FOUND refusal.
patientControlNumberstringCLM01 as transmitted, including an allocated one, with your facility prefix.
payerIdstringRegistry primary payer id when the payer resolves, otherwise the id from the file.
formatVersion*stringAlways 5010.
timeOfResponse*datetimeWhen Claim House answered.
serviceLines*arrayLine control numbers.
lineItemControlNumber*stringREF*6R line control number.
claimIdstringThe clm_ claim id.
facilityId*stringThe resolved office.
payer*objectThe resolved payer.
payerIdstringSame rule as claimReference.payerId.
payerNamestringRegistry display name. Null when the payer did not resolve.
x12stringThe Claim House 999 for this intake decision. Never transported.
renderedX12stringThe 837D as built, only with include=x12. Intake rewrites CLM01, REF*6R and REF*D9 before storing.
errors*arrayIntake findings plus builder findings (LINE_ATTACHMENT_FOLDED, TEXT_SANITIZED, CLAIM_SPLIT, PCN_TOKENIZED and others). Empty when there are none.
code*stringFinding or hold code.
description*stringMessage. Never contains a value from your claim.
followupActionstringWhat to do: correct and resubmit with a new key for errors, do not resubmit for a hold, no action for warnings.
locationstringWhere the finding applies, with segment id and position when known.
valuestringAlways null.
meta*objectRequest metadata.
traceId*stringThe request id, also in X-Request-Id.
applicationMode*stringMode of the key.
one of: TEST · PRODUCTION
facilityId*stringThe resolved office.
idempotentReplay*booleantrue when this is the stored answer to an earlier request.
httpStatusCode*integer200 or 400, matching the HTTP status.

Responses

200Accepted
{
  "status": "ACCEPTED",
  "controlNumber": "0001",
  "tradingPartnerServiceId": "TESTPAYER1",
  "claimReference": {
    "correlationId": "sub_mxd66v1vhtc8y4cvpdvw",
    "patientControlNumber": "SBX-XFDJE4W4KAJE",
    "payerId": "TESTPAYER1",
    "formatVersion": "5010",
    "timeOfResponse": "2026-09-05T14:30:00+00:00",
    "serviceLines": [
      {
        "lineItemControlNumber": "CLMHBFHA45NSX6DZBNNHP6Z"
      }
    ],
    "claimId": "clm_7a038z37x36x0tr02rn0",
    "facilityId": "fac_tycrfy0cs0qc7sq95eme"
  },
  "payer": {
    "payerId": "TESTPAYER1",
    "payerName": "Test Dental Plan"
  },
  "x12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*FA*TESTSENDER*262403888*20260905*1430*1*X*005010X231A1~ST*999*0001*005010X231A1~AK1*HC*1*005010X224A2~AK2*837*0001*005010X224A2~IK5*A~AK9*A*1*1*1~SE*6*0001~GE*1*1~IEA*1*000000001~",
  "renderedX12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*HC*TESTSENDER*262403888*20260905*1430*1*X*005010X224A2~ST*837*0001*005010X224A2~BHT*0019*00*SBX-XFDJE4W4KAJE*20260905*1430*CH~NM1*41*2*CLAIM HOUSE*****46*TESTSENDER~PER*IC*GATEWAY DESK*TE*5555550100~NM1*40*2*TESIA*****46*262403888~HL*1**20*1~NM1*85*2*SAMPLE DENTAL GROUP*****XX*9999999995~N3*200 SAMPLE AVENUE~N4*SAMPLETOWN*OH*44000~REF*EI*990000001~HL*2*1*22*1~SBR*P********CI~NM1*IL*1*SUBSCRIBER*SAMPLE****MI*SYN000123456~NM1*PR*2*EXAMPLE DENTAL PLAN*****PI*TESTPAYER1~HL*3*2*23*0~PAT*19~NM1*QC*1*PATIENT*SAMPLE~N3*100 SAMPLE STREET~N4*SAMPLETOWN*OH*44000~DMG*D8*20260902*U~CLM*SBX-XFDJE4W4KAJE*150***11:B:1*Y*A*Y*Y~DTP*472*D8*20260901~PWK*OZ*EL***AC*NEA1234567~NTE*ADD*NEA#1234567~NM1*82*1*PROVIDER*SAMPLE****XX*9999999995~LX*1~SV3*AD:D1110*150~TOO*JP*3~DTP*472*D8*20260901~SE*30*0001~GE*1*1~IEA*1*000000001~",
  "errors": [
    {
      "code": "LICENSE_RECOMMENDED",
      "description": "Loop 2010AA REF*0B billing provider license is recommended; some payers ask Tesia to forward it when available",
      "followupAction": "No action required.",
      "location": "ST[0]/CLM[0]/REF",
      "value": null
    }
  ],
  "meta": {
    "traceId": "evt_ps2y0z3fp59rk1p9xt56",
    "applicationMode": "TEST",
    "facilityId": "fac_tycrfy0cs0qc7sq95eme",
    "idempotentReplay": false
  },
  "httpStatusCode": 200
}
400Payer not found
{
  "status": "REJECTED",
  "controlNumber": null,
  "tradingPartnerServiceId": "UNKNOWNPAYEREXAMPLE",
  "claimReference": {
    "correlationId": null,
    "patientControlNumber": null,
    "payerId": null,
    "formatVersion": "5010",
    "timeOfResponse": "2026-09-14T18:00:00+00:00",
    "serviceLines": [],
    "claimId": null,
    "facilityId": "fac_tycrfy0cs0qc7sq95eme"
  },
  "payer": {
    "payerId": null,
    "payerName": null
  },
  "x12": null,
  "renderedX12": null,
  "errors": [
    {
      "code": "PAYER_NOT_FOUND",
      "description": "the trading partner service id is not in the payer registry",
      "followupAction": "Correct the claim and resubmit with a new Idempotency-Key.",
      "location": "tradingPartnerServiceId",
      "value": null
    }
  ],
  "meta": {
    "traceId": "evt_jvagd3ctrgrwn3hxz8ww",
    "applicationMode": "TEST",
    "facilityId": "fac_tycrfy0cs0qc7sq95eme",
    "idempotentReplay": false
  },
  "httpStatusCode": 400
}
422Invalid request
{
  "error": "INVALID_REQUEST",
  "message": "the request body is not valid",
  "errors": [
    {
      "code": "missing",
      "location": "body.claimInformation.serviceLines",
      "message": "Field required",
      "value_redacted": true
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
400PAYER_NOT_FOUNDPayer id not in the registry (JSON claim body)
400IDEMPOTENCY_KEY_REQUIREDHeader missing or blank
400IDEMPOTENCY_KEY_INVALIDHeader longer than 255 characters
400CLAIM_BUILD_FAILEDThe claim model or builder refused the claim
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route permission
403FACILITY_NOT_GRANTEDOffice unknown or outside your grants
403MODE_MISMATCHusageIndicator or office binding does not match the key
403BILLING_IDENTITY_MISMATCHBuilt billing NPI or TIN is not the office's
409PAYER_NOT_ENROLLEDPayer not live for this office
409IDEMPOTENCY_IN_PROGRESSSame key still being written (Retry-After: 5)
409DUPLICATE_CONTENTIdentical body under another key within 24 hours
413PAYLOAD_TOO_LARGEBuilt X12 over 6 MB
422ATTACHMENT_REFERENCE_AMBIGUOUSMore than one distinct attachmentId
422IDEMPOTENCY_KEY_REUSEDSame key, different body
422INVALID_REQUESTBody or parameters do not fit the schema
429TOO_MANY_REQUESTSOver 20 in flight or rate bucket empty
429QUOTA_EXCEEDEDSeller daily claim quota used up
502CLAIM_BUILD_INVALIDBuilder output cannot be parsed or transported
503CLAIM_BUILDER_UNAVAILABLEBuilder not configured on this gateway
ImportantAvailability depends on deployment: the builder must be configured on the gateway, otherwise the route answers 503 CLAIM_BUILDER_UNAVAILABLE and points you to the raw X12 route. The published OpenAPI file still labels this route planned and marks the header optional.
InfoCross-field rules return 422 INVALID_REQUEST: frequency 7 and 8 need claimControlNumber and 1 must not carry one; line service dates are required on claims and forbidden on predeterminations; claimDateInformation.serviceDate equals the earliest line date; accidentDate and relatedCausesCode come together; AA needs autoAccidentStateCode; the first diagnosis is ABK; billing has exactly one tax id and one entity type; a report carries a control number or a packet id, not both.
InfoCLAIM_BUILD_FAILED detail uses the claim error shape (code, description, followupAction, location, value: null) with codes such as FACILITY_MISMATCH, USAGE_INDICATOR_MISMATCH, PAYER_UNRESOLVED, PROSTHESIS_CODE_REQUIRED, PAYER_CLAIM_NUMBER_REQUIRED, NPI_CHECK_DIGIT.
ImportantAny otherSubscriberInformation holds the claim with COB_TRANSPORT_GATED. In the hosted API an attachmentId cannot currently resolve, so it holds; declare NEA numbers with attachmentControlNumber instead.
InfoA PAYER_NOT_FOUND refusal stores nothing, so after fixing the payer you may reuse the same Idempotency-Key.
API reference

Submit raw X12 with a compat response

POSThttps://api.claimhouse.ai/v1/dental-claims/raw-x12-submission

Send one finished 837D and receive the ClaimSubmissionResponse envelope.

Runs the same intake as POST /v1/submissions but answers in the ClaimSubmissionResponse response envelope used by common clearinghouse client libraries. Use it when your client already parses that shape; otherwise prefer POST /v1/submissions, which also accepts attachment declarations.

The office is facilityId, or the office in your grants whose billing NPI and TIN match the first claim in the file. The mode is always the key's mode. The answer is 200 for ACCEPTED and HOLD, 400 for REJECTED.

Permission
submit
Idempotency
required
Side effects
Stores the submission, claims and intake events, including held and rejected ones. Never contacts the clearinghouse during the call; later transport is once per file and never retried.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Idempotency-Key*string1 to 255 characters. Same key and same X12 within 24 hours replays with meta.idempotentReplay: true.
e.g. idem-EXAMPLE-0001
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Query parameters

NameTypeDescription
includestringComma-separated extras. x12 returns the text as renderedX12. At most 40 characters.
e.g. x12

Request body

NameTypeDescription
x12*stringThe 837D text, version 005010X224A2. At most 6 MB.
e.g. ISA*00*...~
facilityIdstringThe office the file bills as. 1 to 64 characters. When omitted, resolved from the first claim's billing NPI and TIN inside your grants.
e.g. fac_tycrfy0cs0qc7sq95eme
tenantClaimIdstringYour reference, stored as the submission's tenant_reference. At most 200 characters.
e.g. your-claim-EXAMPLE-1

Request example

{
  "x12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*HC*TESTSENDER*262403888*20260905*1430*1*X*005010X224A2~ST*837*0001*005010X224A2~BHT*0019*00*TESTBATCH0001*20260905*1430*CH~NM1*41*2*TEST SUBMITTER*****46*TESTSENDER~PER*IC*TEST CONTACT*TE*5555550100~NM1*40*2*TEST RECEIVER*****46*262403888~HL*1**20*1~PRV*BI*PXC*1223G0001X~NM1*85*2*TEST BILLING OFFICE*****XX*9999999995~N3*1 TEST STREET~N4*TESTCITY*OH*44000~REF*EI*990000001~REF*0B*TESTLIC0001~HL*2*1*22*0~SBR*P*18*******CI~NM1*IL*1*TEST*PATIENT****MI*TESTMEMBER01~N3*2 TEST LANE~N4*TESTCITY*OH*44000~DMG*D8*19800101*F~NM1*PR*2*TEST PAYER*****PI*TESTPAYER1~CLM*RAW0008*150.00***11:B:1*Y*A*Y*Y~DTP*472*D8*20260901~HI*BK:K089~NM1*82*1*TEST*PROVIDER****XX*9999999995~LX*1~SV3*AD:D0120*150***1~DTP*472*D8*20260901~SE*27*0001~GE*1*1~IEA*1*000000001~",
  "facilityId": "fac_tycrfy0cs0qc7sq95eme",
  "tenantClaimId": "your-claim-0001"
}

Response fields

NameTypeDescription
status*stringACCEPTED (queued for transport), HOLD (held for operator review) or REJECTED (stopped by Claim House edits).
one of: ACCEPTED · HOLD · REJECTED
controlNumberstringST02 of the first transaction in your file. Null when absent.
tradingPartnerServiceIdstringThe first claim's Loop 2010BB payer id.
claimReference*objectWhere to find the claim. Only the first claim in the file is described.
correlationIdstringThe sub_ submission id. Null on a PAYER_NOT_FOUND refusal.
patientControlNumberstringCLM01 as transmitted, including an allocated one, with your facility prefix.
payerIdstringRegistry primary payer id when the payer resolves, otherwise the id from the file.
formatVersion*stringAlways 5010.
timeOfResponse*datetimeWhen Claim House answered.
serviceLines*arrayLine control numbers.
lineItemControlNumber*stringREF*6R line control number.
claimIdstringThe clm_ claim id.
facilityId*stringThe resolved office.
payer*objectThe resolved payer.
payerIdstringSame rule as claimReference.payerId.
payerNamestringRegistry display name. Null when the payer did not resolve.
x12stringThe Claim House 999 for this intake decision. Never transported.
renderedX12stringYour submitted text, only with include=x12.
errors*arrayIntake findings. Empty when there are none.
code*stringFinding or hold code.
description*stringMessage. Never contains a value from your claim.
followupActionstringWhat to do: correct and resubmit with a new key for errors, do not resubmit for a hold, no action for warnings.
locationstringWhere the finding applies, with segment id and position when known.
valuestringAlways null.
meta*objectRequest metadata.
traceId*stringThe request id, also in X-Request-Id.
applicationMode*stringMode of the key.
one of: TEST · PRODUCTION
facilityId*stringThe resolved office.
idempotentReplay*booleantrue when this is the stored answer to an earlier request.
httpStatusCode*integer200 or 400, matching the HTTP status.

Responses

200Accepted
{
  "status": "ACCEPTED",
  "controlNumber": "0001",
  "tradingPartnerServiceId": "TESTPAYER1",
  "claimReference": {
    "correlationId": "sub_4j31mvem4v4rz0m57nzj",
    "patientControlNumber": "SBX-RAW0008",
    "payerId": "TESTPAYER1",
    "formatVersion": "5010",
    "timeOfResponse": "2026-09-05T14:30:00+00:00",
    "serviceLines": [
      {
        "lineItemControlNumber": "CLM49T02GKTJ97QV7V9DFAX"
      }
    ],
    "claimId": "clm_0yx6eztesmt3yqkam64d",
    "facilityId": "fac_tycrfy0cs0qc7sq95eme"
  },
  "payer": {
    "payerId": "TESTPAYER1",
    "payerName": "Test Dental Plan"
  },
  "x12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*FA*TESTSENDER*262403888*20260905*1430*1*X*005010X231A1~ST*999*0001*005010X231A1~AK1*HC*1*005010X224A2~AK2*837*0001*005010X224A2~IK5*A~AK9*A*1*1*1~SE*6*0001~GE*1*1~IEA*1*000000001~",
  "renderedX12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*HC*TESTSENDER*262403888*20260905*1430*1*X*005010X224A2~ST*837*0001*005010X224A2~BHT*0019*00*TESTBATCH0001*20260905*1430*CH~NM1*41*2*TEST SUBMITTER*****46*TESTSENDER~PER*IC*TEST CONTACT*TE*5555550100~NM1*40*2*TEST RECEIVER*****46*262403888~HL*1**20*1~PRV*BI*PXC*1223G0001X~NM1*85*2*TEST BILLING OFFICE*****XX*9999999995~N3*1 TEST STREET~N4*TESTCITY*OH*44000~REF*EI*990000001~REF*0B*TESTLIC0001~HL*2*1*22*0~SBR*P*18*******CI~NM1*IL*1*TEST*PATIENT****MI*TESTMEMBER01~N3*2 TEST LANE~N4*TESTCITY*OH*44000~DMG*D8*19800101*F~NM1*PR*2*TEST PAYER*****PI*TESTPAYER1~CLM*RAW0008*150.00***11:B:1*Y*A*Y*Y~DTP*472*D8*20260901~HI*BK:K089~NM1*82*1*TEST*PROVIDER****XX*9999999995~LX*1~SV3*AD:D0120*150***1~DTP*472*D8*20260901~SE*27*0001~GE*1*1~IEA*1*000000001~",
  "errors": [],
  "meta": {
    "traceId": "evt_bz4daevk64jyafjntdxz",
    "applicationMode": "TEST",
    "facilityId": "fac_tycrfy0cs0qc7sq95eme",
    "idempotentReplay": false
  },
  "httpStatusCode": 200
}
400Parse error
{
  "error": "X12_PARSE_ERROR",
  "message": "the X12 did not parse as an 837D",
  "errors": [
    {
      "reason": "missing ISA segment"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
403Facility not granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "facility not granted",
  "errors": [
    {
      "facility_id": "fac_qnwrs4rq9tjtqwv37phe"
    }
  ],
  "request_id": "evt_e7dzmn5q5adw2cgkddnf"
}

Errors

StatusCodeWhen
400IDEMPOTENCY_KEY_REQUIREDHeader missing or blank
400IDEMPOTENCY_KEY_INVALIDHeader longer than 255 characters
400X12_PARSE_ERRORText does not parse as 837D
400NO_CLAIMSNo facilityId and the file carries no claim
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route permission
403FACILITY_NOT_GRANTEDNo granted office matches the file, or named office not granted
403MODE_MISMATCHOffice binding does not accept the key mode
403BILLING_IDENTITY_MISMATCHA billing NPI or TIN is not the office's
409PAYER_NOT_ENROLLEDPayer not live for this office
409IDEMPOTENCY_IN_PROGRESSSame key still being written (Retry-After: 5)
409DUPLICATE_CONTENTIdentical X12 under another key within 24 hours
413PAYLOAD_TOO_LARGEX12 over 6 MB
422IDEMPOTENCY_KEY_REUSEDSame key, different X12
422INVALID_REQUESTBody or parameters do not fit the schema
429TOO_MANY_REQUESTSOver 20 in flight or rate bucket empty
429QUOTA_EXCEEDEDSeller daily claim quota used up
InfoRefusals before intake use the standard error envelope, not the compat envelope.
InfoOnly the first claim is described in claimReference. For a multi-claim file, call GET /v1/submissions/{submission_id} with correlationId to see every claim.
ImportantThis route has no attachments or attachment_packet_id field, so declared NEA parity and packet parity cannot be requested here. File-internal checks (PWK_NEA_MISMATCH, PWK06_TOO_LONG) still run.
InfoThe published OpenAPI file labels this route a planned alias and marks the header optional. The route is implemented and requires the header.
API reference

Get a submission

GEThttps://api.claimhouse.ai/v1/submissions/{submission_id}

Read one submission's state, dispatch window, transport file record and claim states.

Use it to confirm a submission left in a dispatch window and to read the transported file name and interchange control number.

This read does not return intake findings, hold_reason, expected_transport_by or the 999. The only way to read those again is to replay the original request with the same key and body within 24 hours.

Permission
read
Idempotency
none
Side effects
Writes one read audit row. No clearinghouse or payer call.
In the dashboard
Claims > Claim detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
submission_id*stringThe submission id, sub_.... 1 to 64 characters.
e.g. sub_rw97ns2b72wkzfk6hwpy

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id, also in X-Request-Id.
submission_id*stringThe submission id.
facility_id*stringThe office the submission bills as.
mode*stringMode of the submission.
one of: test · production
state*stringCurrent submission state.
one of: QUEUED · HOLD · REJECTED_PRE_TRANSPORT · TRANSPORTED · TRANSPORT_AMBIGUOUS · CLOSED · RECEIVED
claim_count*integerNumber of claims in the submission.
tenant_referencestringYour tenant_reference or JSON tenantClaimId. Null when not sent.
received_at*datetimeWhen intake received the submission.
updated_at*datetimeLast change to the submission.
dispatch*objectDispatch window.
window_idstringwin_... once a dispatch window picked the submission up. Null before then.
transportobjectThe transport file record. Null until a file is recorded.
file_id*stringFile id, file_....
remote_filenamestringThe name the file was put under.
isa13integerInterchange control number allocated by the window.
gs06integerGroup control number.
statestringFile state.
transported_atdatetimeVerified transport time.
session_idstringTransport session id, ses_....
sha256stringHash of the exact bytes archived and put.
size_bytesintegerFile size in bytes.
claims*arrayClaims in the submission, ordered by claim id.
claim_id*stringThe claim id.
tenant_claim_idstringYour original CLM01.
pcn*stringCLM01 as transmitted.
payer_id*stringPayer id on the claim.
state*stringCurrent claim state.

Responses

200OK
{
  "request_id": "evt_6ngeg01z09sbp4h1mzmj",
  "submission_id": "sub_rw97ns2b72wkzfk6hwpy",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "mode": "test",
  "state": "TRANSPORTED",
  "claim_count": 1,
  "tenant_reference": "batch-sample-001",
  "received_at": "2026-09-05T14:30:00+00:00",
  "updated_at": "2026-09-05T14:30:00+00:00",
  "dispatch": {
    "window_id": null
  },
  "transport": {
    "file_id": "file_xfdnsddqpp31wpm0ykj0",
    "remote_filename": null,
    "isa13": null,
    "gs06": null,
    "state": null,
    "transported_at": null,
    "session_id": null,
    "sha256": null,
    "size_bytes": null
  },
  "claims": [
    {
      "claim_id": "clm_fr0nht13zzkbejwav55f",
      "tenant_claim_id": "SYN0010",
      "pcn": "SBX-SYN0010",
      "payer_id": "TESTPAYER1",
      "state": "TRANSPORTED"
    }
  ]
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such submission",
  "errors": [],
  "request_id": "evt_r86fe18bm08e0mwh4amk"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route permission
403FACILITY_NOT_GRANTEDGroup or office key with no granted office
404NOT_FOUNDNo such submission in your seller and grants
422INVALID_REQUESTPath id longer than 64 characters
429TOO_MANY_REQUESTSOver 20 in flight or rate bucket empty
InfoA submission that exists but is outside your seller or grants returns 404, never 403.
API reference

List claims

GEThttps://api.claimhouse.ai/v1/claims

Return one page of your claims, most recently updated first, with optional filters.

Use it to build a claims table or to find a claim by your own claim id or by its claim reference.

A seller-wide key sees every office of the seller; a facility or group key sees only claims for granted offices. Claims are not filtered by key mode.

Permission
read
Idempotency
none
Side effects
Writes one read audit row listing the returned claim ids. No clearinghouse or payer call.
In the dashboard
Claims > All claims

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
facility_idstringOnly claims for this office. At most 64 characters. A facility or group key gets 403 FACILITY_NOT_GRANTED outside its grants; a seller-wide key simply gets no rows for an office that is not yours.
e.g. fac_tycrfy0cs0qc7sq95eme
statestringExact match on one state, for example PAID. Not validated: an unknown value returns an empty page. At most 64 characters.
e.g. PAID
tenant_claim_idstringExact match against your claim id or the claim_reference. No partial or case-insensitive matching, and it does not match pcn or claim_id. At most 64 characters.
sincedatetimeOnly claims with updated_at at or after this moment. A value without a timezone is treated as UTC.
e.g. 2026-09-01T00:00:00Z
cursorstringThe next_cursor from the previous page. At most 512 characters.
limitintegerPage size, 1 to 500. Defaults to 100.
e.g. 100

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id, also in X-Request-Id.
claims*arrayClaim rows.
claim_id*stringClaim House claim id, clm_....
claim_referencestringHuman reference such as MAIN1-20260912-0007, assigned at first confirmed transport when the office has naming configured. Null before then. Never changes once set.
first_submitted_atdatetimeVerified transport time of the file that first carried the claim. Null before transport.
reference_datedateOffice-local date used in claim_reference. Null until a reference is assigned.
reference_timezonestringIANA timezone used for reference_date. Null until a reference is assigned.
submission_id*stringThe submission that created the claim, sub_....
facility_id*stringOffice id, fac_....
pcn*stringCLM01 as transmitted: your office prefix, a hyphen, and your value or a 12-character token.
tenant_claim_idstringYour original claim id as you sent it.
payer_id*stringPayer id on the claim.
state*stringCurrent claim state. See How a claim moves.
payer_claim_control_numberstringThe payer's claim number, once a 277 carried it.
service_date_fromdateFirst service date.
charge_amount*numberTotal charge as a JSON number (for example 150.0).
updated_at*datetimeLast time any event touched the claim.
next_cursorstringOpaque cursor for the next page. Null on the last page.
has_more*booleantrue exactly when next_cursor is not null.

Responses

200OK
{
  "request_id": "evt_gpsac72b6y81779xjvgg",
  "claims": [
    {
      "claim_id": "clm_84w2n1qj9ytvtb11br0w",
      "claim_reference": null,
      "first_submitted_at": null,
      "reference_date": null,
      "reference_timezone": null,
      "submission_id": "sub_gytnwygh2416ewf7qv60",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "pcn": "SBX-SYN0011",
      "tenant_claim_id": "SYN0011",
      "payer_id": "TESTPAYER1",
      "state": "PAID",
      "payer_claim_control_number": null,
      "service_date_from": null,
      "charge_amount": 100,
      "updated_at": "2026-09-05T14:30:00+00:00"
    }
  ],
  "next_cursor": null,
  "has_more": false
}
400Invalid cursor
{
  "error": "INVALID_CURSOR",
  "message": "cursor is not readable",
  "errors": [],
  "request_id": "evt_zz6nhyefazb7hmcnqgk2"
}
403Facility not granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "this key's grants do not cover that facility",
  "errors": [
    {
      "facility_id": "fac_qnwrs4rq9tjtqwv37phe"
    }
  ],
  "request_id": "evt_n5g3707m7517ksztz77s"
}

Errors

StatusCodeWhen
400INVALID_CURSORCursor unreadable or not issued by the gateway
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route permission
403FACILITY_NOT_GRANTEDNo office grant, or facility_id outside grants
422INVALID_REQUESTA query parameter failed validation
429TOO_MANY_REQUESTSOver 20 in flight or rate bucket empty
InfoOrder is updated_at descending, then claim_id descending. The cursor encodes the last row's position, does not expire, and is not tied to the filters, so keep filters identical while paging.
Importantupdated_at changes whenever any event is appended, so a claim that changes during a forward walk jumps to the top and can be skipped. For reliable sync use the event feed, or re-walk with since set to when your previous walk started.
InfoThe row does not include service_date_to, last_event_at, d9 or the ladder, and there is no total count. Use Get a claim for those. For a 422 on a query parameter the message still reads "the request body is not valid".
API reference

Get a claim

GEThttps://api.claimhouse.ai/v1/claims/{claim_id}

Return one claim with its full event ladder and, once an 835 arrives, its payment summary.

Use it for a claim detail page or to reconcile one claim. The response carries every field of a list row plus d9, service_date_to, last_event_at, the ladder and the remittance block.

Events are the source of truth: the ladder lists every event that touched the claim, oldest first, even events that did not change the state (summary.applied: false).

Permission
read
Idempotency
none
Side effects
Writes one read audit row. No clearinghouse or payer call.
In the dashboard
Claims > Claim detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
claim_id*stringThe Claim House claim id, clm_.... 1 to 64 characters. A claim reference or your own claim id is not accepted; look those up with List claims.
e.g. clm_809svf25qxpwhn9kwp6m

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id, also in X-Request-Id.
claim_id*stringClaim House claim id, clm_....
claim_referencestringHuman reference such as MAIN1-20260912-0007, assigned at first confirmed transport when the office has naming configured. Null before then. Never changes once set.
first_submitted_atdatetimeVerified transport time of the file that first carried the claim. Null before transport.
reference_datedateOffice-local date used in claim_reference. Null until a reference is assigned.
reference_timezonestringIANA timezone used for reference_date. Null until a reference is assigned.
submission_id*stringThe submission that created the claim, sub_....
facility_id*stringOffice id, fac_....
pcn*stringCLM01 as transmitted: your office prefix, a hyphen, and your value or a 12-character token.
tenant_claim_idstringYour original claim id as you sent it.
payer_id*stringPayer id on the claim.
state*stringCurrent claim state. See How a claim moves.
payer_claim_control_numberstringThe payer's claim number, once a 277 carried it.
service_date_fromdateFirst service date.
charge_amount*numberTotal charge as a JSON number (for example 150.0).
updated_at*datetimeLast time any event touched the claim.
d9*stringThe REF*D9 value Claim House stamped into the 837D for this claim.
service_date_todateLast service date.
last_event_atdatetimeWhen the most recent event occurred.
ladder*arrayEvery event on the claim, oldest first. Capped at 500.
event_id*stringEvent id, evt_....
sequence*integerGlobal event sequence.
kind*stringEvent type, for example claim.ack_997.
artifact_idstringDownloadable file behind this event, if any.
occurred_at*datetimeWhen the event happened.
summary*objectThe event payload: codes, ids and amounts, never patient names. Shape depends on the event type.
remittanceobjectPayment summary. Null until at least one 835 payment record exists for the claim.
latest_event_kindstringEvent kind of the most recent payment record: claim.paid, claim.denied, claim.reversed, or null for an unmapped CLP02.
latest_claim_status_code*stringCLP02 of the most recent payment record.
latest_remittance_id*stringRemittance id of the most recent payment record.
paid_amount*stringSum of paid_amount over every payment record, reversals included, two decimals.
patient_responsibilitystringPatient responsibility from the most recent payment record only.
payments*arrayEvery payment record, ordered by remittance receipt then CLP position. Capped at 500. Same shape as a claim on Get a remittance.
payment_id*stringPayment record id, pay_....
remittance_id*stringRemittance id, rem_....
claim_id*stringClaim id.
facility_id*stringOffice id.
submission_id*stringSubmission id.
tenant_claim_idstringAlways null inside this block.
clp_position*integerPosition of the CLP within the 835.
patient_control_number*stringCLP01 as the payer returned it.
routed_by*stringHow the payment was matched to the claim, for example PCN.
claim_status_code*stringCLP02.
payer_claim_numberstringThe payer's claim number.
original_referencestringOriginal reference when the payer sent one.
filing_indicatorstringClaim filing indicator from the 835.
charged_amount*stringCharged amount, two decimals.
paid_amount*stringPaid amount, two decimals.
patient_responsibilitystringPatient responsibility, two decimals.
event_kindstringclaim.paid, claim.denied, claim.reversed, or null.
artifact_idstringThe 835 artifact behind this record.
recorded_at*datetimeWhen the record was stored.
service_lines*arrayLine payment detail from the 835.
adjustments*arrayCAS adjustments.
adjustment_id*stringAdjustment id, adj_....
level*stringclaim or line level.
line_numberintegerLine number for a line adjustment. Null at claim level.
group_code*stringCAS group code, for example PR.
reason_code*stringCARC reason code.
amount*stringAdjustment amount, two decimals.
quantitynumberAdjusted quantity when present.
remark_codes*arrayArray of strings. RARC remark codes.

Responses

200OK
{
  "request_id": "evt_gm95q5amdmz51w17784r",
  "remittance": null,
  "claim_id": "clm_809svf25qxpwhn9kwp6m",
  "claim_reference": null,
  "first_submitted_at": null,
  "reference_date": null,
  "reference_timezone": null,
  "submission_id": "sub_8790w72qvbakjgxs0vsx",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "pcn": "SBX-SYN0012",
  "tenant_claim_id": "SYN0012",
  "d9": "sub_8790w72qvbakjgxs0vsx",
  "payer_id": "TESTPAYER1",
  "state": "TRANSPORTED",
  "payer_claim_control_number": null,
  "service_date_from": null,
  "service_date_to": null,
  "charge_amount": 100,
  "last_event_at": "2026-09-05T14:30:00+00:00",
  "updated_at": "2026-09-05T14:30:00+00:00",
  "ladder": [
    {
      "event_id": "evt_qxygv7kbtajd6rcjwcqn",
      "sequence": 1003,
      "kind": "claim.received",
      "artifact_id": null,
      "occurred_at": "2026-09-05T14:30:00+00:00",
      "summary": {
        "verdict": "ACCEPTED"
      }
    },
    {
      "event_id": "evt_66983ba09mqjne9dtyza",
      "sequence": 1004,
      "kind": "claim.queued",
      "artifact_id": null,
      "occurred_at": "2026-09-05T14:30:00+00:00",
      "summary": {
        "verdict": "ACCEPTED"
      }
    },
    {
      "event_id": "evt_127f8j9cz908qrykjx78",
      "sequence": 1005,
      "kind": "claim.transported",
      "artifact_id": "art_0n1axkj8r8rcjdwffnc4",
      "occurred_at": "2026-09-05T14:30:00+00:00",
      "summary": {
        "verdict": "ACCEPTED"
      }
    }
  ]
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such claim",
  "errors": [],
  "request_id": "evt_72sgpr7d56rt5v4kyyzy"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route permission
403FACILITY_NOT_GRANTEDKey has no office grant
404NOT_FOUNDNo such claim in your seller and grants
422INVALID_REQUESTPath id longer than 64 characters
429TOO_MANY_REQUESTSOver 20 in flight or rate bucket empty
Infocharge_amount is a JSON number while every amount inside remittance is a two-decimal string. Parse both defensively.
Importantremittance.payments[].service_lines currently comes back empty on the hosted API even when the 835 carried line detail. Use the remittance routes for line-level payment detail.
Infoclaim_reference is not an X12 control number. The values the clearinghouse and payer see are pcn (CLM01) and d9 (REF*D9).
API reference

Track a claim

GEThttps://api.claimhouse.ai/v1/claims/{claim_id}/tracking

Return where one claim is as one plain sentence, with the ladder, what happens next and by when, and its artifacts.

Use it for a status widget or a support tool. status is a fixed sentence per state and nextExpected names the event expected next, with a deadline when a watchdog window governs it (4 hours for the 997, 2 days for the 277, measured from transport).

This response uses camelCase field names and carries the request id only in the X-Request-Id header. It does not include claim_reference or first_submitted_at.

Permission
read
Idempotency
none
Side effects
Writes one read audit row. No clearinghouse or payer call.
In the dashboard
Claims > Claim detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
claim_id*stringThe Claim House claim id, clm_.... 1 to 64 characters.
e.g. clm_c41063850yd2fh8x75cm

Request example

null

Response fields

NameTypeDescription
claimId*stringThe claim id.
submissionIdstringThe submission id.
facilityIdstringThe office id.
patientControlNumberstringCLM01 as transmitted (pcn).
tenantClaimIdstringYour claim id.
payerIdstringPayer id.
payerClaimNumberstringThe payer's claim number from a 277 or 835. Null when absent.
state*stringCurrent claim state.
status*stringOne plain sentence for the state, for example "Held by the gateway pending operator review. Do not resubmit."
ladderarrayEvery event, oldest first. Capped at 500.
event*stringEvent type.
occurredAtdatetimeEvent time.
artifactIdstringArtifact behind the event.
summaryobjectEvent payload.
nextExpected*objectWhat should happen next.
eventstringThe event type expected next (claim.queued, claim.transported, claim.ack_997, claim.status_277 or remittance.received). Null when nothing is expected.
dueAtdatetimeDeadline for that event when a watchdog window governs it. Null otherwise, and null when the ladder has no claim.transported event.
basisstringWhy that is the expectation, in words.
artifactsarrayOne entry per ladder event that has an artifact. The same artifact can appear more than once.
artifactId*stringArtifact id.
event*stringEvent type.
occurredAtdatetimeEvent time.
remittanceobjectPayment summary. Null until at least one 835 payment record exists for the claim. Same block as on Get a claim.
latest_event_kindstringEvent kind of the most recent payment record: claim.paid, claim.denied, claim.reversed, or null for an unmapped CLP02.
latest_claim_status_code*stringCLP02 of the most recent payment record.
latest_remittance_id*stringRemittance id of the most recent payment record.
paid_amount*stringSum of paid_amount over every payment record, reversals included, two decimals.
patient_responsibilitystringPatient responsibility from the most recent payment record only.
payments*arrayEvery payment record, ordered by remittance receipt then CLP position. Capped at 500. Same shape as a claim on Get a remittance.
payment_id*stringPayment record id, pay_....
remittance_id*stringRemittance id, rem_....
claim_id*stringClaim id.
facility_id*stringOffice id.
submission_id*stringSubmission id.
tenant_claim_idstringAlways null inside this block.
clp_position*integerPosition of the CLP within the 835.
patient_control_number*stringCLP01 as the payer returned it.
routed_by*stringHow the payment was matched to the claim, for example PCN.
claim_status_code*stringCLP02.
payer_claim_numberstringThe payer's claim number.
original_referencestringOriginal reference when the payer sent one.
filing_indicatorstringClaim filing indicator from the 835.
charged_amount*stringCharged amount, two decimals.
paid_amount*stringPaid amount, two decimals.
patient_responsibilitystringPatient responsibility, two decimals.
event_kindstringclaim.paid, claim.denied, claim.reversed, or null.
artifact_idstringThe 835 artifact behind this record.
recorded_at*datetimeWhen the record was stored.
service_lines*arrayLine payment detail from the 835.
adjustments*arrayCAS adjustments.
adjustment_id*stringAdjustment id, adj_....
level*stringclaim or line level.
line_numberintegerLine number for a line adjustment. Null at claim level.
group_code*stringCAS group code, for example PR.
reason_code*stringCARC reason code.
amount*stringAdjustment amount, two decimals.
quantitynumberAdjusted quantity when present.
remark_codes*arrayArray of strings. RARC remark codes.
updatedAtdatetimeLast update.

Responses

200OK
{
  "claimId": "clm_c41063850yd2fh8x75cm",
  "submissionId": "sub_6avvqt0h4xp1xjn0797d",
  "facilityId": "fac_tycrfy0cs0qc7sq95eme",
  "patientControlNumber": "SBX-SYN0013",
  "tenantClaimId": "SYN0013",
  "payerId": "TESTPAYER1",
  "payerClaimNumber": null,
  "state": "TRANSPORTED",
  "status": "Delivered to the clearinghouse; waiting for the 997 acknowledgment.",
  "ladder": [
    {
      "event": "claim.received",
      "occurredAt": "2026-09-05T14:30:00+00:00",
      "artifactId": null,
      "summary": {
        "verdict": "ACCEPTED"
      }
    },
    {
      "event": "claim.queued",
      "occurredAt": "2026-09-05T14:30:00+00:00",
      "artifactId": null,
      "summary": {
        "verdict": "ACCEPTED"
      }
    },
    {
      "event": "claim.transported",
      "occurredAt": "2026-09-05T14:30:00+00:00",
      "artifactId": "art_zahm54z8ercz9q747369",
      "summary": {
        "verdict": "ACCEPTED"
      }
    }
  ],
  "nextExpected": {
    "event": "claim.ack_997",
    "dueAt": "2026-09-05T18:30:00+00:00",
    "basis": "997 acknowledgment expected within 4 hours of transport"
  },
  "artifacts": [
    {
      "artifactId": "art_zahm54z8ercz9q747369",
      "event": "claim.transported",
      "occurredAt": "2026-09-05T14:30:00+00:00"
    }
  ],
  "remittance": null,
  "updatedAt": "2026-09-05T14:30:00+00:00"
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such claim",
  "errors": [],
  "request_id": "evt_gvr8qxq5eh31dk3yjqb2"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route permission
403FACILITY_NOT_GRANTEDKey has no office grant
404NOT_FOUNDNo such claim in your seller and grants
422INVALID_REQUESTPath id longer than 64 characters
429TOO_MANY_REQUESTSOver 20 in flight or rate bucket empty
InfoA state the sentence table does not know (for example CLOSED) reads "In an unrecognized state; an operator has been asked to look." with no next expected event.
InfodueAt uses the default windows. An office can have a per-binding override, so a stall may be raised at a different time. remittance.received is informational; the state-changing payment events are claim.paid, claim.denied and claim.reversed.
API reference

Claim drafts

Hold a claim as a draft, build it to see exactly what intake would decide and the rendered 837D, then send it once.

Use drafts when a person or a second system must review a claim before it is sent: create (OPEN), optionally edit, build (READY or HELD), then submit once (SUBMITTED, HELD, or REJECTED). source_kind fixes the intake door at create (x12 raw X12, json JSON claim, ada and blank ADA form). pdf bytes are inspected locally, but flattened, encrypted, active, malformed and unreviewed templates are refused; no PDF template is enabled for draft creation yet. from_claim is refused. Every successful edit, build, and submit increments version, so submit with the version from the build response. A key only sees drafts of its own mode in offices it is granted. A draft is patient data held before the ledger: DELETE hard deletes it and expires_at is 30 days after create. If your system already trusts its output, the direct submission routes are one call instead of three.

StateMeaningEditBuildSubmitDelete
OPENCreated, never builtYesYesNo, 409 DRAFT_NOT_READYYes
READYLast build found no error and no holdYes (makes the build stale)YesYes, if the build is not staleYes
HELDLast build found an error or hold, or submit landed in intake HOLDYesYesOnly when the stored verdict is still readyYes
SUBMITTEDSubmit queued the claimNo, 409 DRAFT_ALREADY_SUBMITTEDNoNoNo
REJECTEDSubmit landed in intake REJECTED_PRE_TRANSPORTNo, 409 DRAFT_NOT_OPENNoNoNo, 404 DRAFT_NOT_FOUND
What build does, by doorSource kindsBuild runs
raw_x12x12The stored 837D re-parsed with the draft's packet link and claim id, through the same checks as POST /v1/submissions without attachments[]. Returns your text as rendered_x12.
jsonjsonThe stored JSON claim through the same assembly as POST /v1/dental-claims/submission (payer resolution, builder, identity gate), using json_claim.facilityId. Returns the built 837D as rendered_x12.
ada_formada, blankNot wired yet. Always records ADA_NORMALIZER_PENDING, state HELD, ready: false, and no rendered_x12. These drafts can be saved and edited but never sent.
ImportantBuild runs the intake check phase (office grant, parse, billing identity and mode, validation, holds, enrollment) and stores the result without writing to the claim ledger or contacting the clearinghouse. A build with errors or holds still answers 200 with state: "HELD". Read verdict.ready for the send decision and findings[] with a case-insensitive severity for the reasons: packet and identity holds arrive as severity error and leave verdict.holds[] empty while ready is false.
  1. Submit only after a fresh passing build: verdict.ready is true and build_stale is false.
  2. Call submit once, with the build's version and, optionally, your own Idempotency-Key (without one, the draft's own key minted at create is used). Submit reassembles the claim from the stored source and runs the full intake, answering with the same 202 receipt as POST /v1/submissions plus draft_id.
  3. Nothing reaches the clearinghouse during the call. The dispatcher transports a queued file later, exactly once, never retried; an unverifiable transport goes to an operator, never a resend.
  4. Never resend. A second submit of a SUBMITTED draft is refused with 409 DRAFT_ALREADY_SUBMITTED before intake, and identical content under a different key within 24 hours is 409 DUPLICATE_CONTENT.
  5. After a lost response, GET the draft, read submission_id, then GET /v1/submissions/{submission_id}.
API reference

Create a claim draft

POSThttps://api.claimhouse.ai/v1/claims/drafts

Store one claim input as a draft without submitting it.

Creates a draft in state OPEN from an 837D (x12), a JSON dental claim (json), or ADA form fields (ada, blank). PDF bytes can be inspected, but no PDF template is enabled for draft creation yet. Nothing reaches the claim ledger.

Each call creates a new draft. Build the draft next to see findings and the rendered 837D.

Permission
submit
Idempotency
none
Side effects
Writes one draft and an audit row and counts against the daily claim quota check. Never contacts the clearinghouse.
In the dashboard
Claims > Submit a claim > Drop a file, and Fill out a claim

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Request body

NameTypeDescription
facility_id*stringThe office the claim bills as. Must be active, granted to your key, and bound to your key's mode. 1 to 64 characters.
e.g. fac_tycrfy0cs0qc7sq95eme
source_kind*stringWhat you are sending. Fixes the draft's door. pdf is structurally inspected and currently refused before draft creation; from_claim is refused.
one of: x12 · json · ada · blank · pdf · from_claim
e.g. x12
x12stringThe 837D text, stored verbatim. Required for x12. At most 6 MB of UTF-8 and must parse as 837D.
json_claimobjectThe JSON dental claim, stored verbatim. Required for json. Must validate as the JSON claim submission body, including facilityId.
fieldsobjectADA item fields keyed by item number (string values), for ada and blank. Not validated.
linesarrayADA service lines (array of objects), for ada and blank. Not validated.
payerobjectADA payer block, for ada and blank. Not validated.
content_base64stringPDF bytes as strict base64 for pdf. Decoded bytes must be at most 6 MB. The local structural inspector refuses non-PDF, malformed, encrypted, active-content, over-limit, flattened/scanned, and unreviewed templates. No PDF template is enabled for draft creation yet.
tenant_claim_idstringYour id for the claim. At most 200 characters. For JSON drafts, json_claim.tenantClaimId wins when present.
e.g. your-claim-EXAMPLE-1
attachment_packet_idstringA pkt_ attachment packet id for packet parity. At most 64 characters. Not checked at create.
e.g. pkt_20fng953rtp1hwc3zdcn
from_claim_idstringAccepted and ignored. At most 64 characters.

Request example

{
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "source_kind": "x12",
  "x12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*HC*TESTSENDER*262403888*20260905*1430*1*X*005010X224A2~ST*837*0001*005010X224A2~BHT*0019*00*TESTBATCH0001*20260905*1430*CH~NM1*41*2*TEST SUBMITTER*****46*TESTSENDER~PER*IC*TEST CONTACT*TE*5555550100~NM1*40*2*TEST RECEIVER*****46*262403888~HL*1**20*1~PRV*BI*PXC*1223G0001X~NM1*85*2*TEST BILLING OFFICE*****XX*9999999995~N3*1 TEST STREET~N4*TESTCITY*OH*44000~REF*EI*990000001~REF*0B*TESTLIC0001~HL*2*1*22*0~SBR*P*18*******CI~NM1*IL*1*TEST*PATIENT****MI*TESTMEMBER01~N3*2 TEST LANE~N4*TESTCITY*OH*44000~DMG*D8*19800101*F~NM1*PR*2*TEST PAYER*****PI*TESTPAYER1~CLM*DRF0015*150.00***11:B:1*Y*A*Y*Y~DTP*472*D8*20260901~HI*BK:K089~NM1*82*1*TEST*PROVIDER****XX*9999999995~LX*1~SV3*AD:D0120*150***1~DTP*472*D8*20260901~SE*27*0001~GE*1*1~IEA*1*000000001~",
  "tenant_claim_id": "your-claim-0001"
}

Response fields

NameTypeDescription
draft_id*stringThe draft id, drf_....
e.g. drf_EXAMPLE0000000000001
facility_id*stringThe office the draft belongs to. Controls who can see the draft.
e.g. fac_EXAMPLE0000000000001
source_kind*stringWhat the draft was created from. Fixed at create.
one of: x12 · json · ada · blank
door*stringThe intake door the draft builds and submits through, derived from source_kind: x12 uses raw_x12, json uses json, ada and blank use ada_form.
one of: raw_x12 · json · ada_form
state*stringWhere the draft is in its lifecycle.
one of: OPEN · READY · HELD · SUBMITTED · REJECTED
version*integerOptimistic lock. Every successful edit, build, and submit increments it.
e.g. 1
saved_at*datetimeISO 8601 time of the last change.
expires_at*datetimeISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today.
tenant_claim_idstringYour id for the claim. Null when not set.
fields*objectADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts.
lines*arrayADA service lines (array of objects) for ada and blank drafts. An empty array otherwise.
payerobjectADA payer block for ada and blank drafts. Null otherwise.
findings*arrayFindings from the last build. Empty before the first build.
severity*stringFinding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.
one of: HOLD · error · warning · INFO
code*stringStable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING.
message*stringHuman-readable reason. Never contains a claim value.
itemstringADA item number when known. Null for intake findings today.
pathstringMachine location of the finding, for example transaction[0]/claim[0]. Null when unknown.
verdict*objectThe send decision from the last build. An empty object before the first build.
readybooleantrue only when the last build found no error and no hold. Use this for the send decision.
holdsarrayArray of strings. Codes of findings with severity HOLD. Can be empty while ready is false.
warningsarrayArray of strings. Codes of findings with severity WARNING. Empty for real intake warnings today.
built_atdatetimeISO 8601 time of the last build. Null before the first build.
build_stale*booleantrue until the first build, and again after any edit. Submit requires false.
attachmentobjectThe linked attachment packet. Null when no packet is linked.
packet_id*stringThe pkt_ id used for packet parity.
e.g. pkt_EXAMPLE0000000000001
submission_idstringThe submission created by submit. Null until submit.
submitted_atdatetimeISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit.

Responses

201Created
{
  "draft_id": "drf_rp2zxx59da6pndz06nyn",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "source_kind": "x12",
  "door": "raw_x12",
  "state": "OPEN",
  "version": 1,
  "saved_at": "2026-09-05T14:30:00+00:00",
  "expires_at": "2026-10-05T14:30:00+00:00",
  "tenant_claim_id": "your-claim-0001",
  "fields": {},
  "lines": [],
  "payer": null,
  "findings": [],
  "verdict": {},
  "built_at": null,
  "build_stale": true,
  "attachment": null,
  "submission_id": null,
  "submitted_at": null
}
422Source kind not supported
{
  "error": "DRAFT_SOURCE_UNSUPPORTED",
  "message": "draft source unsupported",
  "errors": [
    {
      "source_kind": "from_claim"
    }
  ],
  "request_id": "evt_2awbm0d9j3jt5aqceeqa"
}
422Source unreadable
{
  "error": "CLAIM_SOURCE_UNREADABLE",
  "message": "claim source unreadable",
  "errors": [
    {
      "reason": "The X12 source did not parse."
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown, or revoked key
400DRAFT_DENIEDThe draft could not be stored
403DRAFT_ACCESS_DENIEDOffice unknown, inactive, not granted, or bound to the other mode
403PERMISSION_DENIEDKey lacks submit
413PAYLOAD_TOO_LARGEx12 or decoded pdf bytes exceed 6 MB
422CLAIM_SOURCE_UNREADABLEx12 is empty or unparseable; json has no valid json_claim; or pdf is missing, invalid base64, non-PDF, malformed, encrypted, active, over structural limits, flattened/scanned, or an unreviewed template
422DRAFT_SOURCE_UNSUPPORTEDsource_kind is from_claim; a future reviewed PDF template also remains unsupported until extraction and immutable source-artifact lineage land together
422INVALID_REQUESTBody or parameters do not fit the schema
429TOO_MANY_REQUESTSRate bucket empty or too many requests in flight
429QUOTA_EXCEEDEDSeller's daily claim quota used up
503DRAFTS_UNAVAILABLEDrafts are not configured on this gateway
ImportantJSON drafts: facility_id controls where the draft is visible, but build and submit use json_claim.facilityId, and create does not check that they match. Always send the same office in both.
InfoJSON drafts take your key's mode. A json_claim.usageIndicator that does not match is caught at build as 400 CLAIM_BUILD_FAILED.
InfoOn draft routes, INVALID_REQUEST entries are {code, path, message: "Invalid claim field.", value_redacted: true}, and path segments that are not schema property names are replaced with field.
InfoThe response has no request_id in the body. Read the X-Request-Id header.
API reference

Get a claim draft

GEThttps://api.claimhouse.ai/v1/claims/drafts/{draft_id}

Return one draft with its findings, verdict, and attachment link.

Use it to show a review screen, or to confirm a submit outcome after a lost response by reading state and submission_id.

The rendered 837D is not included. Build again to see it.

Permission
read
Idempotency
none
Side effects
Writes one read audit row. Never contacts the clearinghouse.
In the dashboard
Claims > Submit a claim > Draft flow (Review, Attach, Send)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
draft_id*stringThe draft to act on, a drf_ id. 1 to 64 characters.
e.g. drf_t2q7c2q033cwa9rhj9ns

Request example

null

Response fields

NameTypeDescription
draft_id*stringThe draft id, drf_....
e.g. drf_EXAMPLE0000000000001
facility_id*stringThe office the draft belongs to. Controls who can see the draft.
e.g. fac_EXAMPLE0000000000001
source_kind*stringWhat the draft was created from. Fixed at create.
one of: x12 · json · ada · blank
door*stringThe intake door the draft builds and submits through, derived from source_kind: x12 uses raw_x12, json uses json, ada and blank use ada_form.
one of: raw_x12 · json · ada_form
state*stringWhere the draft is in its lifecycle.
one of: OPEN · READY · HELD · SUBMITTED · REJECTED
version*integerOptimistic lock. Every successful edit, build, and submit increments it.
e.g. 1
saved_at*datetimeISO 8601 time of the last change.
expires_at*datetimeISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today.
tenant_claim_idstringYour id for the claim. Null when not set.
fields*objectADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts.
lines*arrayADA service lines (array of objects) for ada and blank drafts. An empty array otherwise.
payerobjectADA payer block for ada and blank drafts. Null otherwise.
findings*arrayFindings from the last build. Empty before the first build.
severity*stringFinding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.
one of: HOLD · error · warning · INFO
code*stringStable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING.
message*stringHuman-readable reason. Never contains a claim value.
itemstringADA item number when known. Null for intake findings today.
pathstringMachine location of the finding, for example transaction[0]/claim[0]. Null when unknown.
verdict*objectThe send decision from the last build. An empty object before the first build.
readybooleantrue only when the last build found no error and no hold. Use this for the send decision.
holdsarrayArray of strings. Codes of findings with severity HOLD. Can be empty while ready is false.
warningsarrayArray of strings. Codes of findings with severity WARNING. Empty for real intake warnings today.
built_atdatetimeISO 8601 time of the last build. Null before the first build.
build_stale*booleantrue until the first build, and again after any edit. Submit requires false.
attachmentobjectThe linked attachment packet. Null when no packet is linked.
packet_id*stringThe pkt_ id used for packet parity.
e.g. pkt_EXAMPLE0000000000001
submission_idstringThe submission created by submit. Null until submit.
submitted_atdatetimeISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit.

Responses

200OK
{
  "draft_id": "drf_t2q7c2q033cwa9rhj9ns",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "source_kind": "x12",
  "door": "raw_x12",
  "state": "OPEN",
  "version": 1,
  "saved_at": "2026-09-05T14:30:00+00:00",
  "expires_at": "2026-10-05T14:30:00+00:00",
  "tenant_claim_id": "your-claim-0001",
  "fields": {},
  "lines": [],
  "payer": null,
  "findings": [],
  "verdict": {},
  "built_at": null,
  "build_stale": true,
  "attachment": null,
  "submission_id": null,
  "submitted_at": null
}
404Draft not found
{
  "error": "DRAFT_NOT_FOUND",
  "message": "draft not found",
  "errors": [
    {
      "draft_id": "drf_mxc3nyetdazwd7w4ffgs"
    }
  ],
  "request_id": "evt_hn8t54zz10xprh6wzx2q"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown, or revoked key
403PERMISSION_DENIEDKey lacks read
404DRAFT_NOT_FOUNDNo such draft for your seller, its office is no longer active or granted, or it belongs to the other mode
429TOO_MANY_REQUESTSRate bucket empty or too many requests in flight
503DRAFTS_UNAVAILABLEDrafts are not configured on this gateway
InfoExpiry is not enforced on read today, so a draft past expires_at stays readable and usable.
InfoThe response has no request_id in the body. Read the X-Request-Id header.
API reference

Update a claim draft

PATCHhttps://api.claimhouse.ai/v1/claims/drafts/{draft_id}

Change an ADA draft's fields, lines, or payer, or any draft's claim id or packet link.

Omitted or null fields are left unchanged. Every edit increments version and sets build_stale: true, so build again before submit.

x12 and json drafts are verbatim sources: only tenant_claim_id and attachment_packet_id can change. To correct the claim itself, create a new draft.

Permission
submit
Idempotency
none
Side effects
Updates the draft, increments version, marks the build stale, and writes an audit row. Never contacts the clearinghouse.
In the dashboard
Claims > Submit a claim > Fill out a claim (Save), and Draft flow > Attach

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
draft_id*stringThe draft to act on, a drf_ id. 1 to 64 characters.
e.g. drf_fcc0f9mx74mmw7qh22za

Request body

NameTypeDescription
version*integerThe draft's current version. At least 1. A stale value is refused, so a repeated edit never applies twice.
e.g. 1
fieldsobjectReplaces the whole ADA fields object (string values). ada and blank drafts only.
linesarrayReplaces the whole ADA lines array (array of objects). ada and blank drafts only.
payerobjectReplaces the whole ADA payer object. ada and blank drafts only.
tenant_claim_idstringNew claim id. At most 200 characters.
e.g. your-claim-EXAMPLE-2
attachment_packet_idstringNew pkt_ packet link. At most 64 characters. Not checked.
e.g. pkt_krz5sag8qfzp63jvw8vw

Request example

{
  "version": 1,
  "attachment_packet_id": "pkt_2g7hcpgd99p0p0hnhnaa"
}

Response fields

NameTypeDescription
draft_id*stringThe draft id, drf_....
e.g. drf_EXAMPLE0000000000001
facility_id*stringThe office the draft belongs to. Controls who can see the draft.
e.g. fac_EXAMPLE0000000000001
source_kind*stringWhat the draft was created from. Fixed at create.
one of: x12 · json · ada · blank
door*stringThe intake door the draft builds and submits through, derived from source_kind: x12 uses raw_x12, json uses json, ada and blank use ada_form.
one of: raw_x12 · json · ada_form
state*stringWhere the draft is in its lifecycle.
one of: OPEN · READY · HELD · SUBMITTED · REJECTED
version*integerOptimistic lock. Every successful edit, build, and submit increments it.
e.g. 1
saved_at*datetimeISO 8601 time of the last change.
expires_at*datetimeISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today.
tenant_claim_idstringYour id for the claim. Null when not set.
fields*objectADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts.
lines*arrayADA service lines (array of objects) for ada and blank drafts. An empty array otherwise.
payerobjectADA payer block for ada and blank drafts. Null otherwise.
findings*arrayFindings from the last build. Empty before the first build.
severity*stringFinding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.
one of: HOLD · error · warning · INFO
code*stringStable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING.
message*stringHuman-readable reason. Never contains a claim value.
itemstringADA item number when known. Null for intake findings today.
pathstringMachine location of the finding, for example transaction[0]/claim[0]. Null when unknown.
verdict*objectThe send decision from the last build. An empty object before the first build.
readybooleantrue only when the last build found no error and no hold. Use this for the send decision.
holdsarrayArray of strings. Codes of findings with severity HOLD. Can be empty while ready is false.
warningsarrayArray of strings. Codes of findings with severity WARNING. Empty for real intake warnings today.
built_atdatetimeISO 8601 time of the last build. Null before the first build.
build_stale*booleantrue until the first build, and again after any edit. Submit requires false.
attachmentobjectThe linked attachment packet. Null when no packet is linked.
packet_id*stringThe pkt_ id used for packet parity.
e.g. pkt_EXAMPLE0000000000001
submission_idstringThe submission created by submit. Null until submit.
submitted_atdatetimeISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit.

Responses

200OK
{
  "draft_id": "drf_fcc0f9mx74mmw7qh22za",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "source_kind": "x12",
  "door": "raw_x12",
  "state": "OPEN",
  "version": 2,
  "saved_at": "2026-09-05T14:30:00+00:00",
  "expires_at": "2026-10-05T14:30:00+00:00",
  "tenant_claim_id": "your-claim-0001",
  "fields": {},
  "lines": [],
  "payer": null,
  "findings": [],
  "verdict": {},
  "built_at": null,
  "build_stale": true,
  "attachment": {
    "packet_id": "pkt_2g7hcpgd99p0p0hnhnaa"
  },
  "submission_id": null,
  "submitted_at": null
}
409Version conflict
{
  "error": "DRAFT_VERSION_CONFLICT",
  "message": "draft version conflict",
  "errors": [
    {
      "draft_id": "drf_fcc0f9mx74mmw7qh22za",
      "version": 2
    }
  ],
  "request_id": "evt_hf0fy0q80708t5nbpd7j"
}
422Not editable
{
  "error": "DRAFT_NOT_EDITABLE",
  "message": "draft not editable",
  "errors": [
    {
      "draft_id": "drf_k7n9zma3cjmbfv81h7mq",
      "source_kind": "x12",
      "reason": "verbatim-source drafts are corrected at the source and re-dropped"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown, or revoked key
403PERMISSION_DENIEDKey lacks submit
404DRAFT_NOT_FOUNDNo such draft for your seller, its office is no longer active or granted, or it belongs to the other mode
409DRAFT_ALREADY_SUBMITTEDDraft is SUBMITTED
409DRAFT_NOT_OPENDraft is REJECTED
409DRAFT_VERSION_CONFLICTversion not current (errors[0].version is the current one), or the row changed during the update
422DRAFT_NOT_EDITABLEfields, lines, or payer sent for an x12 or json draft
422INVALID_REQUESTBody or parameters do not fit the schema
429TOO_MANY_REQUESTSRate bucket empty or too many requests in flight
503DRAFTS_UNAVAILABLEDrafts are not configured on this gateway
ImportantThere is no way to clear tenant_claim_id or attachment_packet_id once set. Sending null leaves them unchanged.
API reference

Build a claim draft

POSThttps://api.claimhouse.ai/v1/claims/drafts/{draft_id}/build

Run the intake checks on a draft and return its findings, verdict, and rendered 837D.

Assembles the draft into the exact request its door would submit, runs the intake check phase, and stores findings, verdict, and state (READY or HELD). Nothing is written to the claim ledger.

A build that finds validation errors or a hold still answers 200 with state: "HELD" and verdict.ready: false. A refusal (4xx or 5xx) leaves the draft unchanged.

Permission
submit
Idempotency
none
Side effects
Updates the draft's findings, verdict, state and version and counts against the daily claim quota check. Never contacts the clearinghouse.
In the dashboard
Claims > Submit a claim > Draft flow > Review (Build), and Fill out a claim (Build)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
draft_id*stringThe draft to act on, a drf_ id. 1 to 64 characters.
e.g. drf_wrf6c3tzz29wbw83zn4m

Request body

NameTypeDescription
versionintegerWhen sent, must equal the draft's current version. Omit, send null, or send an empty body to skip the check.
e.g. 2

Request example

{
  "version": 1
}

Response fields

NameTypeDescription
draft_id*stringThe draft id, drf_....
e.g. drf_EXAMPLE0000000000001
facility_id*stringThe office the draft belongs to. Controls who can see the draft.
e.g. fac_EXAMPLE0000000000001
source_kind*stringWhat the draft was created from. Fixed at create.
one of: x12 · json · ada · blank
door*stringThe intake door the draft builds and submits through, derived from source_kind: x12 uses raw_x12, json uses json, ada and blank use ada_form.
one of: raw_x12 · json · ada_form
state*stringWhere the draft is in its lifecycle.
one of: OPEN · READY · HELD · SUBMITTED · REJECTED
version*integerOptimistic lock. Every successful edit, build, and submit increments it.
e.g. 1
saved_at*datetimeISO 8601 time of the last change.
expires_at*datetimeISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today.
tenant_claim_idstringYour id for the claim. Null when not set.
fields*objectADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts.
lines*arrayADA service lines (array of objects) for ada and blank drafts. An empty array otherwise.
payerobjectADA payer block for ada and blank drafts. Null otherwise.
findings*arrayFindings from the last build. Empty before the first build.
severity*stringFinding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.
one of: HOLD · error · warning · INFO
code*stringStable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING.
message*stringHuman-readable reason. Never contains a claim value.
itemstringADA item number when known. Null for intake findings today.
pathstringMachine location of the finding, for example transaction[0]/claim[0]. Null when unknown.
verdict*objectThe send decision from the last build. An empty object before the first build.
readybooleantrue only when the last build found no error and no hold. Use this for the send decision.
holdsarrayArray of strings. Codes of findings with severity HOLD. Can be empty while ready is false.
warningsarrayArray of strings. Codes of findings with severity WARNING. Empty for real intake warnings today.
built_atdatetimeISO 8601 time of the last build. Null before the first build.
build_stale*booleantrue until the first build, and again after any edit. Submit requires false.
attachmentobjectThe linked attachment packet. Null when no packet is linked.
packet_id*stringThe pkt_ id used for packet parity.
e.g. pkt_EXAMPLE0000000000001
submission_idstringThe submission created by submit. Null until submit.
submitted_atdatetimeISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit.
rendered_x12stringThe 837D the door produced. For x12 drafts, your stored text. For json drafts, the file as built, before intake rewrites CLM01, REF*6R, REF*D9 and BHT03. Absent for ada and blank drafts.

Responses

200OK
{
  "draft_id": "drf_wrf6c3tzz29wbw83zn4m",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "source_kind": "x12",
  "door": "raw_x12",
  "state": "READY",
  "version": 2,
  "saved_at": "2026-09-05T14:30:00+00:00",
  "expires_at": "2026-10-05T14:30:00+00:00",
  "tenant_claim_id": "your-claim-0001",
  "fields": {},
  "lines": [],
  "payer": null,
  "findings": [],
  "verdict": {
    "ready": true,
    "holds": [],
    "warnings": []
  },
  "built_at": "2026-09-05T14:30:00+00:00",
  "build_stale": false,
  "attachment": null,
  "submission_id": null,
  "submitted_at": null,
  "rendered_x12": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*HC*TESTSENDER*262403888*20260905*1430*1*X*005010X224A2~ST*837*0001*005010X224A2~BHT*0019*00*TESTBATCH0001*20260905*1430*CH~NM1*41*2*TEST SUBMITTER*****46*TESTSENDER~PER*IC*TEST CONTACT*TE*5555550100~NM1*40*2*TEST RECEIVER*****46*262403888~HL*1**20*1~PRV*BI*PXC*1223G0001X~NM1*85*2*TEST BILLING OFFICE*****XX*9999999995~N3*1 TEST STREET~N4*TESTCITY*OH*44000~REF*EI*990000001~REF*0B*TESTLIC0001~HL*2*1*22*0~SBR*P*18*******CI~NM1*IL*1*TEST*PATIENT****MI*TESTMEMBER01~N3*2 TEST LANE~N4*TESTCITY*OH*44000~DMG*D8*19800101*F~NM1*PR*2*TEST PAYER*****PI*TESTPAYER1~CLM*DRF0018*150.00***11:B:1*Y*A*Y*Y~DTP*472*D8*20260901~HI*BK:K089~NM1*82*1*TEST*PROVIDER****XX*9999999995~LX*1~SV3*AD:D0120*150***1~DTP*472*D8*20260901~SE*27*0001~GE*1*1~IEA*1*000000001~"
}
200OK (ADA draft, normalizer pending)
{
  "draft_id": "drf_k7n9zma3cjmbfv81h7mq",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "source_kind": "blank",
  "door": "ada_form",
  "state": "HELD",
  "version": 3,
  "saved_at": "2026-09-14T18:02:00+00:00",
  "expires_at": "2026-10-14T18:00:00+00:00",
  "tenant_claim_id": null,
  "fields": {
    "1": "EXAMPLE"
  },
  "lines": [],
  "payer": null,
  "findings": [
    {
      "severity": "HOLD",
      "code": "ADA_NORMALIZER_PENDING",
      "message": "this draft's door awaits the ADA normalizer; it cannot be sent yet",
      "item": null,
      "path": null
    }
  ],
  "verdict": {
    "ready": false,
    "holds": [
      "ADA_NORMALIZER_PENDING"
    ],
    "warnings": []
  },
  "built_at": "2026-09-14T18:02:00+00:00",
  "build_stale": false,
  "attachment": null,
  "submission_id": null,
  "submitted_at": null
}
409Version conflict
{
  "error": "DRAFT_VERSION_CONFLICT",
  "message": "draft version conflict",
  "errors": [
    {
      "draft_id": "drf_wrf6c3tzz29wbw83zn4m",
      "version": 2
    }
  ],
  "request_id": "evt_7em48rrq8kp1mgddp4sk"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown, or revoked key
403PERMISSION_DENIEDKey lacks submit
404DRAFT_NOT_FOUNDNo such draft for your seller, its office is no longer active or granted, or it belongs to the other mode
400PAYER_NOT_FOUNDJSON draft: payer not in the registry
400CLAIM_BUILD_FAILEDJSON draft: the claim model or builder refused the claim
403FACILITY_NOT_GRANTEDThe office (for JSON, json_claim.facilityId) is not granted
403MODE_MISMATCHOffice binding does not accept the draft mode
403BILLING_IDENTITY_MISMATCH2010AA NPI or TIN is not the office's
409DRAFT_ALREADY_SUBMITTEDDraft is SUBMITTED
409DRAFT_NOT_OPENDraft is REJECTED
409DRAFT_VERSION_CONFLICTversion not current, or the row changed during the call
409PAYER_NOT_ENROLLEDPayer not live for the office
413PAYLOAD_TOO_LARGEStored X12 over 6 MB
422CLAIM_SOURCE_UNREADABLEStored X12 missing or unparseable, or stored JSON no longer validates
422ATTACHMENT_REFERENCE_AMBIGUOUSJSON draft names more than one packet
502CLAIM_BUILD_INVALIDJSON draft: builder output unparseable
503CLAIM_BUILDER_UNAVAILABLEJSON draft: builder not configured
422INVALID_REQUESTBody or parameters do not fit the schema
429TOO_MANY_REQUESTSRate bucket empty or too many requests in flight
429QUOTA_EXCEEDEDSeller's daily claim quota used up
503DRAFTS_UNAVAILABLEDrafts are not configured on this gateway
ImportantBeta: the ADA form door is not built. Every build of an ada or blank draft returns the single finding ADA_NORMALIZER_PENDING with state HELD, ready: false, and no rendered_x12, so those drafts can never be submitted. x12 and json drafts build fully.
ImportantBuild does not check idempotency or duplicate content. A draft whose content was already submitted can build READY and then fail at submit with 409 DUPLICATE_CONTENT.
ImportantIn the hosted API today, attachment packet lookup is not wired, so every draft linked to an attachment packet builds HELD with PACKET_PARITY.
InfoFor JSON drafts, builder-only findings (such as LINE_ATTACHMENT_FOLDED or CLAIM_SPLIT) are not stored. A builder hold appears as one finding with the builder's hold code and the generic message the claim is held: <CODE>. PAYER_NOT_FOUND uses the standard error envelope here, not the compat envelope.
InfoBuild increments version. Submit with the version from this response.
API reference

Submit a claim draft

POSThttps://api.claimhouse.ai/v1/claims/drafts/{draft_id}/submit

Send a built, ready draft through intake once and return the submission receipt.

Requires a fresh passing build: built_at set, build_stale: false, and verdict.ready: true. Submit reassembles the request from the stored source (the build output is never reused) and runs the full intake, exactly like the direct routes.

Answers 202 with the same receipt as POST /v1/submissions, plus draft_id. Queued, held, and rejected outcomes all answer 202; read state.

Permission
submit
Idempotency
optional
Side effects
Writes the submission, claims, events and receipt, then updates the draft. Never contacts the clearinghouse during the call; the dispatcher transports a queued file later, exactly once, never retried.
In the dashboard
Claims > Submit a claim > Draft flow > Send

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Idempotency-KeystringYour unique value for this send. Without it the draft's own key, minted at create, is used. Same key and same content within 24 hours replays the stored receipt.
e.g. 5d2e-EXAMPLE
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
draft_id*stringThe draft to act on, a drf_ id. 1 to 64 characters.
e.g. drf_2abp0mnygtbkc0z73y7j

Request body

NameTypeDescription
versionintegerWhen sent, must equal the draft's current version, which is the version returned by build.
e.g. 3

Request example

{
  "version": 2
}

Response fields

NameTypeDescription
request_id*stringThe request id, also in X-Request-Id.
e.g. evt_EXAMPLE0000000000005
submission_id*stringThe submission id, sub_....
e.g. sub_EXAMPLE0000000000001
state*stringThe intake outcome. QUEUED sets the draft SUBMITTED, HOLD sets it HELD, REJECTED_PRE_TRANSPORT sets it REJECTED.
one of: QUEUED · HOLD · REJECTED_PRE_TRANSPORT
idempotent_replay*booleantrue when this is a stored answer for the same key and content.
claims*arrayOne entry per claim in the file.
claim_id*stringThe claim id, clm_....
e.g. clm_EXAMPLE0000000000001
tenant_claim_id*stringYour original CLM01 as it appeared in the file.
pcn*stringCLM01 as transmitted: <office prefix>-<CLM01>, tokenized when longer than 17 characters.
payer_id*stringLoop 2010BB payer id from the file.
state*stringSame as the submission state at intake.
one of: QUEUED · HOLD · REJECTED_PRE_TRANSPORT
was_tokenized*booleantrue when the PCN is a 12-character token instead of your CLM01.
lines*arrayService lines.
line_control_number*stringREF*6R, filled by the gateway when absent.
procedure_code*stringCDT code.
validation*objectValidation result for the file.
status*stringREJECTED when any finding has severity error (including a hold finding), otherwise ACCEPTED. Read state and hold_reason to tell a hold from a rejection.
one of: ACCEPTED · REJECTED
errors*arrayEvery finding, errors and warnings.
code*stringFinding code, for example PWK_PARITY or LICENSE_RECOMMENDED.
severity*stringFinding severity.
one of: error · warning
location*stringWhere in the file, for example transaction[0]/claim[1].
message*stringNever contains a value from your file.
value_redacted*booleanAlways true.
acknowledgment_999*stringThe gateway's own 999 for this intake decision. Never transported.
hold_reasonstringWhy the claim is held, for example PACKET_PARITY or IDENTITY_HIERARCHY. Null when not held.
dispatchobjectTransport estimate. Null for held and rejected submissions.
window_idstringAlways null at intake. Read the submission later to see the window.
expected_transport_by*datetimeISO 8601 estimate of when the next transport window picks the submission up.
draft_id*stringThe draft that was consumed.
e.g. drf_EXAMPLE0000000000001

Responses

202Accepted
{
  "request_id": "evt_ehc6n4xghen0grqeay1k",
  "submission_id": "sub_t7j6hdd055pwdbcwbzbx",
  "state": "QUEUED",
  "idempotent_replay": false,
  "claims": [
    {
      "claim_id": "clm_1q2n1zwqkjr715g9xk5b",
      "tenant_claim_id": "DRF0019",
      "pcn": "SBX-DRF0019",
      "payer_id": "TESTPAYER1",
      "state": "QUEUED",
      "was_tokenized": false,
      "lines": [
        {
          "line_control_number": "CLM90WVWJD14D3553F0WEKD",
          "procedure_code": "D0120"
        }
      ]
    }
  ],
  "validation": {
    "status": "ACCEPTED",
    "errors": []
  },
  "acknowledgment_999": "ISA*00*          *00*          *ZZ*TESTSENDER     *ZZ*262403888      *260905*1430*^*00501*000000001*0*T*:~GS*FA*TESTSENDER*262403888*20260905*1430*1*X*005010X231A1~ST*999*0001*005010X231A1~AK1*HC*1*005010X224A2~AK2*837*0001*005010X224A2~IK5*A~AK9*A*1*1*1~SE*6*0001~GE*1*1~IEA*1*000000001~",
  "hold_reason": null,
  "dispatch": {
    "window_id": null,
    "expected_transport_by": "2026-09-05T14:35:00+00:00"
  },
  "draft_id": "drf_2abp0mnygtbkc0z73y7j"
}
409Draft not ready
{
  "error": "DRAFT_NOT_READY",
  "message": "draft not ready",
  "errors": [
    {
      "draft_id": "drf_z7hk8ns6ghcv1nxxhrdr",
      "state": "OPEN"
    }
  ],
  "request_id": "evt_8hkhd6rkgd0raa4q3tnp"
}
409Already submitted
{
  "error": "DRAFT_ALREADY_SUBMITTED",
  "message": "draft already submitted",
  "errors": [
    {
      "draft_id": "drf_k7n9zma3cjmbfv81h7mq"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown, or revoked key
403PERMISSION_DENIEDKey lacks submit
404DRAFT_NOT_FOUNDNo such draft visible to your key, or the intake write succeeded but the draft could not be updated
409DRAFT_NOT_READYNever built, build stale, verdict not ready, or an ada or blank draft
409DUPLICATE_CONTENTIdentical content already submitted for this office under another key within 24 hours
409IDEMPOTENCY_IN_PROGRESSSame key still being written; Retry-After: 5
422IDEMPOTENCY_KEY_REUSEDSame key already used with different content within 24 hours
400PAYER_NOT_FOUNDJSON draft: payer not in the registry
400CLAIM_BUILD_FAILEDJSON draft: the claim model or builder refused the claim
403FACILITY_NOT_GRANTEDThe office (for JSON, json_claim.facilityId) is not granted
403MODE_MISMATCHOffice binding does not accept the draft mode
403BILLING_IDENTITY_MISMATCH2010AA NPI or TIN is not the office's
409DRAFT_ALREADY_SUBMITTEDDraft is SUBMITTED
409DRAFT_NOT_OPENDraft is REJECTED
409DRAFT_VERSION_CONFLICTversion not current, or the row changed during the call
409PAYER_NOT_ENROLLEDPayer not live for the office
413PAYLOAD_TOO_LARGEStored X12 over 6 MB
422CLAIM_SOURCE_UNREADABLEStored X12 missing or unparseable, or stored JSON no longer validates
422ATTACHMENT_REFERENCE_AMBIGUOUSJSON draft names more than one packet
502CLAIM_BUILD_INVALIDJSON draft: builder output unparseable
503CLAIM_BUILDER_UNAVAILABLEJSON draft: builder not configured
422INVALID_REQUESTBody or parameters do not fit the schema
429TOO_MANY_REQUESTSRate bucket empty or too many requests in flight
429QUOTA_EXCEEDEDSeller's daily claim quota used up
503DRAFTS_UNAVAILABLEDrafts are not configured on this gateway
ImportantSubmit once. A second submit of a SUBMITTED draft is 409 DRAFT_ALREADY_SUBMITTED, not a replay. After a lost response, GET the draft, read submission_id, then GET /v1/submissions/{submission_id}.
ImportantIf submit answers 404 DRAFT_NOT_FOUND for a draft you just built, the claim may already be on the ledger. Check GET /v1/claims before doing anything else.
InfoA draft that lands HELD at submit keeps its earlier ready verdict, so it can be submitted again. With no header or the same header that replays the stored HOLD receipt and changes nothing; with a different header it fails with 409 DUPLICATE_CONTENT. Correct a held draft by editing and rebuilding, or create a new draft.
InfoThe OpenAPI document declares 200 for this route; the gateway answers 202.
API reference

List claim drafts

GEThttps://api.claimhouse.ai/v1/claims/drafts

Page your drafts for the key's mode, newest first.

Use it to find drafts a person started and did not finish. Only drafts in offices your key is granted, in your key's mode, are returned.

Permission
read
Idempotency
none
Side effects
Writes one read audit row listing the returned draft ids. Never contacts the clearinghouse.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
facility_idstringOnly drafts for this office. Must be granted to your key. At most 64 characters.
e.g. fac_tycrfy0cs0qc7sq95eme
statestringOnly drafts in this state. REJECTED is refused. At most 16 characters.
one of: OPEN · READY · HELD · SUBMITTED
e.g. HELD
cursorstringThe next_cursor from the previous page. At most 200 characters. A cursor that does not parse returns the first page.
limitintegerPage size, 1 to 200. Defaults to 50.
e.g. 25

Request example

null

Response fields

NameTypeDescription
drafts*arrayThe drafts on this page, each a full draft object.
draft_id*stringThe draft id, drf_....
e.g. drf_EXAMPLE0000000000001
facility_id*stringThe office the draft belongs to. Controls who can see the draft.
e.g. fac_EXAMPLE0000000000001
source_kind*stringWhat the draft was created from. Fixed at create.
one of: x12 · json · ada · blank
door*stringThe intake door the draft builds and submits through, derived from source_kind: x12 uses raw_x12, json uses json, ada and blank use ada_form.
one of: raw_x12 · json · ada_form
state*stringWhere the draft is in its lifecycle.
one of: OPEN · READY · HELD · SUBMITTED · REJECTED
version*integerOptimistic lock. Every successful edit, build, and submit increments it.
e.g. 1
saved_at*datetimeISO 8601 time of the last change.
expires_at*datetimeISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today.
tenant_claim_idstringYour id for the claim. Null when not set.
fields*objectADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts.
lines*arrayADA service lines (array of objects) for ada and blank drafts. An empty array otherwise.
payerobjectADA payer block for ada and blank drafts. Null otherwise.
findings*arrayFindings from the last build. Empty before the first build.
severity*stringFinding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.
one of: HOLD · error · warning · INFO
code*stringStable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING.
message*stringHuman-readable reason. Never contains a claim value.
itemstringADA item number when known. Null for intake findings today.
pathstringMachine location of the finding, for example transaction[0]/claim[0]. Null when unknown.
verdict*objectThe send decision from the last build. An empty object before the first build.
readybooleantrue only when the last build found no error and no hold. Use this for the send decision.
holdsarrayArray of strings. Codes of findings with severity HOLD. Can be empty while ready is false.
warningsarrayArray of strings. Codes of findings with severity WARNING. Empty for real intake warnings today.
built_atdatetimeISO 8601 time of the last build. Null before the first build.
build_stale*booleantrue until the first build, and again after any edit. Submit requires false.
attachmentobjectThe linked attachment packet. Null when no packet is linked.
packet_id*stringThe pkt_ id used for packet parity.
e.g. pkt_EXAMPLE0000000000001
submission_idstringThe submission created by submit. Null until submit.
submitted_atdatetimeISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit.
next_cursorstringOpaque cursor for the next page. Do not parse it. Null on the last page.
has_more*booleantrue when another page may exist. Keep following next_cursor until it is false.

Responses

200OK
{
  "drafts": [
    {
      "draft_id": "drf_s92g3d40sc8ntw646v2d",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "source_kind": "x12",
      "door": "raw_x12",
      "state": "HELD",
      "version": 2,
      "saved_at": "2026-09-05T14:30:00+00:00",
      "expires_at": "2026-10-05T14:30:00+00:00",
      "tenant_claim_id": "your-claim-0002",
      "fields": {},
      "lines": [],
      "payer": null,
      "findings": [
        {
          "severity": "error",
          "code": "PACKET_PARITY",
          "message": "the PWK attachment reference in the file is not the reference the named attachment packet recorded; the claim is held",
          "item": null,
          "path": "interchange"
        }
      ],
      "verdict": {
        "ready": false,
        "holds": [],
        "warnings": []
      },
      "built_at": "2026-09-05T14:30:00+00:00",
      "build_stale": false,
      "attachment": {
        "packet_id": "pkt_2g7hcpgd99p0p0hnhnaa"
      },
      "submission_id": null,
      "submitted_at": null
    }
  ],
  "next_cursor": null,
  "has_more": false
}
400Unknown state
{
  "error": "DRAFT_DENIED",
  "message": "draft denied",
  "errors": [
    {
      "reason": "unknown draft state 'NOPE'"
    }
  ],
  "request_id": "evt_7a3tc6rvn16mv8cg7jaj"
}
403Office not accessible
{
  "error": "DRAFT_ACCESS_DENIED",
  "message": "draft access denied",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown, or revoked key
400DRAFT_DENIEDUnknown state (including REJECTED), or a continuation cursor could not be produced
403DRAFT_ACCESS_DENIEDfacility_id is not active, not granted, or bound to the other mode
403PERMISSION_DENIEDKey lacks read
422INVALID_REQUESTlimit out of range or a parameter too long
429TOO_MANY_REQUESTSRate bucket empty or too many requests in flight
503DRAFTS_UNAVAILABLEDrafts are not configured on this gateway
InfoMode and grant filtering happens after the page is read, so a page can hold fewer than limit drafts, or none, while has_more is true.
InfoThe response has no request_id in the body. Read the X-Request-Id header.
API reference

Delete a claim draft

DELETEhttps://api.claimhouse.ai/v1/claims/drafts/{draft_id}

Permanently delete a draft that was not consumed by a queued submission.

Hard deletes the draft, including its stored source. Use it to discard working state that should not be kept. OPEN, READY, and HELD drafts can be deleted.

Permission
submit
Idempotency
none
Side effects
Hard deletes the draft and writes an audit row. A submission already created from a HELD draft stays on the ledger. Never contacts the clearinghouse.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
draft_id*stringThe draft to act on, a drf_ id. 1 to 64 characters.
e.g. drf_eh932r27rd5ybjdx85pc

Request example

null

Responses

204No Content
null
409Already submitted
{
  "error": "DRAFT_ALREADY_SUBMITTED",
  "message": "draft already submitted",
  "errors": [
    {
      "draft_id": "drf_k7n9zma3cjmbfv81h7mq"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
404Draft not found
{
  "error": "DRAFT_NOT_FOUND",
  "message": "draft not found",
  "errors": [
    {
      "draft_id": "drf_mxc3nyetdazwd7w4ffgs"
    }
  ],
  "request_id": "evt_xq3wg4817k1s1qxzhc0n"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown, or revoked key
403PERMISSION_DENIEDKey lacks submit
404DRAFT_NOT_FOUNDNo such draft visible to your key, already deleted, or the draft is REJECTED
409DRAFT_ALREADY_SUBMITTEDDraft is SUBMITTED
429TOO_MANY_REQUESTSRate bucket empty or too many requests in flight
503DRAFTS_UNAVAILABLEDrafts are not configured on this gateway
InfoA second delete of the same draft answers 404 DRAFT_NOT_FOUND. REJECTED drafts cannot be deleted and also answer 404.
API reference

Read draft history

GEThttps://api.claimhouse.ai/v1/claims/drafts/{draft_id}/history

Read the captured version history of one claim draft.

Every accepted edit captures an immutable version. This route lists those versions newest first, with ?version=N pinning a single version and returning its full retained snapshot.

Reads always use your current grants, never historical grants. Listing returns version metadata; only a pinned version returns the snapshot body. Idempotency keys are stripped from snapshots.

Permission
read
Idempotency
none
Side effects
None. Read only.
In the dashboard
Claim workspace > draft > History

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
draft_id*stringThe draft id, drf_....
e.g. drf_EXAMPLE0000000000000001

Query parameters

NameTypeDescription
versionintegerPin one version. Returns its full snapshot body instead of the metadata list.
e.g. 3
before_versionintegerCursor: only versions below this number. Mutually exclusive with version.
e.g. 5
limitinteger1–100, default 50. Ignored when version is set.
e.g. 50

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
draft_id*stringThe draft id.
e.g. drf_EXAMPLE0000000000000001
current_version*integerThe draft's latest version.
e.g. 4
coverage*stringAlways captured_versions_only — history begins at the first captured version, not necessarily creation.
e.g. captured_versions_only
representation*stringmetadata for a list page, snapshot when version is pinned.
one of: metadata · snapshot
e.g. metadata
versions*arrayVersion rows, newest first.
version*integerVersion number.
e.g. 4
state*stringDraft state at that version.
e.g. EDITABLE
capture_kind*stringWhat triggered the capture.
e.g. edit
recorded_at*datetimeWhen this version was captured.
e.g. 2026-09-05T14:30:00+00:00
snapshot*objectVersion identity fields on list pages; the full retained snapshot when version is pinned.
e.g. {"draft_id":"drf_EXAMPLE0000000000000001","version":4,"source_kind":"json"}
has_more*booleanTrue when more versions exist below this page.
e.g. false
next_before_versionintegerPass as before_version for the next page. Null when has_more is false.
e.g. 3

Responses

200OK
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "draft_id": "drf_35ka7045j25e5w5h2a32",
  "current_version": 4,
  "coverage": "captured_versions_only",
  "representation": "metadata",
  "versions": [
    {
      "version": 4,
      "state": "EDITABLE",
      "capture_kind": "edit",
      "recorded_at": "2026-09-05T14:30:00+00:00",
      "snapshot": {
        "draft_id": "drf_35ka7045j25e5w5h2a32",
        "version": 4,
        "source_kind": "json"
      }
    }
  ],
  "has_more": false,
  "next_before_version": null
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks read or the draft's office
404NOT_FOUNDno such draft or pinned version in your seller and grants
422INVALID_REQUESTlimit out of range, version and before_version combined, or an invalid cursor
503HISTORY_UNAVAILABLEretained history is not configured on this deployment
429TOO_MANY_REQUESTSrate or concurrency limit reached
Infohas_more plus next_before_version is the forward pagination contract; keep passing before_version until has_more is false.
API reference

Attachments

Upload claim documents, send them once to the payer's attachment door, and get back the attachment number your claim must carry.

Packet APIAttachment workspace (drafts)
RoutesPOST /v1/attachments, GET /v1/attachments/packets/{packet_id}, POST /v1/attachments/packets/{packet_id}/send/v1/attachment-drafts routes plus /v1/facilities/{facility_id}/attachment-providers. Still uses POST /v1/attachments to store the files.
Claim contextYou send it in claim_context on the send call. Claim House does not store it.The draft stores it, versions every change, and freezes a reviewed snapshot before the send.
ProvidersFree text in claim_context. Doctor tax id defaults to "0".Chosen from a versioned provider roster. Billing must match the office NPI and TIN. Treating must be confirmed and carry a 9 digit tax id.
Doorauto (default) or a door you name.Always auto.
Checks before the sendDoor file limits and payer documentation rules.All packet checks plus version checks, a document manifest hash, provider eligibility, office and payer snapshots, and an explicit validate step.
Key modeNot checked. A test key can reach a production office.Must match the office (test key for a sandbox office, production key for a production office).
Best forSystems that already hold a verified claim and want the fewest calls.Integrations that want a reviewed, versioned, auditable record, or mirror the dashboard's Attachments screen.
DoorWhat it isBuilt todayReference returned
network_AThe attachment network (NEA)Yes, the only working doornea_number
network_BA second attachment networkNo, send answers 501 DOOR_NOT_BUILTnone
x12_275X12 275 attachment transactionNonone
portalPayer portal uploadNonone
paperMailNonone
RuleAt upload (POST /v1/attachments)At send through the NEA door
FormatJSON body, one files[] item per file, bytes as strict base64 in content_base64. No multipart.Same packet, no re-upload.
Media typesimage/jpeg, image/png, image/tiff, image/gif, application/pdf, text/plainimage/jpeg only (DOOR_MEDIA_TYPE_NOT_ACCEPTED)
Files1 to 200 per packetAt most 127 (DOOR_FILE_COUNT_EXCEEDED)
Bytes per file1 byte to 50 MB decoded (FILE_EMPTY, FILE_TOO_LARGE)15 MB (DOOR_FILE_SIZE_EXCEEDED)
Bytes totalNo packet total15 MB for the whole packet (DOOR_TOTAL_SIZE_EXCEEDED)
Kindsradiograph, periodontal_chart, narrative, photo, eob, otherMust satisfy the payer's documentation rule (DOCUMENTATION_RULE_UNMET)
ImportantOne attempt per packet. A packet moves OPEN to SENDING and ends SENT, FAILED or AMBIGUOUS, and is never sent again. A send that reaches a door answers HTTP 200 whatever happened: read outcome and error_class. After FAILED, create a new packet (identical bytes deduplicate). After AMBIGUOUS, do not resend or build a new packet for the same claim; Claim House reconciles it.
  • A SENT packet carries reference_kind: "nea_number" and reference, the attachment number.
  • JSON claims (POST /v1/dental-claims/submission and claim drafts): put the packet id in claimInformation.claimSupplementalInformation.reportInformation.attachmentId, or the number in attachmentControlNumber if you already hold it, never both.
  • The 837D gets PWK*<report type>*<transmission code>***AC*NEA<number> in Loop 2300 and NTE*ADD*NEA#<number> for any number no claim note already carries.
  • A packet that is not SENT does not resolve and the claim is held with ATTACHMENT_UNRESOLVED. A claim may name at most one packet (422 ATTACHMENT_REFERENCE_AMBIGUOUS).
  • Raw X12 (POST /v1/submissions): write PWK and NTE yourself. attachments must equal the PWK NEA set (PWK_PARITY), and attachment_packet_id must be a SENT packet of the same office whose number is the only one in the PWK segments (PACKET_PARITY).
API reference

List attachment providers

GEThttps://api.claimhouse.ai/v1/facilities/{facility_id}/attachment-providers

Returns the office's provider roster for the attachment workspace.

Returns the latest version of each provider and role, including observed and inactive profiles. Use it to pick the billing and treating providers for a draft.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Attachments > New attachment (provider pickers); Settings > Organization > Providers

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
facility_id*stringThe office whose roster to return. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
providers*arrayLatest version per provider and role.
provider_id*stringProvider id, prv_.... Shared by both roles of one NPI in one office.
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the profile.
one of: test · production
role*stringRole this version describes.
one of: billing · treating
version*integerVersion of this role. Send it back as expected_version when changing the profile, and as billing_provider_version or treating_provider_version on a draft.
status*stringOnly confirmed profiles can be selected on a draft.
one of: observed · confirmed · inactive
identity*objectProvider identity.
npi*stringProvider NPI, 10 digits.
first_name*stringFirst name. Empty string when not given.
last_name*stringLast name. Empty string when not given.
organization_name*stringOrganization name. Empty string when not given.
tax_idstring9 digit tax id. Null when not given.
license_numberstringLicense number. Null when not given.
license_statestringTwo letter license state. Null when not given.
provenance*objectWho supplied and who confirmed this version.
kind*stringWhere the profile came from. Always customer_api today.
one of: customer_api
reference*stringThe source_reference you sent when saving.
seller_id*stringYour seller.
facility_id*stringThe office.
observed_at*datetimeWhen this version was saved.
confirmed_bystringKey id or session that confirmed the profile. Null unless status is confirmed.
confirmed_atdatetimeWhen the profile was confirmed. Null unless status is confirmed.
active_fromdateFirst effective date. Null when open ended.
active_untildateLast effective date. Null when open ended.
actor_id*stringKey id or session principal that saved this version.
created_at*datetimeWhen this version was written.

Responses

200OK
{
  "request_id": "evt_5rq822rgmsn8x57m7jya",
  "providers": [
    {
      "provider_id": "prv_87bbcpssc5ennege20fn",
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "mode": "test",
      "role": "treating",
      "version": 2,
      "status": "confirmed",
      "identity": {
        "npi": "1234567893",
        "first_name": "Sample",
        "last_name": "Provider",
        "organization_name": "",
        "tax_id": "000000000",
        "license_number": null,
        "license_state": null
      },
      "provenance": {
        "kind": "customer_api",
        "reference": "roster-import-example",
        "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
        "facility_id": "fac_tycrfy0cs0qc7sq95eme",
        "observed_at": "2026-09-14T15:00:00+00:00",
        "confirmed_by": "key_35164bqm0bd5ppz3jm5j",
        "confirmed_at": "2026-09-14T15:00:00+00:00"
      },
      "active_from": null,
      "active_until": null,
      "actor_id": "key_35164bqm0bd5ppz3jm5j",
      "created_at": "2026-09-14T15:00:00+00:00"
    }
  ]
}
403Mode mismatch
{
  "error": "MODE_MISMATCH",
  "message": "Office is unavailable in this environment.",
  "errors": [],
  "request_id": "evt_56mpph3tthwh06rf8vjw"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey has no access to the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office, or not granted
422WORKSPACE_INVALIDScope values could not be verified
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
InfoWorkspace routes check key mode: a sandbox office needs a test key and a production office needs a production key. The office must be ACTIVE.
API reference

Save an attachment provider

POSThttps://api.claimhouse.ai/v1/facilities/{facility_id}/attachment-providers

Adds, confirms, changes or retires a provider profile by writing a new version.

Every save appends a version for the NPI and role; nothing is overwritten. A key with submit can add a new observed profile.

Confirming, retiring, or changing an existing profile needs admin as well. To confirm, an admin key posts the same identity with provider_id, expected_version set to the current version, and status: "confirmed".

Permission
submit
Idempotency
none
Side effects
Creates the office provider row for the NPI when missing and inserts a new version. No vendor call.
In the dashboard
Settings > Organization > Providers; Attachments > New attachment (Confirm for reuse)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
facility_id*stringThe office the provider works for. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme

Request body

NameTypeDescription
provider_idstringExisting prv_... to change. Must belong to the same NPI. Setting it needs admin.
expected_versionintegerCurrent latest version of this NPI and role, or 0 for a new role (the default). Above 0 needs admin.
e.g. 0
role*stringThe role this version describes.
one of: billing · treating
e.g. treating
statusstringDefaults to observed. confirmed and inactive need admin.
one of: observed · confirmed · inactive
identity*objectProvider identity. Unknown fields are refused.
npi*stringExactly 10 digits. The check digit is verified later, at draft validate.
e.g. 1234567893
first_namestringAt most 60 characters. Required for a treating provider at validate.
last_namestringAt most 60 characters. Required for a treating provider at validate.
organization_namestringAt most 120 characters. A billing provider needs this or a first and last name.
tax_idstringExactly 9 digits, or null. An empty string is refused. Billing must equal the office TIN. A treating provider needs a real (non placeholder) tax id to send.
license_numberstringAt most 30 characters, or null.
license_statestringAt most 2 characters, or null.
source_reference*stringWhere the identity came from, 1 to 200 characters. Stored as provenance.reference.
e.g. roster-import-example
active_fromdateFirst effective date, YYYY-MM-DD, or null.
active_untildateLast effective date, YYYY-MM-DD, or null.

Request example

{
  "role": "treating",
  "status": "observed",
  "identity": {
    "npi": "1234567893",
    "first_name": "Sample",
    "last_name": "Provider",
    "tax_id": null
  },
  "source_reference": "roster-import-example"
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
provider*objectThe new version. When status is confirmed, provenance.confirmed_by is the saving key and confirmed_at is now.
provider_id*stringProvider id, prv_.... Shared by both roles of one NPI in one office.
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the profile.
one of: test · production
role*stringRole this version describes.
one of: billing · treating
version*integerVersion of this role. Send it back as expected_version when changing the profile, and as billing_provider_version or treating_provider_version on a draft.
status*stringOnly confirmed profiles can be selected on a draft.
one of: observed · confirmed · inactive
identity*objectProvider identity.
npi*stringProvider NPI, 10 digits.
first_name*stringFirst name. Empty string when not given.
last_name*stringLast name. Empty string when not given.
organization_name*stringOrganization name. Empty string when not given.
tax_idstring9 digit tax id. Null when not given.
license_numberstringLicense number. Null when not given.
license_statestringTwo letter license state. Null when not given.
provenance*objectWho supplied and who confirmed this version.
kind*stringWhere the profile came from. Always customer_api today.
one of: customer_api
reference*stringThe source_reference you sent when saving.
seller_id*stringYour seller.
facility_id*stringThe office.
observed_at*datetimeWhen this version was saved.
confirmed_bystringKey id or session that confirmed the profile. Null unless status is confirmed.
confirmed_atdatetimeWhen the profile was confirmed. Null unless status is confirmed.
active_fromdateFirst effective date. Null when open ended.
active_untildateLast effective date. Null when open ended.
actor_id*stringKey id or session principal that saved this version.
created_at*datetimeWhen this version was written.

Responses

200OK
{
  "request_id": "evt_jvagd3ctrgrwn3hxz8ww",
  "provider": {
    "provider_id": "prv_87bbcpssc5ennege20fn",
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "facility_id": "fac_tycrfy0cs0qc7sq95eme",
    "mode": "test",
    "role": "treating",
    "version": 1,
    "status": "observed",
    "identity": {
      "npi": "1234567893",
      "first_name": "Sample",
      "last_name": "Provider",
      "organization_name": "",
      "tax_id": null,
      "license_number": null,
      "license_state": null
    },
    "provenance": {
      "kind": "customer_api",
      "reference": "roster-import-example",
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "observed_at": "2026-09-14T15:00:00+00:00",
      "confirmed_by": null,
      "confirmed_at": null
    },
    "active_from": null,
    "active_until": null,
    "actor_id": "key_y7e70t7hxfdnz6yrwaxr",
    "created_at": "2026-09-14T15:00:00+00:00"
  }
}
403Admin required
{
  "error": "PROVIDER_ADMIN_REQUIRED",
  "message": "An organization administrator must confirm or retire provider profiles.",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
422Billing identity mismatch
{
  "error": "BILLING_IDENTITY_MISMATCH",
  "message": "Billing identity must match the registered office.",
  "errors": [],
  "request_id": "evt_6q83h5553apd50cac4e4"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403PROVIDER_ADMIN_REQUIREDConfirming, retiring or changing a profile without admin
403FACILITY_NOT_GRANTEDKey grants do not cover the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office in scope
409WORKSPACE_VERSION_CONFLICTexpected_version is not the latest, or provider_id has a different NPI
422BILLING_IDENTITY_MISMATCHBilling NPI or tax id differs from the office
422WORKSPACE_INVALIDBad NPI shape or credential named fields in identity
422INVALID_REQUESTSchema failure, including a tax id that is not 9 digits
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
InfoReturns 200, not 201.
InfoThe example omits a tax id. 000000000 is refused as a placeholder, and draft validate needs a real 9 digit treating tax id.
ImportantAn invalid active_from or active_until date currently returns an unhandled 500 instead of a 422.
API reference

Create an attachment draft

POSThttps://api.claimhouse.ai/v1/attachment-drafts

Starts a workspace draft for one office at version 1 in state DRAFT.

Stores optional claim context, provider selections and a linked packet. The create call accepts any context object; completeness is checked at validate.

Permission
submit
Idempotency
none
Side effects
Writes a draft at version 1. When packet_id is given, reads the packet and checks its file manifest. No vendor call.
In the dashboard
Attachments > New attachment (Save context)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Request body

NameTypeDescription
facility_id*stringThe office, 1 to 64 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
packet_idstringA pkt_... from POST /v1/attachments in the same office, or null.
contextobjectClaim context, at most 131,072 bytes encoded. No key named password, secret, token, authorization, api_key or signed_url at any depth.
payer_idstringPayer primary id, exact. Required at validate. The payer must have attachment routing and an attachment network (NEA) payer id on file.
patientobjectThe patient. Required at validate.
first_name*stringPatient first name.
last_name*stringPatient last name.
date_of_birth*datePatient date of birth, YYYY-MM-DD.
subscriberobjectThe subscriber. Required at validate.
person*objectSubscriber identity.
first_name*stringSubscriber first name.
last_name*stringSubscriber last name.
date_of_birthdateSubscriber date of birth. Null when unknown.
member_id*stringSubscriber member id.
group_numberstringGroup number. Null when none.
relationshipstringPatient's relationship to the subscriber. Required at validate.
one of: self · spouse · child · other
patient_is_subscriberbooleanMust be true exactly when relationship is self. With self, patient name and date of birth must match the subscriber.
service_date_startdateFirst date of service. Required at send, even for predeterminations.
service_date_enddateLast date of service. Must not be before service_date_start.
predeterminationbooleanTrue for a predetermination. Defaults to false.
external_claim_referencestringYour claim id. Sent as tenant_claim_id so events land on the matching claim. Null when none.
proceduresarray1 to 50 procedures. Required at validate.
cdt_code*stringCDT code, 2 to 8 characters.
procedure_date*dateProcedure date inside the service date range.
teetharrayTeeth treated.
tooth_number*stringTooth number, 1 to 4 characters.
surfacesstringSurface letters from BDFILMO.
quadrantstringQuadrant. Null when none.
one of: UL · UR · LL · LR · UA · LA · FM
billing_provider_idstringA prv_... with a billing role, or null.
billing_provider_versionintegerBilling provider version, at least 1. Must equal the latest version at validate.
treating_provider_idstringA prv_... with a treating role, or null.
treating_provider_versionintegerTreating provider version, at least 1. Must equal the latest version at validate.

Request example

{
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "billing_provider_id": "prv_xxnj1rkzpd850smz5sqg",
  "billing_provider_version": 2,
  "treating_provider_id": "prv_87bbcpssc5ennege20fn",
  "treating_provider_version": 2,
  "context": {
    "payer_id": "EXMPL",
    "patient": {
      "first_name": "Sample",
      "last_name": "Patient",
      "date_of_birth": "2016-09-01"
    },
    "subscriber": {
      "person": {
        "first_name": "Sample",
        "last_name": "Subscriber",
        "date_of_birth": "1986-09-01"
      },
      "member_id": "SYN000123456",
      "group_number": null
    },
    "relationship": "child",
    "patient_is_subscriber": false,
    "service_date_start": "2026-09-01",
    "service_date_end": "2026-09-01",
    "predetermination": false,
    "external_claim_reference": "clm_b66x8fzgq4nxwk96t54c",
    "procedures": [
      {
        "cdt_code": "D4341",
        "procedure_date": "2026-09-01",
        "teeth": [],
        "quadrant": "UR"
      }
    ]
  }
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
draft*objectThe full draft. Contains PHI.
draft_id*stringThe draft, awd_....
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the draft.
one of: test · production
version*integerCurrent version. Every change adds 1.
state*stringDRAFT is editable, READY is validated at this version, FROZEN means a send was attempted or is in progress and no more edits are allowed.
one of: DRAFT · READY · FROZEN
created_at*datetimeWhen the draft was created.
updated_at*datetimeWhen the draft last changed.
context*objectClaim context as last saved. Contains PHI.
billing_provider_idstringSelected billing provider. Null when none.
billing_provider_versionintegerSelected billing provider version. Null when none.
treating_provider_idstringSelected treating provider. Null when none.
treating_provider_versionintegerSelected treating provider version. Null when none.
packet_idstringLinked packet. Null when none.
provider_snapshots*objectEmpty until validate. Then billing and treating, each the provider version row that was reviewed.
context_snapshot*objectEmpty until validate. Then context (normalized), billing_provider, treating_provider, confirmed_by, confirmed_at, fingerprint (SHA-256) and authority_snapshot (office, payer, routing and door choice at validate).

Responses

201Created
{
  "request_id": "evt_kyqtw98tx29w1ety2nj0",
  "draft": {
    "draft_id": "awd_8f2nd1jewe9sstsgt4ez",
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "facility_id": "fac_tycrfy0cs0qc7sq95eme",
    "mode": "test",
    "version": 1,
    "state": "DRAFT",
    "created_at": "2026-09-14T15:02:00+00:00",
    "updated_at": "2026-09-14T15:02:00+00:00",
    "context": {
      "payer_id": "EXMPL",
      "patient": {
        "first_name": "Sample",
        "last_name": "Patient",
        "date_of_birth": "2016-09-01"
      },
      "subscriber": {
        "person": {
          "first_name": "Sample",
          "last_name": "Subscriber",
          "date_of_birth": "1986-09-01"
        },
        "member_id": "SYN000123456",
        "group_number": null
      },
      "relationship": "child",
      "patient_is_subscriber": false,
      "service_date_start": "2026-09-01",
      "service_date_end": "2026-09-01",
      "predetermination": false,
      "external_claim_reference": "clm_b66x8fzgq4nxwk96t54c",
      "procedures": [
        {
          "cdt_code": "D4341",
          "procedure_date": "2026-09-01",
          "teeth": [],
          "quadrant": "UR"
        }
      ]
    },
    "billing_provider_id": "prv_xxnj1rkzpd850smz5sqg",
    "billing_provider_version": 2,
    "treating_provider_id": "prv_87bbcpssc5ennege20fn",
    "treating_provider_version": 2,
    "packet_id": null,
    "provider_snapshots": {},
    "context_snapshot": {}
  }
}
422Workspace invalid
{
  "error": "WORKSPACE_INVALID",
  "message": "Workspace values could not be verified.",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office, or packet_id not in this office
422PACKET_MANIFEST_INVALIDLinked packet has no files or files outside this office
422WORKSPACE_INVALIDContext not an object, too large, or has a credential named key
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENTS_UNAVAILABLEpacket_id given and attachment service not wired
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
ImportantThe draft body contains PHI by design (patient and subscriber context). Responses carry Cache-Control: private, no-store.
InfoWorkspace routes check key mode: a sandbox office needs a test key and a production office needs a production key. The office must be ACTIVE.
InfoGateway owned keys inside context (seller_id, facility_id, packet_version, billing_provider_id, treating_provider_id, document_manifest_sha256, office_profile_version, payer_mapping_version, provenance) are overwritten at validate.
ImportantA provider id or packet id that does not exist in the office currently returns an unhandled 500 instead of a 4xx.
ImportantAt the NEA door, any relationship other than self, spouse or child (including other) is sent as self. Do not send dependent claims with relationship other until this is fixed.
API reference

Retrieve an attachment draft

GEThttps://api.claimhouse.ai/v1/attachment-drafts/{draft_id}

Returns one draft with its context, provider selections, linked packet and review snapshot.

The snapshots are empty until the draft is validated. Read state and version before updating, validating or sending.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Attachments > All attachments (draft row); Attachments > New attachment

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
draft_id*stringThe attachment draft id, awd_....
e.g. awd_8f2nd1jewe9sstsgt4ez

Query parameters

NameTypeDescription
facility_id*stringThe office the draft belongs to. 1 to 64 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
draft*objectThe draft. Contains PHI.
draft_id*stringThe draft, awd_....
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the draft.
one of: test · production
version*integerCurrent version. Every change adds 1.
state*stringDRAFT is editable, READY is validated at this version, FROZEN means a send was attempted or is in progress and no more edits are allowed.
one of: DRAFT · READY · FROZEN
created_at*datetimeWhen the draft was created.
updated_at*datetimeWhen the draft last changed.
context*objectClaim context as last saved. Contains PHI.
billing_provider_idstringSelected billing provider. Null when none.
billing_provider_versionintegerSelected billing provider version. Null when none.
treating_provider_idstringSelected treating provider. Null when none.
treating_provider_versionintegerSelected treating provider version. Null when none.
packet_idstringLinked packet. Null when none.
provider_snapshots*objectEmpty until validate. Then billing and treating, each the provider version row that was reviewed.
context_snapshot*objectEmpty until validate. Then context (normalized), billing_provider, treating_provider, confirmed_by, confirmed_at, fingerprint (SHA-256) and authority_snapshot (office, payer, routing and door choice at validate).

Responses

200OK
{
  "request_id": "evt_mjj9q2zgdzbt9ndebc50",
  "draft": {
    "draft_id": "awd_8f2nd1jewe9sstsgt4ez",
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "facility_id": "fac_tycrfy0cs0qc7sq95eme",
    "mode": "test",
    "version": 3,
    "state": "READY",
    "created_at": "2026-09-14T15:02:00+00:00",
    "updated_at": "2026-09-14T15:06:00+00:00",
    "context": {
      "payer_id": "EXMPL",
      "patient": {
        "first_name": "Sample",
        "last_name": "Patient",
        "date_of_birth": "2016-09-01"
      },
      "subscriber": {
        "person": {
          "first_name": "Sample",
          "last_name": "Subscriber",
          "date_of_birth": "1986-09-01"
        },
        "member_id": "SYN000123456",
        "group_number": null
      },
      "relationship": "child",
      "patient_is_subscriber": false,
      "service_date_start": "2026-09-01",
      "service_date_end": "2026-09-01",
      "predetermination": false,
      "external_claim_reference": "clm_b66x8fzgq4nxwk96t54c",
      "procedures": [
        {
          "cdt_code": "D4341",
          "procedure_date": "2026-09-01",
          "teeth": [],
          "quadrant": "UR"
        }
      ]
    },
    "billing_provider_id": "prv_xxnj1rkzpd850smz5sqg",
    "billing_provider_version": 2,
    "treating_provider_id": "prv_87bbcpssc5ennege20fn",
    "treating_provider_version": 2,
    "packet_id": "pkt_20fng953rtp1hwc3zdcn",
    "provider_snapshots": {
      "billing": {
        "provider_id": "prv_xxnj1rkzpd850smz5sqg",
        "role": "billing",
        "version": 2,
        "status": "confirmed"
      },
      "treating": {
        "provider_id": "prv_87bbcpssc5ennege20fn",
        "role": "treating",
        "version": 2,
        "status": "confirmed"
      }
    },
    "context_snapshot": {
      "context": {
        "payer_id": "EXMPL",
        "relationship": "child"
      },
      "confirmed_by": "key_y7e70t7hxfdnz6yrwaxr",
      "confirmed_at": "2026-09-14T15:06:00+00:00",
      "fingerprint": "bbee49f08c6ff3dfe313ffd4828d71597cdb2923ad48111983c1e7277198c0e5",
      "authority_snapshot": {
        "door": "network_A"
      }
    }
  }
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "No such workspace resource.",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office, or no such draft in this office and mode
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
ImportantThe draft body contains PHI by design (patient and subscriber context). Responses carry Cache-Control: private, no-store.
API reference

Update an attachment draft

PATCHhttps://api.claimhouse.ai/v1/attachment-drafts/{draft_id}

Changes a draft's context, provider selections or linked packet.

Only fields you send change; the rest carry forward. context replaces the whole context object.

Any change returns the draft to DRAFT at the next version, clears its review snapshot, and requires a new validate.

Permission
submit
Idempotency
none
Side effects
Writes a new draft version. When packet_id is given, reads the packet and checks its manifest. No vendor call.
In the dashboard
Attachments > New attachment (Save context, Store documents)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
draft_id*stringThe attachment draft id, awd_....
e.g. awd_8f2nd1jewe9sstsgt4ez

Query parameters

NameTypeDescription
facility_id*stringThe office the draft belongs to. 1 to 64 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme

Request body

NameTypeDescription
expected_version*integerThe draft's current version, at least 1. Acts as an optimistic lock.
e.g. 1
packet_idstringLink a packet from the same office, or null.
contextobjectReplaces the whole context object. Same limits and shape as on create. Null leaves it unchanged.
billing_provider_idstringA prv_... with a billing role, or null.
billing_provider_versionintegerBilling provider version, at least 1.
treating_provider_idstringA prv_... with a treating role, or null.
treating_provider_versionintegerTreating provider version, at least 1.

Request example

{
  "expected_version": 1,
  "packet_id": "pkt_20fng953rtp1hwc3zdcn"
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
draft*objectThe updated draft: version increased by 1, state DRAFT, empty snapshots.
draft_id*stringThe draft, awd_....
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the draft.
one of: test · production
version*integerCurrent version. Every change adds 1.
state*stringDRAFT is editable, READY is validated at this version, FROZEN means a send was attempted or is in progress and no more edits are allowed.
one of: DRAFT · READY · FROZEN
created_at*datetimeWhen the draft was created.
updated_at*datetimeWhen the draft last changed.
context*objectClaim context as last saved. Contains PHI.
billing_provider_idstringSelected billing provider. Null when none.
billing_provider_versionintegerSelected billing provider version. Null when none.
treating_provider_idstringSelected treating provider. Null when none.
treating_provider_versionintegerSelected treating provider version. Null when none.
packet_idstringLinked packet. Null when none.
provider_snapshots*objectEmpty until validate. Then billing and treating, each the provider version row that was reviewed.
context_snapshot*objectEmpty until validate. Then context (normalized), billing_provider, treating_provider, confirmed_by, confirmed_at, fingerprint (SHA-256) and authority_snapshot (office, payer, routing and door choice at validate).

Responses

200OK
{
  "request_id": "evt_bkfrar1bddj3efgmw6s0",
  "draft": {
    "draft_id": "awd_8f2nd1jewe9sstsgt4ez",
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "facility_id": "fac_tycrfy0cs0qc7sq95eme",
    "mode": "test",
    "version": 2,
    "state": "DRAFT",
    "created_at": "2026-09-14T15:02:00+00:00",
    "updated_at": "2026-09-14T15:06:00+00:00",
    "context": {
      "payer_id": "EXMPL",
      "patient": {
        "first_name": "Sample",
        "last_name": "Patient",
        "date_of_birth": "2016-09-01"
      },
      "subscriber": {
        "person": {
          "first_name": "Sample",
          "last_name": "Subscriber",
          "date_of_birth": "1986-09-01"
        },
        "member_id": "SYN000123456",
        "group_number": null
      },
      "relationship": "child",
      "patient_is_subscriber": false,
      "service_date_start": "2026-09-01",
      "service_date_end": "2026-09-01",
      "predetermination": false,
      "external_claim_reference": "clm_b66x8fzgq4nxwk96t54c",
      "procedures": [
        {
          "cdt_code": "D4341",
          "procedure_date": "2026-09-01",
          "teeth": [],
          "quadrant": "UR"
        }
      ]
    },
    "billing_provider_id": "prv_xxnj1rkzpd850smz5sqg",
    "billing_provider_version": 2,
    "treating_provider_id": "prv_87bbcpssc5ennege20fn",
    "treating_provider_version": 2,
    "packet_id": "pkt_20fng953rtp1hwc3zdcn",
    "provider_snapshots": {},
    "context_snapshot": {}
  }
}
409Version conflict
{
  "error": "WORKSPACE_VERSION_CONFLICT",
  "message": "The workspace changed. Reload before saving.",
  "errors": [],
  "request_id": "evt_6q83h5553apd50cac4e4"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office, draft, or packet in this office
409WORKSPACE_VERSION_CONFLICTexpected_version is stale, or the draft is FROZEN
422PACKET_MANIFEST_INVALIDLinked packet manifest incomplete
422WORKSPACE_INVALIDContext not an object, too large, or has a credential named key
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENTS_UNAVAILABLEpacket_id given and attachment service not wired
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
ImportantThe draft body contains PHI by design (patient and subscriber context). Responses carry Cache-Control: private, no-store.
ImportantAn unknown provider id or packet id currently returns an unhandled 500 instead of a 4xx.
API reference

Create an attachment packet

POSThttps://api.claimhouse.ai/v1/attachments

Stores one or more files for one office and opens a packet you can send once.

Files travel inside the JSON body as strict base64 in files[].content_base64. Each file decodes to 1 byte to 50 MB, and a packet holds 1 to 200 files.

Used by both paths: the packet API sends the packet directly, the workspace links it to a draft. Same bytes uploaded again to the same office come back with deduplicated: true and the first upload's kind, teeth and orientation.

Permission
submit
Idempotency
none
Side effects
Archives each file under your office, writes one packet in state OPEN, and emits attachment.packet_created. No vendor call.
In the dashboard
Attachments > New attachment (Store documents)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. File bytes are base64 inside the JSON body, not multipart.
e.g. application/json

Request body

NameTypeDescription
facility_id*stringThe office the files belong to, 1 to 64 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
files*array1 to 200 files.
content_base64*stringFile bytes as strict base64, at least 4 characters. Decoded size 1 byte to 50 MB.
e.g. /9j/4AAQSkZJRgABAQAAAQABAAD/2wBD
media_type*stringThe file's media type. The NEA door accepts only image/jpeg at send.
one of: image/jpeg · image/png · image/tiff · image/gif · application/pdf · text/plain
e.g. image/jpeg
kind*stringDocument kind. Drives the NEA document code and payer documentation rules.
one of: appeal · bitewing_xray · cephalometric_xray · claim_image · credentialing_profile · curriculum_vitae · dea_certificate · diagnosis · eob · eob_or_cob · full_arch_xray · full_mouth_xrays · hospital_verification_letter · insurance_certificate · intraoral_photo · malpractice_certificate · narrative · other · panoramic_xray · partial_mount_xray · periapical_xray · periodontal_chart · photo · radiograph · recredentialing_profile · release_of_information · report · specialty_certificate · student_verification · work_history · xray
e.g. radiograph
teetharrayArray of strings. Teeth the file shows, at most 32, each 1 to 4 characters.
e.g. ["30"]
orientationstringImage orientation, or null.
one of: left · right · none
taken_atstringImage date, at most 32 characters. Passed to the attachment network as the document date.
filenamestringAt most 200 characters. Accepted but not stored or returned.

Request example

{
  "facility_id": "fac_q5s09nzww25ysd5a2f3g",
  "files": [
    {
      "content_base64": "/9j/BwcHBwcHBwcHBwcHBwcHB//Z",
      "media_type": "image/jpeg",
      "kind": "radiograph",
      "teeth": [
        "30"
      ],
      "orientation": "right",
      "taken_at": "2026-09-01",
      "filename": "bitewing-right.jpg"
    }
  ]
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
packet_id*stringThe new packet, pkt_....
facility_id*stringThe office.
state*stringAlways OPEN on create.
one of: OPEN
attachments*arrayOne entry per stored file, in request order.
attachment_id*stringThe stored file, att_....
sha256*stringHex SHA-256 digest of the decoded bytes.
size_bytes*integerDecoded size in bytes.
media_type*stringStored media type.
one of: image/jpeg · image/png · image/tiff · image/gif · application/pdf · text/plain
kind*stringStored document kind. When deduplicated, the first upload's value.
one of: appeal · bitewing_xray · cephalometric_xray · claim_image · credentialing_profile · curriculum_vitae · dea_certificate · diagnosis · eob · eob_or_cob · full_arch_xray · full_mouth_xrays · hospital_verification_letter · insurance_certificate · intraoral_photo · malpractice_certificate · narrative · other · panoramic_xray · partial_mount_xray · periapical_xray · periodontal_chart · photo · radiograph · recredentialing_profile · release_of_information · report · specialty_certificate · student_verification · work_history · xray
teeth*arrayArray of strings. Teeth the file shows, as stored.
orientationstringStored orientation. Null when none was given.
one of: left · right · none
deduplicated*booleanTrue when these exact bytes already existed in this office and the existing file was reused.

Responses

201Created
{
  "request_id": "evt_b7cb582z7s93chb1k3ar",
  "packet_id": "pkt_fxzt24bgwhc0631767a2",
  "facility_id": "fac_q5s09nzww25ysd5a2f3g",
  "state": "OPEN",
  "attachments": [
    {
      "attachment_id": "att_956fvmp0brws5e0qyw4a",
      "sha256": "6c1aee1eb68aeeeebd2325ce9d4107806a968d84ec0aa8ef5338a7812aafd083",
      "size_bytes": 21,
      "media_type": "image/jpeg",
      "kind": "radiograph",
      "teeth": [
        "30"
      ],
      "orientation": "right",
      "deduplicated": false
    }
  ]
}
422File too large
{
  "error": "FILE_TOO_LARGE",
  "message": "file exceeds the archive ceiling",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
403Office not granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "this key's grants do not cover that facility",
  "errors": [
    {
      "facility_id": "fac_w9qxypnbcw7b308jbbhh"
    }
  ],
  "request_id": "evt_8t458bs69g1h0bb2eajs"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover facility_id
404NOT_FOUNDNo such office for this seller
422INVALID_REQUESTSchema failure, bad base64, or media type, kind or orientation not accepted
422FILE_EMPTYA file decodes to zero bytes
422FILE_TOO_LARGEA file decodes to more than 50 MB
422TOOTH_INVALIDA tooth token is blank or longer than 4 characters
422ATTACHMENT_CONFLICTThe digest collides with a file outside your scope
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENTS_UNAVAILABLEAttachment service not wired on this gateway
ImportantThis route needs the attachment service. A gateway started without it answers 503 ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.
InfoThe key's test or production mode is not checked against the office on this route.
InfoThere is no request body size cap in the gateway itself; your HTTP host may cut off very large bodies first.
API reference

Retrieve an attachment packet

GEThttps://api.claimhouse.ai/v1/attachments/packets/{packet_id}

Returns a packet's state, door, attachment number, files and send record.

Poll this after a send, or read the attachment number before building a claim. It is also where error_class and vendor_transaction_id live for workspace sends.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Attachments > New attachment (linked packet); Attachments > All attachments

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
packet_id*stringThe packet id, pkt_.... 1 to 64 characters.
e.g. pkt_qxwt9srf1300w6s4e6zt

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
packet_id*stringThe packet.
facility_id*stringThe office.
state*stringPacket state.
one of: OPEN · SENDING · SENT · AMBIGUOUS · FAILED
doorstringDoor used. Null until attempted.
reference_kindstringnea_number when sent through the attachment network (NEA). Null otherwise.
referencestringThe attachment number. Null unless SENT.
vendor_transaction_idstringThe id sent to the vendor (the packet id, up to 45 characters). Null until attempted.
payer_idstringPayer named on the send. Null until sent.
submission_idstringSubmission named on the send. Null when none.
tenant_claim_idstringYour claim id named on the send. Null when none.
created_at*datetimeWhen the packet was created.
updated_at*datetimeWhen the packet last changed.
attachments*arrayThe packet's files. deduplicated is always false here.
attachment_id*stringThe stored file, att_....
sha256*stringHex SHA-256 digest of the decoded bytes.
size_bytes*integerDecoded size in bytes.
media_type*stringStored media type.
one of: image/jpeg · image/png · image/tiff · image/gif · application/pdf · text/plain
kind*stringStored document kind. When deduplicated, the first upload's value.
one of: appeal · bitewing_xray · cephalometric_xray · claim_image · credentialing_profile · curriculum_vitae · dea_certificate · diagnosis · eob · eob_or_cob · full_arch_xray · full_mouth_xrays · hospital_verification_letter · insurance_certificate · intraoral_photo · malpractice_certificate · narrative · other · panoramic_xray · partial_mount_xray · periapical_xray · periodontal_chart · photo · radiograph · recredentialing_profile · release_of_information · report · specialty_certificate · student_verification · work_history · xray
teeth*arrayArray of strings. Teeth the file shows, as stored.
orientationstringStored orientation. Null when none was given.
one of: left · right · none
deduplicated*booleanTrue when these exact bytes already existed in this office and the existing file was reused.
sends*arraySend attempts. At most one in practice.
send_id*stringThe send attempt, snd_....
door*stringDoor attempted.
one of: network_A · network_B · x12_275 · portal · paper
outcome*stringResult of the one attempt.
one of: OK · AMBIGUOUS · FAILED
reference_kindstringnea_number when sent through the attachment network (NEA). Null otherwise.
referencestringThe attachment number. Null unless the outcome is OK.
vendor_transaction_idstringId the vendor knows the attempt by. Null when no vendor call was made.
error_classstringWhy the attempt did not end OK. Null on OK.
attempted_at*datetimeWhen the attempt was made.

Responses

200OK
{
  "request_id": "evt_5gqf02jh70wxyy1kgmer",
  "packet_id": "pkt_qxwt9srf1300w6s4e6zt",
  "facility_id": "fac_q5s09nzww25ysd5a2f3g",
  "state": "OPEN",
  "door": null,
  "reference_kind": null,
  "reference": null,
  "vendor_transaction_id": null,
  "payer_id": null,
  "submission_id": null,
  "tenant_claim_id": null,
  "created_at": "2026-09-15T03:38:46.084698+00:00",
  "updated_at": "2026-09-15T03:38:46.084698+00:00",
  "attachments": [
    {
      "attachment_id": "att_6ct682fd8hwy6a6r75x3",
      "sha256": "5b57aa3d7088308beffe03553ccb1580fa27a1b0076eae83a847b263db1f4743",
      "size_bytes": 21,
      "media_type": "image/jpeg",
      "kind": "radiograph",
      "teeth": [
        "14"
      ],
      "orientation": "left",
      "deduplicated": false
    },
    {
      "attachment_id": "att_c144e48gpk0gkvxg6ves",
      "sha256": "1133ee3694581d02ea7b9737731404a78b760219f1f45a3f2319cc3bb29fea2f",
      "size_bytes": 21,
      "media_type": "image/jpeg",
      "kind": "periodontal_chart",
      "teeth": [],
      "orientation": null,
      "deduplicated": false
    }
  ],
  "sends": []
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such packet",
  "errors": [],
  "request_id": "evt_jwamjpg2w054px4bmctc"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
404NOT_FOUNDNo such packet, or outside the key's office grants
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENTS_UNAVAILABLEAttachment service not wired on this gateway
ImportantThis route needs the attachment service. A gateway started without it answers 503 ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.
InfoA packet outside your key's office grants answers 404, not 403.
API reference

Validate an attachment draft

POSThttps://api.claimhouse.ai/v1/attachment-drafts/{draft_id}/validate

Checks that a draft is complete and sendable, then freezes a review snapshot and marks it READY.

Call it after the last change and before send. Checks run in order: version, linked OPEN packet and manifest, payer mapping and routing, provider versions and billing identity, context rules, attachment network field limits, the door auto would choose, that door's file limits, and payer documentation rules.

Success writes a new READY version and records your key or session as the confirming actor. The READY version is the expected_version you pass to send.

Permission
submit
Idempotency
none
Side effects
Writes a READY version with provider and context snapshots. No vendor call: vendor_verified is always false.
In the dashboard
Attachments > New attachment (Check readiness)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
draft_id*stringThe attachment draft id, awd_....
e.g. awd_8f2nd1jewe9sstsgt4ez

Query parameters

NameTypeDescription
facility_id*stringThe office the draft belongs to. 1 to 64 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme

Request body

NameTypeDescription
expected_version*integerThe draft's current version, at least 1.
e.g. 2

Request example

{
  "expected_version": 2
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
draft*objectThe draft at its new version, state READY, snapshots filled.
draft_id*stringThe draft, awd_....
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the draft.
one of: test · production
version*integerCurrent version. Every change adds 1.
state*stringDRAFT is editable, READY is validated at this version, FROZEN means a send was attempted or is in progress and no more edits are allowed.
one of: DRAFT · READY · FROZEN
created_at*datetimeWhen the draft was created.
updated_at*datetimeWhen the draft last changed.
context*objectClaim context as last saved. Contains PHI.
billing_provider_idstringSelected billing provider. Null when none.
billing_provider_versionintegerSelected billing provider version. Null when none.
treating_provider_idstringSelected treating provider. Null when none.
treating_provider_versionintegerSelected treating provider version. Null when none.
packet_idstringLinked packet. Null when none.
provider_snapshots*objectEmpty until validate. Then billing and treating, each the provider version row that was reviewed.
context_snapshot*objectEmpty until validate. Then context (normalized), billing_provider, treating_provider, confirmed_by, confirmed_at, fingerprint (SHA-256) and authority_snapshot (office, payer, routing and door choice at validate).
validation*objectValidation result.
valid*booleanAlways true on 200. Failures come back as errors.
vendor_verified*booleanAlways false. Nothing was checked with the vendor.

Responses

200OK
{
  "request_id": "evt_6q83h5553apd50cac4e4",
  "draft": {
    "draft_id": "awd_8f2nd1jewe9sstsgt4ez",
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "facility_id": "fac_tycrfy0cs0qc7sq95eme",
    "mode": "test",
    "version": 3,
    "state": "READY",
    "created_at": "2026-09-14T15:02:00+00:00",
    "updated_at": "2026-09-14T15:06:00+00:00",
    "context": {
      "payer_id": "EXMPL",
      "patient": {
        "first_name": "Sample",
        "last_name": "Patient",
        "date_of_birth": "2016-09-01"
      },
      "subscriber": {
        "person": {
          "first_name": "Sample",
          "last_name": "Subscriber",
          "date_of_birth": "1986-09-01"
        },
        "member_id": "SYN000123456",
        "group_number": null
      },
      "relationship": "child",
      "patient_is_subscriber": false,
      "service_date_start": "2026-09-01",
      "service_date_end": "2026-09-01",
      "predetermination": false,
      "external_claim_reference": "clm_b66x8fzgq4nxwk96t54c",
      "procedures": [
        {
          "cdt_code": "D4341",
          "procedure_date": "2026-09-01",
          "teeth": [],
          "quadrant": "UR"
        }
      ]
    },
    "billing_provider_id": "prv_xxnj1rkzpd850smz5sqg",
    "billing_provider_version": 2,
    "treating_provider_id": "prv_87bbcpssc5ennege20fn",
    "treating_provider_version": 2,
    "packet_id": "pkt_20fng953rtp1hwc3zdcn",
    "provider_snapshots": {
      "billing": {
        "provider_id": "prv_xxnj1rkzpd850smz5sqg",
        "role": "billing",
        "version": 2,
        "status": "confirmed"
      },
      "treating": {
        "provider_id": "prv_87bbcpssc5ennege20fn",
        "role": "treating",
        "version": 2,
        "status": "confirmed"
      }
    },
    "context_snapshot": {
      "context": {
        "payer_id": "EXMPL",
        "relationship": "child"
      },
      "confirmed_by": "key_y7e70t7hxfdnz6yrwaxr",
      "confirmed_at": "2026-09-14T15:06:00+00:00",
      "fingerprint": "bbee49f08c6ff3dfe313ffd4828d71597cdb2923ad48111983c1e7277198c0e5",
      "authority_snapshot": {
        "door": "network_A"
      }
    }
  },
  "validation": {
    "valid": true,
    "vendor_verified": false
  }
}
422Context incomplete
{
  "error": "ATTACHMENT_CONTEXT_INCOMPLETE",
  "message": "Attachment context requires review.",
  "errors": [
    {
      "code": "TREATING_TAX_ID_REQUIRED",
      "path": "treating_provider.identity.tax_id"
    }
  ],
  "request_id": "evt_fm68ahm66jejbmm4pfkm"
}
422Packet required
{
  "error": "PACKET_REQUIRED",
  "message": "Attach and review the actual packet before validation.",
  "errors": [],
  "request_id": "evt_61ae1hdfawbmybrxa762"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office, draft or packet
409WORKSPACE_VERSION_CONFLICTexpected_version not current, or draft FROZEN
409PACKET_ALREADY_ATTEMPTEDLinked packet is not OPEN
409PROVIDER_VERSION_CONFLICTSelected provider missing or not at the recorded version
422PACKET_REQUIREDNo packet linked
422PACKET_MANIFEST_INVALIDPacket manifest incomplete
422PAYER_MAPPING_UNRESOLVEDPayer not found exactly, no NEA payer id, or no routing
422BILLING_IDENTITY_MISMATCHBilling NPI or tax id differs from the office
422ATTACHMENT_CONTEXT_INCOMPLETEContext rules failed; errors[] lists {code, path}
422ATTACHMENT_CONTEXT_INVALIDContext has the wrong types or shape
422NEA_CONTEXT_REQUIREDNo procedures, or treating provider has no valid tax id
422NEA_CONTEXT_INVALIDContext does not fit the attachment network fields
422PAYER_ROUTING_UNKNOWNNo attachment routing for the payer
422FACILITY_NOT_ENROLLED_FOR_DOOROffice not enrolled for any payer door
422DOOR_FILE_COUNT_EXCEEDEDToo many files for the door
422DOOR_FILE_SIZE_EXCEEDEDA file exceeds the door's per file limit
422DOOR_MEDIA_TYPE_NOT_ACCEPTEDA file's media type is refused by the door
422DOOR_TOTAL_SIZE_EXCEEDEDPacket exceeds the door's total size
422DOCUMENTATION_RULE_UNMETPayer rule for a CDT code needs kinds the packet lacks
422WORKSPACE_INVALIDSnapshot refused
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENTS_UNAVAILABLEAttachment service not wired on this gateway
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
ImportantThis route needs the attachment service. A gateway started without it answers 503 ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.
InfoATTACHMENT_CONTEXT_INCOMPLETE item codes include RELATIONSHIP_REQUIRED, RELATIONSHIP_CONFLICT, SELF_IDENTITY_CONFLICT, PERSON_IDENTITY_REQUIRED, MEMBER_ID_REQUIRED, SERVICE_DATES_REQUIRED, SERVICE_DATE_ORDER, PROVIDER_NOT_CONFIRMED, PROVIDER_NOT_EFFECTIVE, PROVIDER_NPI_INVALID, PROVIDER_CONFIRMATION_REQUIRED, TREATING_IDENTITY_REQUIRED, BILLING_IDENTITY_REQUIRED, BILLING_TAX_ID_REQUIRED, TREATING_TAX_ID_REQUIRED and PROVIDER_UNKNOWN_OR_AMBIGUOUS.
ImportantValidate can pass when the router would choose an unbuilt door (for example paper for an office without NEA registration). The send then consumes the packet and answers 501 DOOR_NOT_BUILT.
API reference

Send an attachment draft

POSThttps://api.claimhouse.ai/v1/attachment-drafts/{draft_id}/send

Sends the draft's linked packet to the payer once, using the reviewed snapshot.

Re-checks billing identity, packet state and manifest, office and payer authority, provider roster, context, door limits and documentation rules against the validate snapshot. Then it freezes the draft and makes one attempt through door: auto.

Returns 200 whenever the attempt reached a door. Read packet.outcome: OK carries the attachment number, AMBIGUOUS must be reconciled and never resent. The draft is FROZEN afterwards and can never be edited or sent again.

Permission
submit
Idempotency
none
Side effects
Freezes the draft, then contacts the attachment network (NEA) exactly once, never retried. Records the send, updates the packet and emits attachment.sent or attachment.failed.
In the dashboard
Attachments > New attachment (Send attachment, once)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
draft_id*stringThe attachment draft id, awd_....
e.g. awd_8f2nd1jewe9sstsgt4ez

Query parameters

NameTypeDescription
facility_id*stringThe office the draft belongs to. 1 to 64 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme

Request body

NameTypeDescription
expected_version*integerThe READY version returned by validate, at least 1.
e.g. 3

Request example

{
  "expected_version": 3
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
draft*objectThe frozen draft (state FROZEN).
draft_id*stringThe draft, awd_....
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the draft.
one of: test · production
version*integerCurrent version. Every change adds 1.
state*stringDRAFT is editable, READY is validated at this version, FROZEN means a send was attempted or is in progress and no more edits are allowed.
one of: DRAFT · READY · FROZEN
created_at*datetimeWhen the draft was created.
updated_at*datetimeWhen the draft last changed.
context*objectClaim context as last saved. Contains PHI.
billing_provider_idstringSelected billing provider. Null when none.
billing_provider_versionintegerSelected billing provider version. Null when none.
treating_provider_idstringSelected treating provider. Null when none.
treating_provider_versionintegerSelected treating provider version. Null when none.
packet_idstringLinked packet. Null when none.
provider_snapshots*objectEmpty until validate. Then billing and treating, each the provider version row that was reviewed.
context_snapshot*objectEmpty until validate. Then context (normalized), billing_provider, treating_provider, confirmed_by, confirmed_at, fingerprint (SHA-256) and authority_snapshot (office, payer, routing and door choice at validate).
packet*objectResult of the one attempt.
packet_id*stringThe linked packet.
state*stringPacket state after the attempt.
one of: SENT · AMBIGUOUS · FAILED
referencestringThe attachment number on OK. Null otherwise.
reference_kindstringnea_number on OK. Null otherwise.
outcome*stringResult of the attempt.
one of: OK · AMBIGUOUS · FAILED
send_id*stringThe send attempt, snd_....

Responses

200OK
{
  "request_id": "evt_fm68ahm66jejbmm4pfkm",
  "draft": {
    "draft_id": "awd_8f2nd1jewe9sstsgt4ez",
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "facility_id": "fac_tycrfy0cs0qc7sq95eme",
    "mode": "test",
    "version": 4,
    "state": "FROZEN",
    "created_at": "2026-09-14T15:02:00+00:00",
    "updated_at": "2026-09-14T15:09:00+00:00",
    "context": {
      "payer_id": "EXMPL",
      "patient": {
        "first_name": "Sample",
        "last_name": "Patient",
        "date_of_birth": "2016-09-01"
      },
      "subscriber": {
        "person": {
          "first_name": "Sample",
          "last_name": "Subscriber",
          "date_of_birth": "1986-09-01"
        },
        "member_id": "SYN000123456",
        "group_number": null
      },
      "relationship": "child",
      "patient_is_subscriber": false,
      "service_date_start": "2026-09-01",
      "service_date_end": "2026-09-01",
      "predetermination": false,
      "external_claim_reference": "clm_b66x8fzgq4nxwk96t54c",
      "procedures": [
        {
          "cdt_code": "D4341",
          "procedure_date": "2026-09-01",
          "teeth": [],
          "quadrant": "UR"
        }
      ]
    },
    "billing_provider_id": "prv_xxnj1rkzpd850smz5sqg",
    "billing_provider_version": 2,
    "treating_provider_id": "prv_87bbcpssc5ennege20fn",
    "treating_provider_version": 2,
    "packet_id": "pkt_20fng953rtp1hwc3zdcn",
    "provider_snapshots": {
      "billing": {
        "provider_id": "prv_xxnj1rkzpd850smz5sqg",
        "role": "billing",
        "version": 2,
        "status": "confirmed"
      },
      "treating": {
        "provider_id": "prv_87bbcpssc5ennege20fn",
        "role": "treating",
        "version": 2,
        "status": "confirmed"
      }
    },
    "context_snapshot": {
      "context": {
        "payer_id": "EXMPL",
        "relationship": "child"
      },
      "confirmed_by": "key_y7e70t7hxfdnz6yrwaxr",
      "confirmed_at": "2026-09-14T15:06:00+00:00",
      "fingerprint": "bbee49f08c6ff3dfe313ffd4828d71597cdb2923ad48111983c1e7277198c0e5",
      "authority_snapshot": {
        "door": "network_A"
      }
    }
  },
  "packet": {
    "packet_id": "pkt_20fng953rtp1hwc3zdcn",
    "state": "SENT",
    "reference": "EXAMPLE123456",
    "reference_kind": "nea_number",
    "outcome": "OK",
    "send_id": "snd_mca62fv2qg9fne6xs828"
  }
}
409Draft not ready
{
  "error": "DRAFT_NOT_READY",
  "message": "Review the current draft and linked packet before sending.",
  "errors": [],
  "request_id": "evt_61ae1hdfawbmybrxa762"
}
503Send uncertain
{
  "error": "ATTACHMENT_SEND_UNCERTAIN",
  "message": "The draft is frozen and the send outcome requires reconciliation. Do not resend.",
  "errors": [],
  "request_id": "evt_9f5vkfv8a8rxpmm7k77z"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office, draft or packet
409DRAFT_NOT_READYDraft not READY, version not current, or no packet linked
409BILLING_IDENTITY_CHANGEDOffice billing NPI or TIN changed since validate
409PACKET_CHANGED_OR_ATTEMPTEDPacket not OPEN, or its manifest changed
409ATTACHMENT_AUTHORITY_CHANGEDOffice, payer or routing changed since validate
409ATTACHMENT_CONTEXT_CHANGEDProvider roster or context no longer passes
409WORKSPACE_VERSION_CONFLICTDraft changed during the freeze
409ATTACHMENT_MANIFEST_CHANGEDManifest changed between freeze and attempt; no vendor call
409ATTACHMENT_ROUTING_CHANGEDRouting changed between freeze and attempt; no vendor call
409PACKET_ALREADY_SENTPacket claimed by another attempt at the last moment
422NEA_CONTEXT_REQUIREDAs on validate
422NEA_CONTEXT_INVALIDAs on validate
422PAYER_MAPPING_UNRESOLVEDAs on validate
422PAYER_ROUTING_UNKNOWNAs on validate
422FACILITY_NOT_ENROLLED_FOR_DOORAs on validate
422DOOR_FILE_COUNT_EXCEEDEDDoor limit
422DOOR_FILE_SIZE_EXCEEDEDDoor limit
422DOOR_MEDIA_TYPE_NOT_ACCEPTEDDoor limit
422DOOR_TOTAL_SIZE_EXCEEDEDDoor limit
422DOCUMENTATION_RULE_UNMETPayer documentation rule not met
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
501DOOR_NOT_BUILTRouter chose an unbuilt door; draft frozen, packet FAILED
503ATTACHMENT_SEND_UNCERTAINUnexpected fault after the freeze; reconcile, do not resend
503ATTACHMENT_DRAFT_SEND_UNAVAILABLEAttachment service not wired; no vendor attempt
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
ImportantOne attempt only. An AMBIGUOUS outcome or a 503 ATTACHMENT_SEND_UNCERTAIN means the network may hold a record. Do not resend; reconcile first.
InfoNo Idempotency-Key is read. The guards are expected_version, the READY state, the freeze to FROZEN, and the packet's OPEN to SENDING switch. A repeat call answers 409 DRAFT_NOT_READY.
Infoerror_class and vendor_transaction_id are not in this response. Read them with GET /v1/attachments/packets/{packet_id}.
ImportantA gateway without the attachment service answers 503 ATTACHMENT_DRAFT_SEND_UNAVAILABLE with no vendor attempt. Deployed wiring was not confirmed on 2026-09-14.
API reference

Send an attachment packet

POSThttps://api.claimhouse.ai/v1/attachments/packets/{packet_id}/send

Sends one OPEN packet to the payer through one door, once.

Use it on the packet API path with complete claim_context, then put the returned attachment number (or the packet id) on your claim.

Routing, door limit and documentation refusals happen before the packet is claimed and leave it OPEN. Once claimed, every attempt that reaches a door returns 200: read outcome and error_class.

Permission
submit
Idempotency
none
Side effects
Contacts the attachment network (NEA) exactly once (authenticate, create, upload each image, close), never retried. Records the send, updates the packet and emits attachment.sent or attachment.failed.
In the dashboard
Claims > Submit a claim > Draft flow > Attach (not working today)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
packet_id*stringThe packet id, pkt_.... 1 to 64 characters.
e.g. pkt_txz0tef8k7pvn9tvggbg

Request body

NameTypeDescription
payer_id*stringPayer primary id, exact, 1 to 40 characters. Routing and the NEA payer id are looked up by primary id only.
e.g. EXMPL
doorstringDoor to use. Defaults to auto.
one of: auto · network_A · network_B · x12_275 · portal · paper
e.g. auto
submission_idstringLinks the send to a submission, at most 64 characters. Also used as the attachment network reference.
tenant_claim_idstringYour claim id, at most 80 characters. Links events to the matching claim.
procedure_codesarrayArray of strings. CDT codes, at most 50, checked against payer documentation rules. Omitted means no documentation check.
claim_contextobjectClaim facts the attachment network needs. Optional in the schema but required by the NEA door. PHI: never logged or stored by the gateway.
patient_first_name*string1 to 60 characters.
patient_last_name*string1 to 60 characters.
patient_date_of_birth*string8 to 10 characters, passed as sent. YYYYMMDD recommended.
insured_id*stringSubscriber member id, 1 to 80 characters.
insured_first_name*stringSubscriber first name, 1 to 60 characters.
insured_last_name*stringSubscriber last name, 1 to 60 characters.
relationshipstringPatient's relationship to the subscriber, at most 8 characters. Defaults to self. self, spouse, child or 18, 01, 19 pass through; anything else is sent as self.
group_numberstringAt most 50 characters.
procedures*array1 to 50 procedures.
cdt_code*string2 to 8 characters.
procedure_date*string8 to 10 characters. YYYYMMDD recommended.
teetharrayAt most 32 teeth.
tooth_number*string1 to 4 characters.
surfacesstringAt most 8 letters from BDFILMO.
quadrantstringUL, UR, LL, LR, UA, LA or FM. Anything else is sent as none.
date_of_service_from*string8 to 10 characters.
date_of_service_thru*string8 to 10 characters.
is_predeterminationbooleanDefaults to false.
doctor_first_namestringAt most 60 characters.
doctor_last_namestringAt most 60 characters.
doctor_npistringAt most 10 characters. Not check digit validated on this route.
doctor_licensestringAt most 30 characters.
doctor_tax_idstringAt most 9 characters. Sent as "0" when empty.

Request example

{
  "payer_id": "TESTPAYER1",
  "door": "auto",
  "tenant_claim_id": "clm_b66x8fzgq4nxwk96t54c",
  "procedure_codes": [
    "D4341"
  ],
  "claim_context": {
    "patient_first_name": "Sample",
    "patient_last_name": "Patient",
    "patient_date_of_birth": "20160901",
    "insured_id": "SYN000123456",
    "insured_first_name": "Sample",
    "insured_last_name": "Subscriber",
    "relationship": "child",
    "procedures": [
      {
        "cdt_code": "D4341",
        "procedure_date": "20260901",
        "quadrant": "UR"
      }
    ],
    "date_of_service_from": "20260901",
    "date_of_service_thru": "20260901",
    "doctor_first_name": "Sample",
    "doctor_last_name": "Provider",
    "doctor_npi": "9999999995",
    "doctor_tax_id": "990000001"
  }
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
packet_id*stringThe packet.
state*stringPacket state after the attempt.
one of: SENT · AMBIGUOUS · FAILED
door*stringDoor used.
door_requested*stringauto or the door you named.
outcome*stringResult of the one attempt.
one of: OK · AMBIGUOUS · FAILED
reference_kindstringnea_number on OK. Null otherwise.
referencestringThe attachment number on OK. Null otherwise.
vendor_transaction_idstringId the vendor knows the attempt by. Null when no vendor call was made.
error_classstringWhy the outcome is not OK, for example FacilityNotRegistered, ClaimContextRequired, PayerMasterIdUnknown, MediaTypeNotJpeg, NeaAuthError, CloseStatusNotSent or DoorNotBuilt. Null on OK.
send_id*stringThe send attempt, snd_....

Responses

200OK
{
  "request_id": "evt_vajseqp6wr2j43k05grm",
  "packet_id": "pkt_txz0tef8k7pvn9tvggbg",
  "state": "SENT",
  "door": "network_A",
  "door_requested": "auto",
  "outcome": "OK",
  "reference_kind": "nea_number",
  "reference": "7654321",
  "vendor_transaction_id": "pkt_txz0tef8k7pvn9tvggbg",
  "error_class": null,
  "send_id": "snd_1np89szp4z32sv662hp7"
}
409Already sent
{
  "error": "PACKET_ALREADY_SENT",
  "message": "this packet was already sent or is being sent",
  "errors": [
    {
      "packet_id": "pkt_20fng953rtp1hwc3zdcn"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
422Door media type
{
  "error": "DOOR_MEDIA_TYPE_NOT_ACCEPTED",
  "message": "a file's media type is not accepted by this door",
  "errors": [
    {
      "door": "network_A",
      "media_types": [
        "image/jpeg"
      ],
      "attachment_id": "att_k8snrmqpcs4dpabqh2x2"
    }
  ],
  "request_id": "evt_6q83h5553apd50cac4e4"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover the office
404NOT_FOUNDNo such packet in scope, or its office is missing
409PACKET_ALREADY_SENTPacket is not OPEN, including FAILED and AMBIGUOUS
422PAYER_ROUTING_UNKNOWNNo routing for payer_id; packet untouched
422PAYER_DOOR_NOT_ACCEPTEDNamed door not accepted by the payer; packet untouched
422FACILITY_NOT_ENROLLED_FOR_DOOROffice not enrolled for the door; packet untouched
422ATTACHMENT_MISSINGA file of the packet is gone; packet untouched
422DOOR_FILE_COUNT_EXCEEDEDToo many files for the door; packet untouched
422DOOR_FILE_SIZE_EXCEEDEDA file exceeds the door limit; packet untouched
422DOOR_MEDIA_TYPE_NOT_ACCEPTEDMedia type refused by the door; packet untouched
422DOOR_TOTAL_SIZE_EXCEEDEDPacket exceeds the door total; packet untouched
422DOCUMENTATION_RULE_UNMETPayer rule needs kinds the packet lacks; packet untouched
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
501DOOR_NOT_BUILTChosen door not built; packet now FAILED
503ATTACHMENTS_UNAVAILABLEAttachment service not wired on this gateway
ImportantOne attempt only. Never resend an AMBIGUOUS packet. For FAILED, create a new packet (same bytes deduplicate) and send that.
ImportantThe key's test or production mode is not checked against the office, so a test key can make a real send for a production office.
InfoNo Idempotency-Key is read. The packet itself is the guard: only an OPEN packet can be claimed, and a second call answers 409 PACKET_ALREADY_SENT whatever the first outcome was. Its message says "already sent" even for FAILED.
InfoDOCUMENTATION_RULE_UNMET puts cdt_code, missing_kinds and guideline_url in errors[0]. Door limit errors name the limit in errors[0].
ImportantA missing claim_context or an unknown NEA payer id is found only after the packet is claimed: the send ends FAILED (ClaimContextRequired or PayerMasterIdUnknown) and the packet is consumed.
ImportantAt the NEA door, relationship other or any unlisted value is sent as self. Do not send dependent claims with relationship other until this is fixed.
ImportantA store fault after the packet left OPEN can surface as an unhandled 500 with no error code. Treat it as ambiguous and do not resend.
ImportantThis route needs the attachment service. A gateway started without it answers 503 ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.
API reference

List attachment drafts

GEThttps://api.claimhouse.ai/v1/attachment-drafts

Lists attachment drafts for one office without their protected context.

Returns draft heads ordered by opaque draft id descending, not by creation time. Pass next_cursor back as cursor for the next page.

Permission
read
Idempotency
none
Side effects
Writes an audit read row listing the returned draft ids. No vendor call.
In the dashboard
Attachments > All attachments

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
facility_id*stringThe office to list, 1 to 64 characters. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
limitintegerPage size, 1 to 200. Defaults to 100. Use 100 or less (see note).
e.g. 50
cursorstringThe next_cursor from the previous page, at most 64 characters.

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
drafts*arrayDraft heads.
draft_id*stringThe draft, awd_....
seller_id*stringYour seller.
facility_id*stringThe office.
mode*stringEnvironment of the draft.
one of: test · production
version*integerCurrent version.
state*stringDraft state.
one of: DRAFT · READY · FROZEN
created_at*datetimeWhen the draft was created.
updated_at*datetimeWhen the draft last changed.
next_cursorstringPass as cursor for the next page. Null when there are no more pages.

Responses

200OK
{
  "request_id": "evt_b24r3x72zsm5gs7pbwea",
  "drafts": [
    {
      "draft_id": "awd_8f2nd1jewe9sstsgt4ez",
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "mode": "test",
      "version": 4,
      "state": "FROZEN",
      "created_at": "2026-09-14T15:02:00+00:00",
      "updated_at": "2026-09-14T15:09:00+00:00"
    }
  ],
  "next_cursor": null
}
403Office not granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "this key's grants do not cover that facility",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, unknown or revoked key
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey grants do not cover the office
403MODE_MISMATCHKey mode does not match the office binding, or office not ACTIVE
404NOT_FOUNDNo such office in scope
422WORKSPACE_INVALIDScope values could not be verified
422INVALID_REQUESTBody, query or path fails the schema
429TOO_MANY_REQUESTSToo many requests in flight or rate bucket empty
503ATTACHMENT_WORKSPACE_UNAVAILABLEWorkspace not wired on this gateway
ImportantPagination quirk: next_cursor is set only when the page size equals limit, and a page is capped at 100 rows. With limit above 100 you get at most 100 rows and next_cursor: null, so paging stops early.
InfoWorkspace routes check key mode: a sandbox office needs a test key and a production office needs a production key. The office must be ACTIVE.
API reference

Get attachment requirements

GEThttps://api.claimhouse.ai/v1/attachment-requirements

What a payer asks you to attach for a set of procedure codes.

Answers from payer rules Claim House refreshes on a schedule. No external call is made on this path, and no patient information is involved, so it is safe to call on every claim.

This is guidance, not adjudication. A payer listing a document is not a promise of payment, and a payer listing nothing is not a promise that nothing is needed.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Attachments > New attachment (checks)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
If-None-MatchstringSend the ETag from a previous answer to get 304 Not Modified while the rules have not changed.
e.g. W/"0f1a2b3c4d5e6f708192a3b4c5d6e7f8"

Query parameters

NameTypeDescription
payer_id*stringThe payer id you put on the claim, the same value your 837D carries. 1 to 20 characters.
e.g. 60054
procedure_codes*stringComma separated CDT codes, 1 to 50, each matching D plus four digits. Case insensitive.
e.g. D4341,D1110
plan_idstringA specific plan, when the payer's requirements differ by plan. Opaque apl_ value from the plans route.
e.g. apl_9e98e74a6a3c1f02
facility_idstringThe office asking. Supplying it applies that office's remembered plan choice. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme

Request example

null

Response fields

NameTypeDescription
payer_id*stringThe payer id you asked about.
payer_namestringPayer name when Claim House knows it.
electronic_attachments*stringavailable or not_available. When not_available the claim still goes out; the attachment cannot.
plan_selection*stringnot_needed, required, chosen or remembered. required means a requested code differs by plan.
planobjectThe plan in scope, when one is. plan_id is opaque and stable.
narrative*objectallowed and max_characters (2000).
payer_reference_number*stringrequired, allowed or not_accepted.
file_limits*objectUpload constraints: formats, max_images, max_total_bytes, orientation_values.
payer_notesstringThe payer's own note, unedited. Null when the payer provided none.
notes_vary_by_plan*booleanTrue when plans under this payer carry different notes or return policies.
return_policystringThe payer's own return policy, unedited.
procedures*arrayOne entry per requested code, in the order you sent them.
procedure_code*stringThe CDT code.
status*stringrequired, no_requirement_listed, depends_on_plan or unknown. no_requirement_listed means the payer listed nothing; it is not a statement that nothing is needed.
descriptionstringThe procedure description as the payer publishes it.
payer_saysstringThe payer's own wording, unedited. Show this rather than paraphrasing.
documentsarrayWhat the payer lists for this code.
kind*stringStable Claim House document kind, for example periodontal_chart, xray, narrative. Match on this, not on the label.
label*stringDisplay text, for example X-ray.
format*stringfilm or paper.
needs_date*booleanThe image must carry a date.
needs_orientation*booleanThe image must carry an orientation of left or right. Orientation is the mount side of the film, not the shape of the image.
optionsarrayPresent only for depends_on_plan: one group per distinct answer, with documents, plan_count and up to five example plans.
messagestringPlain explanation for the non-required statuses.
source*objectrules_as_of (when Claim House last refreshed), payer_last_updated (when this payer last changed theirs) and freshness (current or stale). Always show the date next to the guidance.

Responses

200Payer lists requirements
{
  "payer_id": "60054",
  "payer_name": "Aetna",
  "electronic_attachments": "available",
  "plan_selection": "not_needed",
  "plan": null,
  "narrative": {
    "allowed": true,
    "max_characters": 2000
  },
  "payer_reference_number": "allowed",
  "file_limits": {
    "formats": [
      "jpeg"
    ],
    "max_images": 127,
    "max_total_bytes": 15728640,
    "orientation_values": [
      "left",
      "right"
    ]
  },
  "payer_notes": "Codes available to Providers at WWW.AetnaDental.com",
  "notes_vary_by_plan": true,
  "return_policy": "All original x-rays and photos are returned to the dentist after 30 days. Digital (or paper) x-rays are not returned.",
  "procedures": [
    {
      "procedure_code": "D4341",
      "status": "required",
      "description": "periodontal scaling and root planing - four or more contiguous teeth or bounded teeth per quadrant",
      "payer_says": "Current dated periodontal charting and Radiographs, Chart notes that show whether or not a local anesthetic was administered, and details regarding the treatment performed, as well as length of appointment are required.",
      "documents": [
        {
          "kind": "periodontal_chart",
          "label": "Periodontal chart",
          "format": "paper",
          "needs_date": false,
          "needs_orientation": false
        },
        {
          "kind": "xray",
          "label": "X-ray",
          "format": "film",
          "needs_date": true,
          "needs_orientation": true
        },
        {
          "kind": "narrative",
          "label": "Narrative",
          "format": "paper",
          "needs_date": false,
          "needs_orientation": false
        },
        {
          "kind": "report",
          "label": "Report",
          "format": "paper",
          "needs_date": false,
          "needs_orientation": false
        }
      ],
      "options": [],
      "message": null
    },
    {
      "procedure_code": "D1110",
      "status": "no_requirement_listed",
      "documents": [],
      "options": [],
      "message": "This payer lists no attachment requirement for this code."
    }
  ],
  "message": null,
  "source": {
    "rules_as_of": "2026-09-19T17:36:08Z",
    "payer_last_updated": "2026-07-22T16:37:37Z",
    "freshness": "current"
  }
}
200Payer is not on the attachment network
{
  "payer_id": "72468",
  "payer_name": "ACS Benefit Services Inc",
  "electronic_attachments": "not_available",
  "plan_selection": "not_needed",
  "plan": null,
  "narrative": {
    "allowed": false,
    "max_characters": 2000
  },
  "payer_reference_number": "not_accepted",
  "file_limits": {
    "formats": [
      "jpeg"
    ],
    "max_images": 127,
    "max_total_bytes": 15728640,
    "orientation_values": [
      "left",
      "right"
    ]
  },
  "payer_notes": null,
  "notes_vary_by_plan": false,
  "return_policy": null,
  "procedures": [],
  "message": "This payer does not accept electronic attachments through Claim House.",
  "source": {
    "rules_as_of": "2026-09-19T17:36:08Z",
    "payer_last_updated": null,
    "freshness": "current"
  }
}
400Malformed procedure code
{
  "error": {
    "code": "INVALID_PROCEDURE_CODE",
    "message": "procedure_codes must be CDT codes such as D4341",
    "errors": [
      {
        "field": "procedure_codes",
        "value": "4341"
      }
    ]
  }
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
400INVALID_PROCEDURE_CODEA code is malformed, the list is empty, or more than 50 were sent
404PAYER_UNKNOWNNo payer source Claim House holds knows this identifier
422PLAN_NOT_FOR_PAYERplan_id does not belong to that payer
403FACILITY_NOT_GRANTEDYour key has no access to facility_id
503ATTACHMENT_RULES_UNAVAILABLENo payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it
InfoAnswers carry an ETag and Cache-Control: private, max-age=300. Payer rules change weekly at most, so conditional requests are cheap.
InfoGuidance is advisory. Treat a failure as non-fatal and continue the claim workflow rather than blocking on it.
API reference

Get attachment requirements in bulk

POSThttps://api.claimhouse.ai/v1/attachment-requirements/batch

Requirements for many payer and code groups in one call.

Use this when you are about to submit a batch. A thousand claim batch resolves in one request rather than one request per claim.

Each result carries the index of the group it answers. A group that fails carries an error instead of failing the whole batch.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Not shown in the dashboard.

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Request body

NameTypeDescription
groups*array1 to 500 groups. Each group takes payer_id, procedure_codes (1 to 50), and optionally plan_id and facility_id.
payer_id*stringThe claim payer id.
procedure_codes*arrayArray of CDT codes, at most 50.
plan_idstringOpaque plan id.
facility_idstringOffice asking, for the remembered plan choice.

Request example

{
  "groups": [
    {
      "payer_id": "60054",
      "procedure_codes": [
        "D4341"
      ]
    },
    {
      "payer_id": "CX014",
      "procedure_codes": [
        "D2950"
      ]
    }
  ]
}

Response fields

NameTypeDescription
results*arrayOne entry per group, in request order.
index*integerThe index of the group this answers.
payer_id*stringEchoed payer id.
resultobjectThe same shape as the single lookup. Null when the group failed.
errorobjectcode and message when that one group could not be answered.

Responses

200OK
{
  "results": [
    {
      "index": 0,
      "payer_id": "60054",
      "result": {
        "payer_id": "60054",
        "payer_name": "Aetna",
        "electronic_attachments": "available",
        "plan_selection": "not_needed",
        "plan": null,
        "narrative": {
          "allowed": true,
          "max_characters": 2000
        },
        "payer_reference_number": "allowed",
        "file_limits": {
          "formats": [
            "jpeg"
          ],
          "max_images": 127,
          "max_total_bytes": 15728640,
          "orientation_values": [
            "left",
            "right"
          ]
        },
        "payer_notes": "Codes available to Providers at WWW.AetnaDental.com",
        "notes_vary_by_plan": true,
        "return_policy": "All original x-rays and photos are returned to the dentist after 30 days. Digital (or paper) x-rays are not returned.",
        "procedures": [
          {
            "procedure_code": "D4341",
            "status": "required",
            "description": "periodontal scaling and root planing - four or more contiguous teeth or bounded teeth per quadrant",
            "payer_says": "Current dated periodontal charting and Radiographs, Chart notes that show whether or not a local anesthetic was administered, and details regarding the treatment performed, as well as length of appointment are required.",
            "documents": [
              {
                "kind": "periodontal_chart",
                "label": "Periodontal chart",
                "format": "paper",
                "needs_date": false,
                "needs_orientation": false
              },
              {
                "kind": "xray",
                "label": "X-ray",
                "format": "film",
                "needs_date": true,
                "needs_orientation": true
              },
              {
                "kind": "narrative",
                "label": "Narrative",
                "format": "paper",
                "needs_date": false,
                "needs_orientation": false
              },
              {
                "kind": "report",
                "label": "Report",
                "format": "paper",
                "needs_date": false,
                "needs_orientation": false
              }
            ],
            "options": [],
            "message": null
          },
          {
            "procedure_code": "D1110",
            "status": "no_requirement_listed",
            "documents": [],
            "options": [],
            "message": "This payer lists no attachment requirement for this code."
          }
        ],
        "message": null,
        "source": {
          "rules_as_of": "2026-09-19T17:36:08Z",
          "payer_last_updated": "2026-07-22T16:37:37Z",
          "freshness": "current"
        }
      },
      "error": null
    },
    {
      "index": 1,
      "payer_id": "ZZZZZ",
      "result": null,
      "error": {
        "code": "PAYER_UNKNOWN",
        "message": "no payer source holds this identifier"
      }
    }
  ]
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
422INVALID_REQUESTMore than 500 groups, or a group with no procedure codes
503ATTACHMENT_RULES_UNAVAILABLENo payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it
InfoPrefer this over looping the single lookup. It is one round trip and one audit row for the whole batch.
API reference

Eligibility

Check a patient's dental coverage with the eligibility vendor, store the answer, and render it as a PDF.

ImportantBeta. Checks and PDFs answer 503 ELIGIBILITY_UNAVAILABLE when eligibility is not configured on the gateway, and the deployed vendor configuration was not confirmed when these docs were written. Reading stored answers and office history works regardless.
  • A check makes at most one vendor call and Claim House never retries it. If the vendor rejects the access token, Claim House fetches a new token once and posts the same check once more; a second rejection is AUTH_FAILED.
  • A timeout, connection failure or unreadable answer is stored as a failure with its own eligibility_id and returned as 502 VENDOR_ERROR with an error_class. It is never served from cache.
  • An uncertain outcome is not a success. Before checking again, read GET /v1/eligibility/{eligibility_id} or the office history. Checking again with the same Idempotency-Key replays the stored failure; a live retry needs a new key, and that is your decision.
  • Every answer states absence explicitly: optional values are null and not_returned names each field the payer did not return, so absent is never read as zero.
ModeWhat happens
standard (default)The vendor's standard eligibility answer.
enhancedThe vendor's enhanced answer, adding plan number, issuing state, benefit period, procedure_benefits, network_tiers, coordination_of_benefits, eligibility_flags and payer_notes. 503 ENHANCED_ELIGIBILITY_UNAVAILABLE when disabled on the gateway; 502 VENDOR_ERROR with AUTH_FAILED when your vendor access is not provisioned for it.
autoenhanced when the payer is on the enhanced allowlist (42 carrier ids), otherwise standard. The resolved mode is returned as mode.
  • A successful answer for the same office, payer, subscriber, dependent, service date and resolved mode is served from cache for your account's window (default 3600 seconds; 0 disables it). Names, member id and group number match case insensitively.
  • A cache hit returns the original eligibility_id with cache.state: "hit" and cache.age_seconds, makes no vendor call, writes no new history row and does not record your Idempotency-Key.
  • Procedure codes are not part of the cache key, so a cached answer may show a different procedure_codes list. Send Cache-Control: no-cache (the dashboard's "Skip the answer we already have from today") when you need a live answer or benefit detail for specific codes.
You sendResult
New Idempotency-KeyCache hit if one matches, else one live vendor call.
Same key, same contentThe recorded answer replayed with idempotent_replay: true, or the recorded failure as 502 VENDOR_ERROR. No vendor call.
Same key, different content422 IDEMPOTENCY_KEY_REUSED. Content is office, payer, subscriber and dependent identity, service date, procedure codes and resolved mode.
Same new key, two requests at onceNot locked. Both can reach the vendor and one answers 500. Check the history before sending again.
No key400 IDEMPOTENCY_KEY_REQUIRED.
API reference

Check eligibility

POSThttps://api.claimhouse.ai/v1/eligibility

Check one subscriber, or one dependent, with one payer for one office and store the answer.

Use it before treatment or before building a claim. Claim House calls the eligibility vendor at most once, never retries, and returns a normalized dental answer that never echoes the names, dates of birth or member ids you sent.

A matching answer from the cache window is returned without a vendor call. Send Cache-Control: no-cache to force a live check.

Permission
submit
Idempotency
required
Side effects
Contacts the eligibility vendor exactly once unless the answer is replayed or served from cache, stores one result row (success or vendor failure), and emits eligibility.checked. With include=pdf also stores a PDF artifact.
In the dashboard
Eligibility > New check

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Idempotency-Key*stringAny non blank string, unique per check you intend. Keys are unique per seller for all time. OpenAPI marks it optional; the gateway requires it.
e.g. elig-EXAMPLE-0001
Cache-ControlstringAny value containing no-cache skips the cache and makes a live vendor call.
e.g. no-cache
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Query parameters

NameTypeDescription
modestringWhich eligibility path to use. auto picks enhanced when the payer is on the enhanced allowlist, else standard. Overrides a body mode. Default standard.
one of: standard · enhanced · auto
e.g. auto
includestringComma list. pdf renders a PDF of the answer and adds artifact_id and pdf_filename to the response.
e.g. pdf

Request body

NameTypeDescription
tradingPartnerServiceId*stringThe payer. Primary payer id or internal payer id, exact match (aliases are not accepted). 1 to 80 letters, digits, ., -, _ or spaces.
e.g. EXMPL
facilityId*stringThe office the check is for. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
providerobjectAccepted for compatibility. The office's registered billing NPI and tax id are always what the payer receives.
npistring10 digit NPI with a valid check digit. If it differs from the office's billing NPI the check is refused with 403 BILLING_IDENTITY_MISMATCH.
e.g. 1234567893
organizationNamestringAt most 60 characters. Ignored.
firstNamestringAt most 35 characters. Ignored.
lastNamestringAt most 60 characters. Ignored.
taxIdstring9 digits. Ignored.
subscriber*objectThe policy holder.
memberId*stringMember id, at most 80 characters. Required by rule for the subscriber: a missing or null value is 422 with finding SUBSCRIBER_MEMBER_ID_REQUIRED.
e.g. SYN000123456
firstName*stringFirst name, 1 to 35 characters.
e.g. Sample
lastName*stringLast name, 1 to 60 characters.
e.g. Subscriber
dateOfBirth*stringDate of birth as YYYYMMDD (not YYYY-MM-DD). Must be a real date not after the service date.
e.g. 19800101
groupNumberstringGroup number, at most 50 characters.
dependentsarrayAt most one item: the patient when the patient is not the subscriber.
memberIdstringMember id, at most 80 characters. Optional for a dependent.
e.g. SYN000123456
firstName*stringFirst name, 1 to 35 characters.
e.g. Sample
lastName*stringLast name, 1 to 60 characters.
e.g. Patient
dateOfBirth*stringDate of birth as YYYYMMDD (not YYYY-MM-DD). Must be a real date not after the service date.
e.g. 20150101
groupNumberstringGroup number, at most 50 characters.
relationshipToSubscriberCode*stringTwo digit code: 01 spouse, 19 child, any other code is read as other. Missing is 422 INVALID_ELIGIBILITY_REQUEST with PATIENT_RELATIONSHIP_REQUIRED.
e.g. 19
encounterobjectService detail.
dateOfServicestringService date as YYYYMMDD. Defaults to today (UTC).
e.g. 20260914
procedureCodestringOne CDT code, D plus 4 digits. Merged with procedureCodes.
e.g. D1120
procedureCodesarrayArray of strings. CDT codes for benefit detail. The merged list may hold at most 10 codes.
e.g. ["D1120"]
serviceTypeCodesarrayArray of strings, at most 10. Accepted and ignored on the dental rail.
tenantReferencestringYour reference, at most 200 characters. Stored and returned.
e.g. visit-EXAMPLE-1

Request example

{
  "tradingPartnerServiceId": "FULLPAYER",
  "facilityId": "fac_q5s09nzww25ysd5a2f3g",
  "subscriber": {
    "memberId": "SYN000123456",
    "firstName": "SAMPLE",
    "lastName": "SUBSCRIBER",
    "dateOfBirth": "19800101"
  },
  "dependents": [
    {
      "firstName": "SAMPLE",
      "lastName": "PATIENT",
      "dateOfBirth": "20150101",
      "relationshipToSubscriberCode": "19"
    }
  ],
  "encounter": {
    "dateOfService": "20260907",
    "procedureCodes": [
      "D1120"
    ]
  },
  "tenantReference": "visit-0001"
}

Response fields

NameTypeDescription
request_id*stringRequest id.
idempotent_replay*booleanTrue when the recorded answer for this Idempotency-Key was replayed without a vendor call.
eligibility_id*stringThe check id, elg_.... A cache hit returns the original check's id.
e.g. elg_EXAMPLE0000000000001
facility_id*stringThe office the check was made for.
e.g. fac_EXAMPLE0000000000001
payer_id*stringThe payer id you sent.
e.g. EXMPL
mode*stringThe resolved mode. auto resolves to one of these before the call.
one of: standard · enhanced
relationship*stringself for a subscriber check, else the dependent's relationship.
one of: self · spouse · child · other
service_date*dateISO date the check was made for.
procedure_codes*arrayArray of strings. CDT codes sent on the original check. On a cache hit these are the original check's codes, not yours.
tenant_referencestringYour reference. Null when you did not send one.
status*stringCoverage status. UNKNOWN when not returned or not recognized.
one of: ACTIVE · INACTIVE · UNKNOWN
coverage*objectCoverage dates.
effective_datedateCoverage start. Null when not returned.
termination_datedateCoverage end. Null when not returned.
plan*objectPlan detail. Enhanced mode adds number, issuing_state and benefit_period.
namestringPlan name. Null when not returned.
typestringPlan type. Null when not returned.
group_numberstringGroup number. Null when not returned.
numberstringEnhanced mode only. Plan number. Null when not returned.
issuing_statestringEnhanced mode only. Issuing state. Null when not returned.
benefit_periodstringEnhanced mode only. Benefit period. Null when not returned.
network_statusstringNetwork status for the office. Null when not returned.
one of: IN_NETWORK · OUT_OF_NETWORK · BOTH
deductibles*arrayDeductible benefit lines. Every item field may be null.
sectionstringBenefit section, for example deductibles. Null when not returned.
networkstringNetwork, for example in_network. Null when not returned.
categorystringService category. Null when not returned.
service_typestringService type. Null when not returned.
coverage_levelstringCoverage level, for example individual or family. Null when not returned.
plan_periodstringPlan period, for example calendar_year. Null when not returned.
procedure_codestringCDT code the line applies to. Null when not returned.
insurance_typestringInsurance type. Null when not returned.
amountstringMoney amount as a decimal string. Null when not returned.
remainingstringRemaining amount as a decimal string. Null when not returned.
usedstringUsed amount as a decimal string. Null when not returned.
percentstringPercent as a decimal string. Null when not returned.
descriptionstringPayer description. Null when not returned.
start_datedateStart date. Null when not returned.
end_datedateEnd date. Null when not returned.
maximums*arrayAnnual or lifetime maximum lines, same item shape as deductibles.
coinsurance*arrayCoinsurance lines, same item shape as deductibles.
copayments*arrayCopayment lines, same item shape as deductibles.
not_covered*arrayNot covered lines, same item shape as deductibles.
frequency_limitations*arrayFrequency or quantity limits.
networkstringNetwork. Null when not returned.
categorystringService category. Null when not returned.
service_typestringService type. Null when not returned.
procedure_codestringCDT code. Null when not returned.
plan_periodstringPlan period. Null when not returned.
quantitystringAllowed quantity. Null when not returned.
quantity_remainingstringQuantity remaining. Null when not returned.
quantity_usedstringQuantity used. Null when not returned.
descriptionstringPayer description. Null when not returned.
start_datedateStart date. Null when not returned.
end_datedateEnd date. Null when not returned.
waiting_periods*arrayWaiting periods.
networkstringNetwork. Null when not returned.
categorystringService category. Null when not returned.
service_typestringService type. Null when not returned.
procedure_codestringCDT code. Null when not returned.
descriptionstringPayer description. Null when not returned.
start_datedateStart date. Null when not returned.
end_datedateEnd date. Null when not returned.
missing_tooth_clauseobjectMissing tooth clause. Null when not returned.
textstringPayer text.
appliesbooleanFalse only when the payer's text negates the clause.
downgrade_rules*arrayDowngrade rules.
textstringPayer text.
categorystringService category.
service_typestringService type.
procedure_codestringCDT code.
tooth_history*arrayPrior services by tooth.
toothstringTooth.
procedure_codestringCDT code.
service_datedateDate of service.
descriptionstringPayer description.
procedure_benefitsarrayEnhanced mode only. Per code benefit detail: procedure_code, description, network, category, covered, coinsurance_percent, copayment, deductible_applies, frequency, age and waiting period fields, usage_count, remaining_usage, last_service_date, next_eligible_date, disclaimers.
network_tiersarrayEnhanced mode only. Items carry network, status, description.
coordination_of_benefitsobjectEnhanced mode only. Carries other_coverage and description.
eligibility_flagsarrayEnhanced mode only. Items carry name, value, description.
payer_notesarrayEnhanced mode only. Array of strings.
disclaimers*arrayArray of strings. Payer disclaimers.
not_returned*arrayArray of strings. Each dental field the payer did not return, so absent is never read as zero.
one of: coverage_status · coverage_effective_date · coverage_termination_date · plan_name · network_status · deductibles · maximums · coinsurance · frequency_limitations · waiting_periods · missing_tooth_clause · downgrade_rules · tooth_history · procedure_benefits · network_tiers
warnings*arrayArray of strings. Parse warnings such as UNREADABLE_DATE or UNRECOGNIZED_COVERAGE_STATUS. They name the field, never the value.
source*objectWhere the answer came from.
vendor*stringEligibility vendor name.
transaction_idstringVendor transaction id. Null when none was read.
retrieved_at*datetimeWhen the vendor call was made.
vendor_latency_ms*integerVendor latency in milliseconds.
cache*objectWhether this answer came from the cache.
state*stringfresh for a live call, hit when served from cache.
one of: fresh · hit
age_secondsintegerAge of the cached answer. 0 when fresh.
artifact_idstringOnly with include=pdf. Pass it to GET /v1/artifacts/{artifact_id} for a download link.
pdf_filenamestringOnly with include=pdf. Always elig_<eligibility_id>.pdf, never a patient name.

Responses

200OK
{
  "request_id": "evt_nk9njxq4qqjtbfnh6rxb",
  "idempotent_replay": false,
  "eligibility_id": "elg_hp130jp45188vw4wk5tz",
  "facility_id": "fac_q5s09nzww25ysd5a2f3g",
  "payer_id": "FULLPAYER",
  "mode": "standard",
  "relationship": "child",
  "service_date": "2026-09-07",
  "procedure_codes": [
    "D1120"
  ],
  "tenant_reference": "visit-0001",
  "status": "ACTIVE",
  "coverage": {
    "effective_date": "2026-01-01",
    "termination_date": "2026-12-31"
  },
  "plan": {
    "name": "Test Dental PPO",
    "type": "PPO",
    "group_number": "TESTGRP1"
  },
  "network_status": "IN_NETWORK",
  "deductibles": [
    {
      "section": "deductible",
      "network": "in_network",
      "category": null,
      "service_type": null,
      "coverage_level": "individual",
      "plan_period": "calendar",
      "procedure_code": null,
      "insurance_type": null,
      "amount": "50.00",
      "remaining": "25.00",
      "used": null,
      "percent": null,
      "description": null,
      "start_date": null,
      "end_date": null
    },
    {
      "section": "deductible",
      "network": "in_network",
      "category": null,
      "service_type": null,
      "coverage_level": "family",
      "plan_period": "calendar",
      "procedure_code": null,
      "insurance_type": null,
      "amount": "150.00",
      "remaining": "150.00",
      "used": null,
      "percent": null,
      "description": null,
      "start_date": null,
      "end_date": null
    }
  ],
  "maximums": [
    {
      "section": "maximums",
      "network": "in_network",
      "category": null,
      "service_type": null,
      "coverage_level": null,
      "plan_period": "calendar",
      "procedure_code": null,
      "insurance_type": null,
      "amount": "1500.00",
      "remaining": "1200.00",
      "used": "300.00",
      "percent": null,
      "description": "Annual Maximum",
      "start_date": null,
      "end_date": null
    },
    {
      "section": "maximums",
      "network": "in_network",
      "category": null,
      "service_type": "orthodontics",
      "coverage_level": null,
      "plan_period": "lifetime",
      "procedure_code": null,
      "insurance_type": null,
      "amount": "1000.00",
      "remaining": "1000.00",
      "used": null,
      "percent": null,
      "description": null,
      "start_date": null,
      "end_date": null
    }
  ],
  "coinsurance": [
    {
      "section": "coinsurance",
      "network": "in_network",
      "category": "preventive",
      "service_type": null,
      "coverage_level": null,
      "plan_period": null,
      "procedure_code": null,
      "insurance_type": null,
      "amount": null,
      "remaining": null,
      "used": null,
      "percent": "100",
      "description": null,
      "start_date": null,
      "end_date": null
    },
    {
      "section": "coinsurance",
      "network": "in_network",
      "category": "basic",
      "service_type": null,
      "coverage_level": null,
      "plan_period": null,
      "procedure_code": null,
      "insurance_type": null,
      "amount": null,
      "remaining": null,
      "used": null,
      "percent": "80",
      "description": null,
      "start_date": null,
      "end_date": null
    },
    {
      "section": "coinsurance",
      "network": "in_network",
      "category": "major",
      "service_type": null,
      "coverage_level": null,
      "plan_period": null,
      "procedure_code": null,
      "insurance_type": null,
      "amount": null,
      "remaining": null,
      "used": null,
      "percent": "50",
      "description": null,
      "start_date": null,
      "end_date": null
    },
    {
      "section": "coinsurance",
      "network": "in_network",
      "category": null,
      "service_type": null,
      "coverage_level": null,
      "plan_period": null,
      "procedure_code": "D2740",
      "insurance_type": null,
      "amount": null,
      "remaining": null,
      "used": null,
      "percent": "50",
      "description": null,
      "start_date": null,
      "end_date": null
    }
  ],
  "copayments": [],
  "not_covered": [
    {
      "section": "not_covered",
      "network": null,
      "category": null,
      "service_type": null,
      "coverage_level": null,
      "plan_period": null,
      "procedure_code": "D9972",
      "insurance_type": null,
      "amount": null,
      "remaining": null,
      "used": null,
      "percent": null,
      "description": "Cosmetic bleaching",
      "start_date": null,
      "end_date": null
    }
  ],
  "frequency_limitations": [
    {
      "network": null,
      "category": "preventive",
      "service_type": null,
      "procedure_code": "D1110",
      "plan_period": "calendar",
      "quantity": 2,
      "quantity_remaining": 1,
      "quantity_used": 1,
      "description": "2 per calendar year",
      "start_date": null,
      "end_date": null
    },
    {
      "network": null,
      "category": null,
      "service_type": null,
      "procedure_code": "D0274",
      "plan_period": null,
      "quantity": 1,
      "quantity_remaining": 1,
      "quantity_used": null,
      "description": "1 per 12 months",
      "start_date": null,
      "end_date": null
    }
  ],
  "waiting_periods": [
    {
      "network": null,
      "category": "major",
      "service_type": null,
      "procedure_code": null,
      "description": "12 month waiting period for major services",
      "start_date": "2026-01-01",
      "end_date": "2026-12-31"
    }
  ],
  "missing_tooth_clause": {
    "text": "Missing Tooth Clause applies to teeth extracted prior to coverage",
    "applies": true
  },
  "downgrade_rules": [
    {
      "text": "Posterior composites downgraded to amalgam (alternate benefit)",
      "category": "restorative",
      "service_type": null,
      "procedure_code": null
    }
  ],
  "tooth_history": [
    {
      "tooth": "30",
      "procedure_code": "D2740",
      "service_date": "2024-03-10",
      "description": "Crown placed"
    }
  ],
  "disclaimers": [
    "Missing Tooth Clause applies to teeth extracted prior to coverage",
    "UCR applies"
  ],
  "not_returned": [],
  "warnings": [],
  "source": {
    "vendor": "onederful",
    "transaction_id": "fake-txn-full-0001"
  },
  "retrieved_at": "2026-09-07T15:00:00+00:00",
  "vendor_latency_ms": 0,
  "cache": {
    "state": "fresh",
    "age_seconds": 0
  },
  "artifact_id": "art_ktkbnvrcxbjpsrzytwet",
  "pdf_filename": "elig_elg_c04ysmvj0h58513dt65t.pdf"
}
502Vendor did not answer
{
  "error": "VENDOR_ERROR",
  "message": "the eligibility vendor did not answer this check",
  "errors": [
    {
      "eligibility_id": "elg_wddgq19nhagdyseyyhte",
      "error_class": "TIMEOUT",
      "vendor_status": null,
      "mode": "standard"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
422Idempotency key reused
{
  "error": "IDEMPOTENCY_KEY_REUSED",
  "message": "this Idempotency-Key was used with different content",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
422Request rule findings
{
  "error": "INVALID_ELIGIBILITY_REQUEST",
  "message": "invalid eligibility request",
  "errors": [
    {
      "findings": [
        "SUBSCRIBER_MEMBER_ID_REQUIRED"
      ]
    }
  ],
  "request_id": "evt_47qjfbxj2q2rzbvq8zes"
}

Errors

StatusCodeWhen
400IDEMPOTENCY_KEY_REQUIREDIdempotency-Key header missing or blank
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDOffice unknown to your account or not granted (never 404)
403BILLING_IDENTITY_MISMATCHprovider.npi differs from the office billing NPI
422INVALID_REQUESTPath, query or body fails the schema
422INVALID_ELIGIBILITY_REQUESTDependent relationship missing, or request rule findings in errors[0].findings
422PAYER_NOT_SUPPORTED_FOR_ELIGIBILITYPayer not found by primary or internal id, or has no eligibility mapping
422IDEMPOTENCY_KEY_REUSEDKey used before with different content
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
502VENDOR_ERRORVendor did not answer usefully, or its answer could not be read
503ELIGIBILITY_UNAVAILABLEEligibility is not configured on this gateway
503ENHANCED_ELIGIBILITY_UNAVAILABLEResolved mode is enhanced and enhanced is disabled
ImportantBeta: the route answers 503 ELIGIBILITY_UNAVAILABLE when the gateway has no eligibility vendor configured, and the deployed vendor configuration was not confirmed when these docs were written. Expect this status until your account is enabled for eligibility.
InfoVENDOR_ERROR detail: errors[0] carries eligibility_id, error_class (AUTH_FAILED, TIMEOUT, CONNECTION_ERROR, VENDOR_UNAVAILABLE, VENDOR_REJECTED, MALFORMED_RESPONSE, SECRET_UNAVAILABLE), vendor_status and mode. Nothing of the vendor body is returned. The failure is stored and can be read with GET /v1/eligibility/{eligibility_id}.
InfoSecond body shape: the route also accepts snake_case fields facility_id, payer_id, subscriber (first_name, last_name, date_of_birth as YYYY-MM-DD, member_id, group_number), patient (same fields plus required relationship: spouse, child or other), service_date, procedure_codes, tenant_reference and mode. Unknown fields are ignored there; in the shape above unknown fields are refused, so pass mode as a query parameter with it. A body that fits neither shape returns 422 with errors for both.
InfoRequest rule findings (422 INVALID_ELIGIBILITY_REQUEST): SUBSCRIBER_MEMBER_ID_REQUIRED, SUBSCRIBER_NAME_REQUIRED, DATE_OF_BIRTH_IN_FUTURE, PATIENT_NAME_REQUIRED, PATIENT_RELATIONSHIP_REQUIRED, PATIENT_RELATIONSHIP_INVALID, PATIENT_IS_SUBSCRIBER, TOO_MANY_PROCEDURE_CODES, PROCEDURE_CODE_INVALID. PATIENT_IS_SUBSCRIBER means the dependent's name, date of birth, member id and group all equal the subscriber's.
ImportantTwo concurrent requests with the same new Idempotency-Key are not locked against each other. Both can reach the vendor and the second answers 500. Treat a 500 as uncertain: read GET /v1/facilities/{facility_id}/eligibility before sending the check again.
InfoWith include=pdf, a PDF failure (404, 422 or 500) happens after the check was recorded. Retrying with the same Idempotency-Key replays the check without a vendor call and tries the PDF again.
InfoA test key makes a real vendor call. There is no test mode for eligibility.
API reference

Get an eligibility answer

GEThttps://api.claimhouse.ai/v1/eligibility/{eligibility_id}

Read a stored check: the answer the check returned, or the failure record when the vendor did not answer.

Returns the same answer body as the check, with outcome added. A failed check returns a short record with status: UNKNOWN, outcome: VENDOR_ERROR and the error_class.

The stored body is the one from the live call, so cache.state is fresh even if you later received it as a cache hit. This route works even when eligibility checks are not configured.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. Never contacts the eligibility vendor.
In the dashboard
Eligibility > New check (answer card), Eligibility > Previous checks

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
eligibility_id*stringThe check id, elg_..., 1 to 64 characters.
e.g. elg_hp130jp45188vw4wk5tz

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id.
outcome*stringOK for an answer, VENDOR_ERROR for a recorded vendor failure.
one of: OK · VENDOR_ERROR
error_classstringOnly on a failure record. The vendor error class, for example TIMEOUT.
eligibility_id*stringThe check id, elg_.... A cache hit returns the original check's id.
e.g. elg_EXAMPLE0000000000001
facility_id*stringThe office the check was made for.
e.g. fac_EXAMPLE0000000000001
payer_id*stringThe payer id you sent.
e.g. EXMPL
mode*stringThe resolved mode. auto resolves to one of these before the call.
one of: standard · enhanced
relationship*stringself for a subscriber check, else the dependent's relationship.
one of: self · spouse · child · other
service_date*dateISO date the check was made for.
procedure_codes*arrayArray of strings. CDT codes sent on the original check. On a cache hit these are the original check's codes, not yours.
tenant_referencestringYour reference. Null when you did not send one.
status*stringCoverage status. UNKNOWN when not returned or not recognized.
one of: ACTIVE · INACTIVE · UNKNOWN
coverage*objectCoverage dates.
effective_datedateCoverage start. Null when not returned.
termination_datedateCoverage end. Null when not returned.
plan*objectPlan detail. Enhanced mode adds number, issuing_state and benefit_period.
namestringPlan name. Null when not returned.
typestringPlan type. Null when not returned.
group_numberstringGroup number. Null when not returned.
numberstringEnhanced mode only. Plan number. Null when not returned.
issuing_statestringEnhanced mode only. Issuing state. Null when not returned.
benefit_periodstringEnhanced mode only. Benefit period. Null when not returned.
network_statusstringNetwork status for the office. Null when not returned.
one of: IN_NETWORK · OUT_OF_NETWORK · BOTH
deductibles*arrayDeductible benefit lines. Every item field may be null.
sectionstringBenefit section, for example deductibles. Null when not returned.
networkstringNetwork, for example in_network. Null when not returned.
categorystringService category. Null when not returned.
service_typestringService type. Null when not returned.
coverage_levelstringCoverage level, for example individual or family. Null when not returned.
plan_periodstringPlan period, for example calendar_year. Null when not returned.
procedure_codestringCDT code the line applies to. Null when not returned.
insurance_typestringInsurance type. Null when not returned.
amountstringMoney amount as a decimal string. Null when not returned.
remainingstringRemaining amount as a decimal string. Null when not returned.
usedstringUsed amount as a decimal string. Null when not returned.
percentstringPercent as a decimal string. Null when not returned.
descriptionstringPayer description. Null when not returned.
start_datedateStart date. Null when not returned.
end_datedateEnd date. Null when not returned.
maximums*arrayAnnual or lifetime maximum lines, same item shape as deductibles.
coinsurance*arrayCoinsurance lines, same item shape as deductibles.
copayments*arrayCopayment lines, same item shape as deductibles.
not_covered*arrayNot covered lines, same item shape as deductibles.
frequency_limitations*arrayFrequency or quantity limits.
networkstringNetwork. Null when not returned.
categorystringService category. Null when not returned.
service_typestringService type. Null when not returned.
procedure_codestringCDT code. Null when not returned.
plan_periodstringPlan period. Null when not returned.
quantitystringAllowed quantity. Null when not returned.
quantity_remainingstringQuantity remaining. Null when not returned.
quantity_usedstringQuantity used. Null when not returned.
descriptionstringPayer description. Null when not returned.
start_datedateStart date. Null when not returned.
end_datedateEnd date. Null when not returned.
waiting_periods*arrayWaiting periods.
networkstringNetwork. Null when not returned.
categorystringService category. Null when not returned.
service_typestringService type. Null when not returned.
procedure_codestringCDT code. Null when not returned.
descriptionstringPayer description. Null when not returned.
start_datedateStart date. Null when not returned.
end_datedateEnd date. Null when not returned.
missing_tooth_clauseobjectMissing tooth clause. Null when not returned.
textstringPayer text.
appliesbooleanFalse only when the payer's text negates the clause.
downgrade_rules*arrayDowngrade rules.
textstringPayer text.
categorystringService category.
service_typestringService type.
procedure_codestringCDT code.
tooth_history*arrayPrior services by tooth.
toothstringTooth.
procedure_codestringCDT code.
service_datedateDate of service.
descriptionstringPayer description.
procedure_benefitsarrayEnhanced mode only. Per code benefit detail: procedure_code, description, network, category, covered, coinsurance_percent, copayment, deductible_applies, frequency, age and waiting period fields, usage_count, remaining_usage, last_service_date, next_eligible_date, disclaimers.
network_tiersarrayEnhanced mode only. Items carry network, status, description.
coordination_of_benefitsobjectEnhanced mode only. Carries other_coverage and description.
eligibility_flagsarrayEnhanced mode only. Items carry name, value, description.
payer_notesarrayEnhanced mode only. Array of strings.
disclaimers*arrayArray of strings. Payer disclaimers.
not_returned*arrayArray of strings. Each dental field the payer did not return, so absent is never read as zero.
one of: coverage_status · coverage_effective_date · coverage_termination_date · plan_name · network_status · deductibles · maximums · coinsurance · frequency_limitations · waiting_periods · missing_tooth_clause · downgrade_rules · tooth_history · procedure_benefits · network_tiers
warnings*arrayArray of strings. Parse warnings such as UNREADABLE_DATE or UNRECOGNIZED_COVERAGE_STATUS. They name the field, never the value.
source*objectWhere the answer came from.
vendor*stringEligibility vendor name.
transaction_idstringVendor transaction id. Null when none was read.
retrieved_at*datetimeWhen the vendor call was made.
vendor_latency_ms*integerVendor latency in milliseconds.
cache*objectWhether this answer came from the cache.
state*stringfresh for a live call, hit when served from cache.
one of: fresh · hit
age_secondsintegerAge of the cached answer. 0 when fresh.

Responses

200OK
{
  "request_id": "evt_j5n01dg8asp116e4h1t1",
  "cache": {
    "age_seconds": 0,
    "state": "fresh"
  },
  "coinsurance": [
    {
      "amount": null,
      "category": "preventive",
      "coverage_level": null,
      "description": null,
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": "100",
      "plan_period": null,
      "procedure_code": null,
      "remaining": null,
      "section": "coinsurance",
      "service_type": null,
      "start_date": null,
      "used": null
    },
    {
      "amount": null,
      "category": "basic",
      "coverage_level": null,
      "description": null,
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": "80",
      "plan_period": null,
      "procedure_code": null,
      "remaining": null,
      "section": "coinsurance",
      "service_type": null,
      "start_date": null,
      "used": null
    },
    {
      "amount": null,
      "category": "major",
      "coverage_level": null,
      "description": null,
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": "50",
      "plan_period": null,
      "procedure_code": null,
      "remaining": null,
      "section": "coinsurance",
      "service_type": null,
      "start_date": null,
      "used": null
    },
    {
      "amount": null,
      "category": null,
      "coverage_level": null,
      "description": null,
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": "50",
      "plan_period": null,
      "procedure_code": "D2740",
      "remaining": null,
      "section": "coinsurance",
      "service_type": null,
      "start_date": null,
      "used": null
    }
  ],
  "copayments": [],
  "coverage": {
    "effective_date": "2026-01-01",
    "termination_date": "2026-12-31"
  },
  "deductibles": [
    {
      "amount": "50.00",
      "category": null,
      "coverage_level": "individual",
      "description": null,
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": null,
      "plan_period": "calendar",
      "procedure_code": null,
      "remaining": "25.00",
      "section": "deductible",
      "service_type": null,
      "start_date": null,
      "used": null
    },
    {
      "amount": "150.00",
      "category": null,
      "coverage_level": "family",
      "description": null,
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": null,
      "plan_period": "calendar",
      "procedure_code": null,
      "remaining": "150.00",
      "section": "deductible",
      "service_type": null,
      "start_date": null,
      "used": null
    }
  ],
  "disclaimers": [
    "Missing Tooth Clause applies to teeth extracted prior to coverage",
    "UCR applies"
  ],
  "downgrade_rules": [
    {
      "category": "restorative",
      "procedure_code": null,
      "service_type": null,
      "text": "Posterior composites downgraded to amalgam (alternate benefit)"
    }
  ],
  "eligibility_id": "elg_hp130jp45188vw4wk5tz",
  "facility_id": "fac_q5s09nzww25ysd5a2f3g",
  "frequency_limitations": [
    {
      "category": "preventive",
      "description": "2 per calendar year",
      "end_date": null,
      "network": null,
      "plan_period": "calendar",
      "procedure_code": "D1110",
      "quantity": 2,
      "quantity_remaining": 1,
      "quantity_used": 1,
      "service_type": null,
      "start_date": null
    },
    {
      "category": null,
      "description": "1 per 12 months",
      "end_date": null,
      "network": null,
      "plan_period": null,
      "procedure_code": "D0274",
      "quantity": 1,
      "quantity_remaining": 1,
      "quantity_used": null,
      "service_type": null,
      "start_date": null
    }
  ],
  "maximums": [
    {
      "amount": "1500.00",
      "category": null,
      "coverage_level": null,
      "description": "Annual Maximum",
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": null,
      "plan_period": "calendar",
      "procedure_code": null,
      "remaining": "1200.00",
      "section": "maximums",
      "service_type": null,
      "start_date": null,
      "used": "300.00"
    },
    {
      "amount": "1000.00",
      "category": null,
      "coverage_level": null,
      "description": null,
      "end_date": null,
      "insurance_type": null,
      "network": "in_network",
      "percent": null,
      "plan_period": "lifetime",
      "procedure_code": null,
      "remaining": "1000.00",
      "section": "maximums",
      "service_type": "orthodontics",
      "start_date": null,
      "used": null
    }
  ],
  "missing_tooth_clause": {
    "applies": true,
    "text": "Missing Tooth Clause applies to teeth extracted prior to coverage"
  },
  "mode": "standard",
  "network_status": "IN_NETWORK",
  "not_covered": [
    {
      "amount": null,
      "category": null,
      "coverage_level": null,
      "description": "Cosmetic bleaching",
      "end_date": null,
      "insurance_type": null,
      "network": null,
      "percent": null,
      "plan_period": null,
      "procedure_code": "D9972",
      "remaining": null,
      "section": "not_covered",
      "service_type": null,
      "start_date": null,
      "used": null
    }
  ],
  "not_returned": [],
  "payer_id": "FULLPAYER",
  "plan": {
    "group_number": "TESTGRP1",
    "name": "Test Dental PPO",
    "type": "PPO"
  },
  "procedure_codes": [
    "D1120"
  ],
  "relationship": "child",
  "retrieved_at": "2026-09-07T15:00:00+00:00",
  "service_date": "2026-09-07",
  "source": {
    "transaction_id": "fake-txn-full-0001",
    "vendor": "onederful"
  },
  "status": "ACTIVE",
  "tenant_reference": "visit-0001",
  "tooth_history": [
    {
      "description": "Crown placed",
      "procedure_code": "D2740",
      "service_date": "2024-03-10",
      "tooth": "30"
    }
  ],
  "vendor_latency_ms": 0,
  "waiting_periods": [
    {
      "category": "major",
      "description": "12 month waiting period for major services",
      "end_date": "2026-12-31",
      "network": null,
      "procedure_code": null,
      "service_type": null,
      "start_date": "2026-01-01"
    }
  ],
  "warnings": [],
  "outcome": "OK"
}
200Recorded vendor failure
{
  "request_id": "evt_5dee93bjtjsgwv27fgk2",
  "eligibility_id": "elg_wddgq19nhagdyseyyhte",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "payer_id": "EXMPL",
  "relationship": "self",
  "service_date": "2026-09-14",
  "status": "UNKNOWN",
  "outcome": "VENDOR_ERROR",
  "error_class": "TIMEOUT",
  "source": {
    "vendor": "eligibility_vendor",
    "transaction_id": null
  },
  "retrieved_at": "2026-09-14T15:20:00+00:00",
  "vendor_latency_ms": 15002,
  "tenant_reference": null
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such eligibility result",
  "errors": [],
  "request_id": "evt_wmkpgd7r9t71sskpmz6q"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route's permission
404NOT_FOUNDNo such check, or outside the key's office grants
422INVALID_REQUESTPath, query or body fails the schema
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
InfoA failure record carries only eligibility_id, facility_id, payer_id, relationship, service_date, status, outcome, error_class, source, retrieved_at, vendor_latency_ms and tenant_reference. There is no idempotent_replay field on this route.
API reference

Create an eligibility PDF

POSThttps://api.claimhouse.ai/v1/eligibility/{eligibility_id}/pdf

Render a PDF report of a successful check and store it as a downloadable artifact.

Renders from the vendor answer archived for that check, so there is no new vendor call. Fetch a short lived download link with GET /v1/artifacts/{artifact_id} (valid 600 seconds).

Each call renders and stores a new artifact with a new artifact_id. A failed check cannot produce a PDF.

Permission
submit
Idempotency
none
Side effects
Stores a new PDF artifact and emits eligibility.pdf_generated. No vendor call and no practice management system write.
In the dashboard
Eligibility > New check (Download PDF), Eligibility > Previous checks (PDF)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
eligibility_id*stringThe check id, elg_..., 1 to 64 characters. The check must have succeeded.
e.g. elg_hp130jp45188vw4wk5tz

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id.
eligibility_id*stringThe check.
artifact_id*stringThe stored PDF, art_.... Pass to GET /v1/artifacts/{artifact_id} for a download link.
filename*stringAlways elig_<eligibility_id>.pdf. No patient name.
bytes*integerPDF size in bytes.

Responses

200OK
{
  "request_id": "evt_9vw2py8b5f1xw79dykcg",
  "eligibility_id": "elg_hp130jp45188vw4wk5tz",
  "artifact_id": "art_7cx8qyshggtk72qstpjz",
  "filename": "elig_elg_c04ysmvj0h58513dt65t.pdf",
  "bytes": 7820
}
422Check was a vendor failure
{
  "error": "INVALID_ELIGIBILITY_REQUEST",
  "message": "PDF requires a successful eligibility result",
  "errors": [
    {
      "eligibility_id": "elg_wddgq19nhagdyseyyhte"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such eligibility result",
  "errors": [
    {
      "eligibility_id": "elg_5zgacj2z460gmw1ebbq7"
    }
  ],
  "request_id": "evt_y496y9sv80mh6vq5a73g"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDThe check's office is not granted to your key
404NOT_FOUNDNo such check for your account
422INVALID_REQUESTPath, query or body fails the schema
422INVALID_ELIGIBILITY_REQUESTThe check was a vendor failure or has no archived answer
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
503ELIGIBILITY_UNAVAILABLEEligibility is not configured on this gateway
ImportantBeta: like the check, this route answers 503 ELIGIBILITY_UNAVAILABLE when eligibility is not configured on the gateway, and the deployed configuration was not confirmed when these docs were written.
InfoA failure in the archive read or the renderer returns a 500 with no error code. Calling again is safe: it renders a new artifact and never contacts the vendor.
InfoOpenAPI does not declare the 403, 404 or 503 answers.
API reference

List an office's eligibility checks

GEThttps://api.claimhouse.ai/v1/facilities/{facility_id}/eligibility

List recorded checks for one office, newest first, with ids, codes and outcomes only.

Use it for history, reconciliation, and to find out what happened after an uncertain check before you send it again. Rows include vendor failures.

There is no cursor and no upper time bound: only since and limit. Cache hits are not stored rows and do not appear.

Permission
read
Idempotency
none
Side effects
Writes an audit read row listing the returned ids. Never contacts the eligibility vendor.
In the dashboard
Eligibility > Previous checks, Eligibility > New check (history below the form)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
facility_id*stringThe office, 1 to 64 characters. Must be granted to your key.
e.g. fac_q5s09nzww25ysd5a2f3g

Query parameters

NameTypeDescription
sincedatetimeOnly checks retrieved at or after this ISO 8601 time.
e.g. 2026-09-01T00:00:00Z
limitintegerRows to return, 1 to 500. Default 100.
e.g. 2

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id.
facility_id*stringThe office.
results*arrayChecks, newest first.
eligibility_id*stringThe check id, elg_....
facility_id*stringThe office.
payer_id*stringThe payer id sent.
relationship*stringWho was checked.
one of: self · spouse · child · other
service_date*dateService date.
status*stringCoverage status.
one of: ACTIVE · INACTIVE · UNKNOWN
outcome*stringOK or VENDOR_ERROR.
one of: OK · VENDOR_ERROR
error_classstringVendor error class on failures. Null on success.
vendor*stringEligibility vendor name.
vendor_transaction_idstringVendor transaction id. Null when none was read.
vendor_latency_ms*integerVendor latency in milliseconds.
tenant_referencestringYour reference. Null when not sent.
retrieved_at*datetimeWhen the vendor call was made.

Responses

200OK
{
  "request_id": "evt_hswemz9x4fvz88dzzgjg",
  "facility_id": "fac_q5s09nzww25ysd5a2f3g",
  "results": [
    {
      "eligibility_id": "elg_hp130jp45188vw4wk5tz",
      "facility_id": "fac_q5s09nzww25ysd5a2f3g",
      "payer_id": "FULLPAYER",
      "relationship": "child",
      "service_date": "2026-09-07",
      "status": "ACTIVE",
      "outcome": "OK",
      "error_class": null,
      "vendor": "onederful",
      "vendor_transaction_id": "fake-txn-full-0001",
      "vendor_latency_ms": 0,
      "tenant_reference": "visit-0001",
      "retrieved_at": "2026-09-07T15:00:00+00:00"
    }
  ]
}
403Office not granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "this key's grants do not cover that facility",
  "errors": [
    {
      "facility_id": "fac_w9qxypnbcw7b308jbbhh"
    }
  ],
  "request_id": "evt_raxyv96xhdgz5s57d4bd"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDOffice not covered by the key's grants
404NOT_FOUNDNo such office for your account
422INVALID_REQUESTPath, query or body fails the schema
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
Infomode is not on list rows. Read a single check for it.
ImportantWithout a cursor you cannot page past the newest 500 checks retrieved after a given since. Narrow since to walk older history.
API reference

Poll an eligibility operation

GEThttps://api.claimhouse.ai/v1/eligibility/{eligibility_id}/operation

Poll the state of one eligibility request, including archive and PDF status.

Eligibility answers asynchronously. This route reports the operation state, whether the raw request and response are durably archived, and the latest PDF artifact — without re-issuing the clinical question.

Reads are safe to repeat: polling never causes a second payer call.

Permission
read
Idempotency
none
Side effects
None. Read only.
In the dashboard
Eligibility > request detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
eligibility_id*stringThe eligibility id returned by POST /v1/eligibility.
e.g. elg_EXAMPLE0000000000000001

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
eligibility_id*stringThe eligibility id.
e.g. elg_EXAMPLE0000000000000001
facility_id*stringThe office the check ran under.
e.g. fac_EXAMPLE0000000000000001
environment*stringThe key mode the operation ran in, test or live.
one of: test · live
e.g. test
state*stringOperation state.
e.g. ANSWERED
response_mode*stringHow the answer was delivered.
e.g. realtime
result_idstringThe retained result id once answered. Null while in flight.
e.g. res_EXAMPLE0000000000000001
request_archived*booleanThe exact outbound request is durably archived.
e.g. true
response_archived*booleanThe raw payer response is durably archived.
e.g. true
pdfobjectLatest PDF artifact metadata when one has been generated.
e.g. {"artifact_id":"art_EXAMPLE0000000000000001","status":"ready"}

Responses

200OK
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "eligibility_id": "elg_35ka7045j25e5w5h2a32",
  "facility_id": "fac_9zm2x7v1kq8w3n4t6y5b",
  "environment": "test",
  "state": "ANSWERED",
  "response_mode": "realtime",
  "result_id": "res_4hw8cj2mdn6xpk9evtaq",
  "request_archived": true,
  "response_archived": true,
  "pdf": {
    "artifact_id": "art_7xk2m9nq4w8v6e3c1b5z",
    "status": "ready"
  }
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such eligibility operation",
  "errors": [],
  "request_id": "evt_ang0n1gexynjw0dmfh8v"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks read or the operation's office
404NOT_FOUNDno such eligibility operation in your seller, grants and key mode
503ELIGIBILITY_UNAVAILABLEthe eligibility rail is not configured on this gateway
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoPoll this route instead of re-submitting. Re-submitting the same patient question spends another payer request; polling never does.
API reference

Payments

Read the 835 remittances payers send for your claims, with each matched payment and its adjustments.

When a payer pays, denies or reverses a claim, the clearinghouse delivers an 835 remittance. Claim House reads each file, matches every claim payment line (CLP) to the claim it issued, records the payment and adjustments, and moves the claim state. These routes are read only.

  • A remittance (rem_...) is one 835 file. The same file received twice is stored once, so replays never create duplicate payments or events.
  • A payment (pay_...) is one CLP matched to one of your claims. Lines that match nothing are held for Claim House review and are not shown.
  • An 835 can cover many Claim House customers. You only see your own lines, and totals are computed over those lines only.
  • Amounts are strings with exactly two decimals, never floats, as the payer reported them. Reversal lines can be negative.
  • The complete raw 835 is downloadable through artifact_id with GET /v1/artifacts/{artifact_id}.
routed_byMatched when
PCNCLP01 equals the patient control number Claim House put on the claim.
PAYER_CLAIM_NUMBERCLP07 equals a payer claim number already known for the claim (from a 277).
ORIGINAL_REFERENCEREF*F8 equals a payer claim number or patient control number.
CLP02event_kind
1, 2, 3, 19, 20, 21claim.paid
4claim.denied
22claim.reversed
Any other codenull, the claim state does not change
API reference

List remittances

GEThttps://api.claimhouse.ai/v1/remittances

Page through the 835 files that contain at least one of your payment lines, newest received first.

Use it for a payments inbox or to find the remittance behind a bank deposit. Counts and totals cover only your lines within your key's grants; the file's grand total is never returned.

There is no filter by payer, trace number, payment date or amount.

Permission
read
Idempotency
none
Side effects
Writes an audit row listing the returned remittance ids. No vendor call.
In the dashboard
Payments, Overview > Money in

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
facility_idstringOnly count and list payment lines for this office, at most 64 characters. Must be granted to your key.
e.g. fac_d6h4k8qph9qqvx7jwkh8
sincedatetimeOnly remittances Claim House received at or after this moment (received_at), not the payer's payment date. No timezone means UTC.
e.g. 2026-09-01T00:00:00Z
cursorstringThe next_cursor from the previous page, at most 512 characters. Keep other filters identical while paging.
limitintegerPage size, 1 to 500. Default 100.
e.g. 1

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id.
remittances*arrayRemittance rows, ordered by received_at then remittance_id, descending.
remittance_id*stringRemittance id, rem_.... One 835 file.
payer_name*stringPayer name from the 835. Empty string when absent.
payer_id*stringPayer identifier from the 835. Empty string when absent.
trace_number*stringCheck or EFT trace number (TRN02). Use it to match a bank deposit.
payment_method*stringBPR04 payment method code as sent, for example ACH, CHK or NON.
transaction_handling_code*stringBPR01 code as sent, for example C (payment with remittance) or I (remittance information only).
payment_datedateBPR16 effective payment date. Null when absent.
received_at*datetimeWhen Claim House received the 835 file.
claim_count*integerNumber of your payment lines (CLPs) in this file within your scope. A reversal and a corrected payment for one claim count as two.
paid_amount*stringSum of paid_amount over those lines, as a two decimal string.
artifact_idstringArtifact id for the raw 835. When your lines span several offices, one office's copy is returned.
next_cursorstringOpaque cursor for the next page. Null on the last page.
has_more*booleanTrue exactly when next_cursor is not null.

Responses

200OK
{
  "request_id": "evt_1wjwnjm7k0fdcg2w4dnx",
  "remittances": [
    {
      "remittance_id": "rem_6e65br54q5wnygrtqcrq",
      "payer_name": "SYNTHETIC DENTAL PLAN",
      "payer_id": "SYNTHPAY",
      "trace_number": "SYNTHTRACE0302",
      "payment_method": "ACH",
      "transaction_handling_code": "I",
      "payment_date": "2026-09-07",
      "received_at": "2026-09-07T14:00:00+00:00",
      "claim_count": 1,
      "paid_amount": "180.00",
      "artifact_id": "art_4q9j4031fp41enb38epf"
    }
  ],
  "next_cursor": "eyJjIjoicmVtXzM2ZXFzMnR6eng2MmI0eng5ajZhIiwidSI6IjIwMjYtMDktMDdUMTQ6MDA6MDArMDA6MDAiLCJ2IjoidjEifQ",
  "has_more": true
}
400Invalid cursor
{
  "error": "INVALID_CURSOR",
  "message": "cursor is not readable",
  "errors": [],
  "request_id": "evt_6yzmma09tbqtvyw60mtk"
}
403Office not granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "this key's grants do not cover that facility",
  "errors": [
    {
      "facility_id": "fac_tycrfy0cs0qc7sq95eme"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
400INVALID_CURSORcursor was not issued by the gateway
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route's permission
403FACILITY_NOT_GRANTEDKey has no office grant, or facility_id is outside its grants
422INVALID_REQUESTBad since, or limit outside 1 to 500
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
InfoThe cursor does not expire and is not tied to filters. New remittances arrive at the top, so walking forward never skips an old row; to pick up new ones, start again without a cursor or with since.
API reference

Get a remittance

GEThttps://api.claimhouse.ai/v1/remittances/{remittance_id}

Read one 835 header and every payment line in it that belongs to you, with claim and line adjustments.

Use it to post payments to a practice system or explain a patient balance. Each entry in claims is one CLP loop matched to one of your claims, with the match method in routed_by and the claim event it raised in event_kind.

Returns 404 unless the file has at least one of your payment lines within your grants. At most 500 lines are listed, in file order.

Permission
read
Idempotency
none
Side effects
Writes an audit row. No vendor call.
In the dashboard
Payments > Remittance detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
remittance_id*stringRemittance id, rem_..., 1 to 64 characters.
e.g. rem_6e65br54q5wnygrtqcrq

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id.
remittance_id*stringRemittance id, rem_.... One 835 file.
payer_name*stringPayer name from the 835. Empty string when absent.
payer_id*stringPayer identifier from the 835. Empty string when absent.
trace_number*stringCheck or EFT trace number (TRN02). Use it to match a bank deposit.
payment_method*stringBPR04 payment method code as sent, for example ACH, CHK or NON.
transaction_handling_code*stringBPR01 code as sent, for example C (payment with remittance) or I (remittance information only).
payment_datedateBPR16 effective payment date. Null when absent.
received_at*datetimeWhen Claim House received the 835 file.
claim_count*integerNumber of your payment lines (CLPs) in this file within your scope. A reversal and a corrected payment for one claim count as two.
paid_amount*stringSum of paid_amount over those lines, as a two decimal string.
artifact_idstringArtifact id for the raw 835. When your lines span several offices, one office's copy is returned.
production_datedate835 production date. Null when absent.
claims*arrayYour payment lines, in file order, at most 500.
payment_id*stringPayment id, pay_....
remittance_id*stringParent remittance.
claim_id*stringThe claim this line was matched to.
facility_id*stringOffice of that claim.
submission_id*stringSubmission of that claim.
tenant_claim_idstringYour claim id. Null when not set.
clp_position*integer1 based position of this CLP in the file.
patient_control_number*stringCLP01 as the payer returned it.
routed_by*stringHow the line was matched to your claim.
one of: PCN · PAYER_CLAIM_NUMBER · ORIGINAL_REFERENCE
claim_status_code*stringCLP02 claim status code.
payer_claim_numberstringCLP07 payer claim control number. Null when empty.
original_referencestringREF*F8 original reference. Null when empty.
filing_indicator*stringCLP06 claim filing indicator.
charged_amount*stringCLP03 total charge.
paid_amount*stringCLP04 payment amount. Can be negative on a reversal.
patient_responsibility*stringCLP05 patient responsibility.
event_kindstringThe claim event this line raised. Null for a CLP02 that raises no event.
one of: claim.paid · claim.denied · claim.reversed
artifact_idstringYour office's copy of the raw 835.
recorded_at*datetimeWhen Claim House recorded the line.
service_lines*arrayService line (SVC) detail. Currently always empty, see notes.
line_number*integerLine position.
procedure_code*stringCDT code.
procedure_modifier*stringModifier. May be empty.
charged_amount*stringLine charge.
paid_amount*stringLine payment.
service_date_from*stringLine service date as parsed.
service_date_to*stringLine end date as parsed.
line_control_number*stringREF*6R line item control number.
adjustments*arrayLine level adjustments, same shape as adjustments without adjustment_id.
adjustments*arrayEvery adjustment for this payment: claim level first, then line level, in file order.
adjustment_id*stringAdjustment id, adj_.... Not present on items inside service_lines[].adjustments.
level*stringclaim for a CAS before the first SVC, line for a CAS under an SVC.
one of: claim · line
line_numberintegerService line for a line adjustment. Null for claim.
group_code*stringCAS01 group code, for example CO (contractual), PR (patient responsibility), OA or PI.
reason_code*stringCAS02 claim adjustment reason code.
amount*stringAdjustment amount as a two decimal string.
quantitystringAdjustment quantity. Null when absent.
remark_codes*arrayArray of strings. Remittance advice remark codes attached to the adjustment.

Responses

200OK
{
  "request_id": "evt_dm6xe94b4phsgzttjwb6",
  "remittance_id": "rem_6e65br54q5wnygrtqcrq",
  "payer_name": "SYNTHETIC DENTAL PLAN",
  "payer_id": "SYNTHPAY",
  "trace_number": "SYNTHTRACE0302",
  "payment_method": "ACH",
  "transaction_handling_code": "I",
  "payment_date": "2026-09-07",
  "received_at": "2026-09-07T14:00:00+00:00",
  "claim_count": 1,
  "paid_amount": "180.00",
  "artifact_id": "art_4q9j4031fp41enb38epf",
  "production_date": "2026-09-06",
  "claims": [
    {
      "payment_id": "pay_v03c8hhwsssy5axy6qd8",
      "remittance_id": "rem_6e65br54q5wnygrtqcrq",
      "claim_id": "clm_thw8w242jpj27s1aa6g3",
      "facility_id": "fac_d6h4k8qph9qqvx7jwkh8",
      "submission_id": "sub_534hy00sj1xjq5rg7pca",
      "tenant_claim_id": "TENANT-0001",
      "clp_position": 1,
      "patient_control_number": "AAA-000000000001",
      "routed_by": "PCN",
      "claim_status_code": "1",
      "payer_claim_number": "PAYERA0001",
      "original_reference": null,
      "filing_indicator": "12",
      "charged_amount": "300.00",
      "paid_amount": "180.00",
      "patient_responsibility": "60.00",
      "event_kind": "claim.paid",
      "artifact_id": "art_4q9j4031fp41enb38epf",
      "recorded_at": "2026-09-15T03:38:46.204836+00:00",
      "service_lines": [
        {
          "adjustments": [
            {
              "amount": "30.00",
              "group_code": "PR",
              "level": "line",
              "line_number": 1,
              "quantity": null,
              "reason_code": "2",
              "remark_codes": []
            }
          ],
          "charged_amount": "150.00",
          "line_control_number": "L1",
          "line_number": 1,
          "paid_amount": "100.00",
          "procedure_code": "D2740",
          "procedure_modifier": "",
          "service_date_from": "20260901",
          "service_date_to": ""
        },
        {
          "adjustments": [
            {
              "amount": "30.00",
              "group_code": "PR",
              "level": "line",
              "line_number": 2,
              "quantity": null,
              "reason_code": "3",
              "remark_codes": [
                "N30"
              ]
            }
          ],
          "charged_amount": "150.00",
          "line_control_number": "L2",
          "line_number": 2,
          "paid_amount": "80.00",
          "procedure_code": "D2750",
          "procedure_modifier": "",
          "service_date_from": "20260901",
          "service_date_to": ""
        }
      ],
      "adjustments": [
        {
          "adjustment_id": "adj_p029g2ypj6x29fhvp5pk",
          "level": "claim",
          "line_number": null,
          "group_code": "CO",
          "reason_code": "45",
          "amount": "50.00",
          "quantity": null,
          "remark_codes": []
        },
        {
          "adjustment_id": "adj_gzpa8e8qh1t8p6wwt7p2",
          "level": "claim",
          "line_number": null,
          "group_code": "CO",
          "reason_code": "131",
          "amount": "10.00",
          "quantity": null,
          "remark_codes": []
        },
        {
          "adjustment_id": "adj_j2zrtt2k7r3005qasgfv",
          "level": "line",
          "line_number": 1,
          "group_code": "PR",
          "reason_code": "2",
          "amount": "30.00",
          "quantity": null,
          "remark_codes": []
        },
        {
          "adjustment_id": "adj_vtkxmcpseymwwfq2f6w0",
          "level": "line",
          "line_number": 2,
          "group_code": "PR",
          "reason_code": "3",
          "amount": "30.00",
          "quantity": null,
          "remark_codes": [
            "N30"
          ]
        }
      ]
    }
  ]
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such remittance",
  "errors": [],
  "request_id": "evt_ykazcjzw21epeybbysyw"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks the route's permission
404NOT_FOUNDNo remittance with that id containing your lines within your grants
422INVALID_REQUESTPath id longer than 64 characters
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
ImportantKnown issue: service_lines is currently returned as an empty array for every payment. Adjustments are unaffected and include line level ones with line_number. Until this is fixed, build line detail from adjustments and the raw 835 artifact.
InfoA CLP that matches none of your claims is held for Claim House review and is not visible here.
API reference

Payers

Look up which payers Claim House knows, which transactions each supports, and which attachment doors each accepts.

The payer registry is global: every account sees the same records, and payer routes have no office scope.

RouteAccepts
GET /v1/payers/{payer_id}Primary id, internal id or alias
GET /v1/payers/{payer_id}/doorsPrimary id only
POST /v1/eligibilityPrimary id or internal id, not aliases
Attachment packet send and draftsPrimary id only
POST /v1/dental-claims/submissionPrimary id, internal id or alias
InfoPass primaryPayerId everywhere unless a route says otherwise. Store payerId as your database key: it never changes.
  • transactionSupport levels are SUPPORTED, ENROLLMENT_REQUIRED and NOT_SUPPORTED. claimPayment is ENROLLMENT_REQUIRED whenever offered, since an 835 always needs ERA enrollment.
  • eligibilityCheck: SUPPORTED does not guarantee a check works: the payer also needs an eligibility mapping, or the check answers 422 PAYER_NOT_SUPPORTED_FOR_ELIGIBILITY.
  • unsolicitedClaimAttachment: SUPPORTED does not guarantee a network_A send works: it also needs network_A in the door order and a network registration that no payer route exposes.
ImportantPer payer documentation requirements by CDT code have no public route yet. Missing documentation is only reported at attachment validate and send time as 422 DOCUMENTATION_RULE_UNMET.
API reference

Search payers

GEThttps://api.claimhouse.ai/v1/payers/search

Find payers whose display name, name variant, primary id or alias contains your text.

Use it for a payer picker. Matching is a case insensitive substring match on the trimmed text across display name, every name variant in names, primary payer id and aliases. Returns at most 50 payers ordered by display name then primary id, with no paging.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Eligibility > New check (payer search)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
q*stringSearch text, 2 to 120 characters.
e.g. dental

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id.
payers*arrayMatching payer records. Empty when nothing matches (never 404).
payerId*stringImmutable internal id. Use it as your database key.
displayName*stringName to show.
primaryPayerId*stringThe id to show and to send on claims, eligibility and attachments.
aliases*arrayArray of strings. Other ids that resolve to this payer.
names*arrayArray of strings. Known names. Falls back to [displayName].
transactionSupport*objectSupport level per transaction.
eligibilityCheck*stringEligibility checks.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
claimStatus*stringClaim status.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
dentalClaimSubmission*stringDental claim submission.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
claimPayment*string835 remittances. Never SUPPORTED unless set explicitly, since an 835 needs ERA enrollment.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
unsolicitedClaimAttachment*stringAttachments sent after the claim.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
coverageTypes*arrayArray of strings. Falls back to ["dental"].
attachmentDoors*arrayArray of strings. The payer's attachment door order, for example ["network_A", "paper"]. Empty when the payer has no routing.
eraEnrollmentCodestringERA enrollment code. Null when none.
paperEobShutoffbooleanWhether paper EOBs stop after ERA enrollment. Null when unknown.

Responses

200OK
{
  "request_id": "evt_zjy4k8m4y4611f2362kw",
  "payers": [
    {
      "payerId": "pay_test",
      "displayName": "Test Dental Plan",
      "primaryPayerId": "TESTPAYER1",
      "aliases": [
        "TDP"
      ],
      "names": [
        "Test Dental Plan",
        "TDP of Testland"
      ],
      "transactionSupport": {
        "eligibilityCheck": "SUPPORTED",
        "claimStatus": "NOT_SUPPORTED",
        "dentalClaimSubmission": "ENROLLMENT_REQUIRED",
        "claimPayment": "ENROLLMENT_REQUIRED",
        "unsolicitedClaimAttachment": "SUPPORTED"
      },
      "coverageTypes": [
        "dental"
      ],
      "attachmentDoors": [
        "network_A",
        "portal"
      ],
      "eraEnrollmentCode": "S",
      "paperEobShutoff": true
    }
  ]
}
422Query too short
{
  "error": "INVALID_REQUEST",
  "message": "the request body is not valid",
  "errors": [
    {
      "code": "string_too_short",
      "location": "query.q",
      "message": "String should have at least 2 characters",
      "value_redacted": true
    }
  ],
  "request_id": "evt_tsp30zyjmerzr3c6sxvj"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
422INVALID_REQUESTq missing, shorter than 2 or longer than 120
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
Info% and _ in q act as wildcards.
API reference

List payers

GEThttps://api.claimhouse.ai/v1/payers

Page through the whole payer registry, or download it as one CSV file.

Use it to sync the registry into your system. JSON pages are ordered by internal id; pass nextPageToken back as pageToken until it is null.

format=csv returns one text/csv file named payers.csv instead, with one row per payer.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Payers directory, Attachments > New attachment (payer select)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
formatstringResponse format. Default json.
one of: json · csv
e.g. json
pageTokenstringThe nextPageToken from the previous page, at most 200 characters. Ignored for CSV.
pageSizeintegerPayers per page, 10 to 50. Default 50. Ignored for CSV.
e.g. 10

Request example

null

Response fields

NameTypeDescription
payers*arrayPayer records for this page.
payerId*stringImmutable internal id. Use it as your database key.
displayName*stringName to show.
primaryPayerId*stringThe id to show and to send on claims, eligibility and attachments.
aliases*arrayArray of strings. Other ids that resolve to this payer.
names*arrayArray of strings. Known names. Falls back to [displayName].
transactionSupport*objectSupport level per transaction.
eligibilityCheck*stringEligibility checks.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
claimStatus*stringClaim status.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
dentalClaimSubmission*stringDental claim submission.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
claimPayment*string835 remittances. Never SUPPORTED unless set explicitly, since an 835 needs ERA enrollment.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
unsolicitedClaimAttachment*stringAttachments sent after the claim.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
coverageTypes*arrayArray of strings. Falls back to ["dental"].
attachmentDoors*arrayArray of strings. The payer's attachment door order, for example ["network_A", "paper"]. Empty when the payer has no routing.
eraEnrollmentCodestringERA enrollment code. Null when none.
paperEobShutoffbooleanWhether paper EOBs stop after ERA enrollment. Null when unknown.
nextPageTokenstringToken for the next page. Null on the last page.

Responses

200OK
{
  "payers": [
    {
      "payerId": "pay_second",
      "displayName": "Second Test Plan",
      "primaryPayerId": "TESTPAYER2",
      "aliases": [],
      "names": [
        "Second Test Plan"
      ],
      "transactionSupport": {
        "eligibilityCheck": "NOT_SUPPORTED",
        "claimStatus": "SUPPORTED",
        "dentalClaimSubmission": "SUPPORTED",
        "claimPayment": "NOT_SUPPORTED",
        "unsolicitedClaimAttachment": "NOT_SUPPORTED"
      },
      "coverageTypes": [
        "dental"
      ],
      "attachmentDoors": [],
      "eraEnrollmentCode": null,
      "paperEobShutoff": null
    },
    {
      "payerId": "pay_test",
      "displayName": "Test Dental Plan",
      "primaryPayerId": "TESTPAYER1",
      "aliases": [
        "TDP"
      ],
      "names": [
        "Test Dental Plan",
        "TDP of Testland"
      ],
      "transactionSupport": {
        "eligibilityCheck": "SUPPORTED",
        "claimStatus": "NOT_SUPPORTED",
        "dentalClaimSubmission": "ENROLLMENT_REQUIRED",
        "claimPayment": "ENROLLMENT_REQUIRED",
        "unsolicitedClaimAttachment": "SUPPORTED"
      },
      "coverageTypes": [
        "dental"
      ],
      "attachmentDoors": [
        "network_A",
        "portal"
      ],
      "eraEnrollmentCode": "S",
      "paperEobShutoff": true
    }
  ],
  "nextPageToken": null
}
422Page size out of range
{
  "error": "INVALID_REQUEST",
  "message": "the request body is not valid",
  "errors": [
    {
      "code": "less_than_equal",
      "location": "query.pageSize",
      "message": "Input should be less than or equal to 50",
      "value_redacted": true
    }
  ],
  "request_id": "evt_be4nnzktd50qt0hta11z"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
422INVALID_REQUESTpageSize outside 10 to 50, unknown format, or pageToken too long
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
InfoThis route's JSON body has no request_id field. The X-Request-Id header is still set.
InfoCSV columns: payerId, displayName, primaryPayerId, aliases, names, eligibilityCheck, claimStatus, dentalClaimSubmission, claimPayment, unsolicitedClaimAttachment, coverageTypes, attachmentDoors, eraEnrollmentCode, paperEobShutoff. List values are joined with |; paperEobShutoff is true, false or empty.
ImportantThe CSV export stops at 10,000 payers and is silently truncated above that.
API reference

Get a payer

GEThttps://api.claimhouse.ai/v1/payers/{payer_id}

Read one payer record by exact primary id, internal id or alias.

When an alias of one payer equals another payer's primary id, the primary id match wins.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Payers directory (payer detail rail, preview only)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
payer_id*stringPrimary id, internal id or alias, 1 to 80 characters. Exact match.
e.g. TESTPAYER1

Request example

null

Response fields

NameTypeDescription
payerId*stringImmutable internal id. Use it as your database key.
displayName*stringName to show.
primaryPayerId*stringThe id to show and to send on claims, eligibility and attachments.
aliases*arrayArray of strings. Other ids that resolve to this payer.
names*arrayArray of strings. Known names. Falls back to [displayName].
transactionSupport*objectSupport level per transaction.
eligibilityCheck*stringEligibility checks.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
claimStatus*stringClaim status.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
dentalClaimSubmission*stringDental claim submission.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
claimPayment*string835 remittances. Never SUPPORTED unless set explicitly, since an 835 needs ERA enrollment.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
unsolicitedClaimAttachment*stringAttachments sent after the claim.
one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED
coverageTypes*arrayArray of strings. Falls back to ["dental"].
attachmentDoors*arrayArray of strings. The payer's attachment door order, for example ["network_A", "paper"]. Empty when the payer has no routing.
eraEnrollmentCodestringERA enrollment code. Null when none.
paperEobShutoffbooleanWhether paper EOBs stop after ERA enrollment. Null when unknown.

Responses

200OK
{
  "payerId": "pay_test",
  "displayName": "Test Dental Plan",
  "primaryPayerId": "TESTPAYER1",
  "aliases": [
    "TDP"
  ],
  "names": [
    "Test Dental Plan",
    "TDP of Testland"
  ],
  "transactionSupport": {
    "eligibilityCheck": "SUPPORTED",
    "claimStatus": "NOT_SUPPORTED",
    "dentalClaimSubmission": "ENROLLMENT_REQUIRED",
    "claimPayment": "ENROLLMENT_REQUIRED",
    "unsolicitedClaimAttachment": "SUPPORTED"
  },
  "coverageTypes": [
    "dental"
  ],
  "attachmentDoors": [
    "network_A",
    "portal"
  ],
  "eraEnrollmentCode": "S",
  "paperEobShutoff": true
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such payer",
  "errors": [],
  "request_id": "evt_g80nnx0fjhj0d0ng5adn"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
404NOT_FOUNDNo payer has that primary id, internal id or alias
422INVALID_REQUESTPath, query or body fails the schema
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
InfoThe body is the bare payer record with no request_id field. The X-Request-Id header is still set.
API reference

Get a payer's attachment doors

GEThttps://api.claimhouse.ai/v1/payers/{payer_id}/doors

Read the doors a payer accepts attachments through, in the payer's order, with file limits per door.

Call it before creating an attachment packet so your files fit the door that will be used. Only network_A can send today; its limits are fixed at 127 JPEG files and 15 MB in total.

The answer does not say whether your office is enrolled for a door.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Claims > Submit a claim > Draft flow > Attach (door card)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
payer_id*stringPrimary payer id only, 1 to 40 characters, exact. Aliases and internal ids answer 404.
e.g. TESTPAYER1

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id.
payer_id*stringPrimary payer id.
display_name*stringRegistry name.
networks*arrayArray of strings. Attachment networks listed on the registry row.
routing_known*booleanFalse when the payer has no routing. Every attachment send for this payer then fails with 422 PAYER_ROUTING_UNKNOWN.
door_order*arrayArray of strings. Doors in the payer's order. Empty without routing.
doors*arrayArray of strings. The known doors from door_order, listed in Claim House's fixed order (network_A, network_B, x12_275, portal, paper), not the payer's order. Unknown names are dropped.
one of: network_A · network_B · x12_275 · portal · paper
limits*arrayOne entry per door_order entry, in the same order.
door*stringThe door.
max_filesintegerMaximum file count. Null means no check. Always 127 for network_A.
max_file_bytesintegerMaximum size of one file. Null means no check. Always 15728640 for network_A.
max_total_bytesintegerMaximum total size. Null means no check. Always 15728640 for network_A.
media_types*arrayArray of strings. Allowed media types. Empty means any stored type. Always ["image/jpeg"] for network_A.
portal_platformstringPortal platform name when the payer uses one. Null otherwise.
portal_after_claimbooleanWhether the portal accepts attachments only after the claim exists. Null when unknown.
evidence_gradestringConfidence grade of the routing research. Null when not graded.
source_urlstringWhere the routing was sourced. Null when not recorded.

Responses

200OK
{
  "request_id": "evt_1ghjs8sxft1syy2e88nr",
  "payer_id": "TESTPAYER1",
  "display_name": "Test Dental Plan",
  "networks": [
    "network_A"
  ],
  "routing_known": true,
  "door_order": [
    "network_A",
    "portal",
    "paper"
  ],
  "doors": [
    "network_A",
    "portal",
    "paper"
  ],
  "limits": [
    {
      "door": "network_A",
      "max_files": 127,
      "max_file_bytes": 15728640,
      "max_total_bytes": 15728640,
      "media_types": [
        "image/jpeg"
      ]
    },
    {
      "door": "portal",
      "max_files": 2,
      "max_file_bytes": 5242880,
      "max_total_bytes": null,
      "media_types": [
        "image/jpeg",
        "application/pdf"
      ]
    },
    {
      "door": "paper",
      "max_files": null,
      "max_file_bytes": null,
      "max_total_bytes": null,
      "media_types": []
    }
  ],
  "portal_platform": null,
  "portal_after_claim": null,
  "evidence_grade": null,
  "source_url": null
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such payer",
  "errors": [],
  "request_id": "evt_4fcz5qg00vmpw8b3g8zg"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
404NOT_FOUNDNo registry row with that primary id
422INVALID_REQUESTPath, query or body fails the schema
429TOO_MANY_REQUESTSMore than 20 in flight or rate bucket empty
ImportantA payer whose stored file limits are malformed returns a 500 with no error code.
API reference

Get a payer's attachment profile

GEThttps://api.claimhouse.ai/v1/payers/{payer_id}/attachment-profile

Payer level attachment facts, without procedure codes.

Use it for a payer page. When you have procedure codes in hand, use the requirements lookup instead.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Payers > (payer) > Attachments

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
payer_id*stringThe claim payer id.
e.g. CX014

Request example

null

Response fields

NameTypeDescription
payer_id*stringEchoed payer id.
payer_namestringPayer name when known.
electronic_attachments*stringavailable or not_available.
route_state*stringrouted, choose_plan or not_on_network.
plan_count*integerHow many plans accept attachments under this payer id.
plan_sensitive_codes*arrayCodes whose answer depends on which plan the patient is on. Empty for most payers.
narrative*objectallowed and max_characters.
payer_reference_number*stringrequired, allowed or not_accepted.
payer_notesstringThe payer's own note.
notes_vary_by_plan*booleanTrue when plans differ in notes or return policy.
return_policystringThe payer's own return policy.
file_limits*objectUpload constraints.
source*objectFreshness stamp.

Responses

200OK
{
  "payer_id": "CX014",
  "payer_name": "California Dental Network - Dentaquest",
  "electronic_attachments": "available",
  "route_state": "choose_plan",
  "plan_count": 216,
  "plan_sensitive_codes": [
    "D2940",
    "D2950",
    "D2954"
  ],
  "narrative": {
    "allowed": true,
    "max_characters": 2000
  },
  "payer_reference_number": "allowed",
  "payer_notes": null,
  "notes_vary_by_plan": true,
  "return_policy": null,
  "file_limits": {
    "formats": [
      "jpeg"
    ],
    "max_images": 127,
    "max_total_bytes": 15728640,
    "orientation_values": [
      "left",
      "right"
    ]
  },
  "source": {
    "rules_as_of": "2026-09-19T17:36:08Z",
    "payer_last_updated": "2026-09-09T15:14:36Z",
    "freshness": "current"
  }
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
404PAYER_UNKNOWNNo payer source Claim House holds knows this identifier
503ATTACHMENT_RULES_UNAVAILABLENo payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it
API reference

List a payer's attachment plans

GEThttps://api.claimhouse.ai/v1/payers/{payer_id}/attachment-plans

Plan names for the plan picker, when requirements differ by plan.

Plan ids are opaque Claim House values. Only plans that accept attachments are listed.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Attachments > New attachment (plan picker)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
payer_id*stringThe claim payer id.
e.g. CX014

Query parameters

NameTypeDescription
qstringCase insensitive substring of the plan name.
e.g. emblem
pageTokenstringToken from the previous page.
pageSizeinteger10 to 50, default 25.

Request example

null

Response fields

NameTypeDescription
payer_id*stringEchoed payer id.
plans*arrayplan_id and name.
total*integerHow many plans match.
next_page_tokenstringNull on the last page.

Responses

200OK
{
  "payer_id": "CX014",
  "plans": [
    {
      "plan_id": "apl_9e98e74a6a3c1f02",
      "name": "Doral Dental USA"
    },
    {
      "plan_id": "apl_2a156835fbfcc398",
      "name": "DQ/Emblem (Emblem Health Medicaid)"
    }
  ],
  "total": 216,
  "next_page_token": "25"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
404PAYER_UNKNOWNNo payer source Claim House holds knows this identifier
503ATTACHMENT_RULES_UNAVAILABLENo payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it
API reference

Offices

Register the billing offices your claims belong to, name their claims, and check payer enrollment and attachment network registration.

An office (called a facility in the API, ids start with fac_) is one billing entity at one location: one billing NPI plus one TIN, as the payer sees it. A ten-location dental group is one seller with ten offices. Every claim, attachment packet and eligibility check names its office, and the gateway proves a claim's billing NPI and TIN against that office before it transports anything.

FieldSet byRules
facility_idGatewayfac_ plus 20 lowercase base32 characters. Opaque.
billing_npiYou, on createTen digits with a valid NPI check digit. Unique together with the TIN inside your seller. Cannot be changed.
tin / tin_typeYou, on createNine digits, no dashes. The TIN is never returned by any route; responses carry only tin_type (EI default, or SY). Cannot be changed.
control_prefixGateway (or you, to import one)Three or four uppercase letters or digits stamped into X12 control numbers. Unique across the whole gateway. Cannot be changed.
binding_kindGatewaysandbox or production. API-created offices are sandbox and accept only test keys. Moving to production is a Claim House action with no route.
claim_office_code / claim_timezoneYou, onceForm human claim references such as NORTH-20260914-0001. Permanent once saved.
nea_facility_idGateway, on NEA registrationThe office's id on the NEA attachment network. Null until registered.
statusGatewayACTIVE, SUSPENDED or RETIRED. Created offices are ACTIVE.
  • Claim references are assigned when the gateway records confirmed transport of the file that carried the claim, not at submission.
  • The format is <claim_office_code>-<YYYYMMDD>-<sequence>: the transport date in claim_timezone, and a per-office per-day sequence starting at 1, zero-padded to four digits.
  • Claims transported before naming was set never get a reference. The reference is separate from X12 control numbers, which use control_prefix.
Enrollment stateMeaningClaims to this payer
NOT_REQUIREDThe payer needs no enrollment for this transaction.Accepted
REQUESTEDClaim House has requested enrollment.Refused
PENDINGThe payer or clearinghouse is processing it.Refused
LIVEEnrolled.Accepted
REJECTEDThe payer refused the enrollment.Refused
InfoThe per-office transport armed switch is set by a Claim House operator before production transport and is not returned by any route. Enrollment rows are set by the Claim House enrollment desk; no route requests one.
API reference

List offices

GEThttps://api.claimhouse.ai/v1/facilities

List every office your credential reaches.

Use it to map your own office records to facility_ids and to read each office's control prefix, binding, claim naming and NEA registration.

There is no pagination: at most 500 offices are returned, ordered by facility_id. A seller scope sees every office, a group scope sees offices in its groups, and an office scope sees its granted offices.

Permission
read
Idempotency
none
Side effects
Records one audit entry listing the returned office ids. No vendor call.
In the dashboard
Settings > Organization > Offices

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id, also sent in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000101
facilities*arrayOne entry per reachable office.
facility_id*stringThe office id.
e.g. fac_EXAMPLE0000000000001
group_idstringThe group the office belongs to. Null when the office is in no group.
billing_npi*stringThe office's billing NPI.
e.g. 1234567893
tin_type*stringThe TIN type. The TIN itself is never returned.
one of: EI · SY
taxonomy*stringBilling taxonomy code. Empty string when not set.
e.g. 1223G0001X
control_prefix*stringThe three or four character code stamped into this office's X12 control numbers.
e.g. AAB
claim_office_codestringOffice code used in claim references. Null until set.
claim_timezonestringIANA timezone that decides the claim reference date. Null until set.
nea_facility_idstringThe office's id on the NEA attachment network. Null until registered.
binding_kind*stringWhether the office accepts test or production keys.
one of: sandbox · production
status*stringThe office's lifecycle status.
one of: ACTIVE · SUSPENDED · RETIRED
address_line1stringReturned by the current gateway build; not yet described in this reference. Can be null.
address_line2stringReturned by the current gateway build; not yet described in this reference. Can be null.
citystringReturned by the current gateway build; not yet described in this reference. Can be null.
contact_emailstringReturned by the current gateway build; not yet described in this reference. Can be null.
contact_namestringReturned by the current gateway build; not yet described in this reference. Can be null.
contact_phonestringReturned by the current gateway build; not yet described in this reference. Can be null.
countrystringReturned by the current gateway build; not yet described in this reference. Can be null.
display_namestringReturned by the current gateway build; not yet described in this reference. Can be null.
identity_review_statusstringReturned by the current gateway build; not yet described in this reference. Can be null.
legal_namestringReturned by the current gateway build; not yet described in this reference. Can be null.
partner_office_refstringReturned by the current gateway build; not yet described in this reference. Can be null.
postal_codestringReturned by the current gateway build; not yet described in this reference. Can be null.
profile_sourcestringReturned by the current gateway build; not yet described in this reference. Can be null.
profile_versionstringReturned by the current gateway build; not yet described in this reference. Can be null.
statestringReturned by the current gateway build; not yet described in this reference. Can be null.

Responses

200OK
{
  "request_id": "evt_j912jjpyq90stk2r84g5",
  "facilities": [
    {
      "facility_id": "fac_q5s09nzww25ysd5a2f3g",
      "group_id": null,
      "billing_npi": "9999999995",
      "tin_type": "EI",
      "taxonomy": "1223G0001X",
      "control_prefix": "HAL",
      "claim_office_code": null,
      "claim_timezone": null,
      "nea_facility_id": null,
      "binding_kind": "production",
      "status": "ACTIVE",
      "display_name": null,
      "legal_name": null,
      "address_line1": null,
      "address_line2": null,
      "city": null,
      "state": null,
      "postal_code": null,
      "country": null,
      "contact_name": null,
      "contact_email": null,
      "contact_phone": null,
      "partner_office_ref": null,
      "profile_source": null,
      "profile_version": null,
      "identity_review_status": null
    },
    {
      "facility_id": "fac_w9qxypnbcw7b308jbbhh",
      "group_id": null,
      "billing_npi": "9999999995",
      "tin_type": "EI",
      "taxonomy": "1223G0001X",
      "control_prefix": "HA2",
      "claim_office_code": null,
      "claim_timezone": null,
      "nea_facility_id": null,
      "binding_kind": "production",
      "status": "ACTIVE",
      "display_name": null,
      "legal_name": null,
      "address_line1": null,
      "address_line2": null,
      "city": null,
      "state": null,
      "postal_code": null,
      "country": null,
      "contact_name": null,
      "contact_email": null,
      "contact_phone": null,
      "partner_office_ref": null,
      "profile_source": null,
      "profile_version": null,
      "identity_review_status": null
    },
    {
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "group_id": null,
      "billing_npi": "9999999995",
      "tin_type": "EI",
      "taxonomy": "1223G0001X",
      "control_prefix": "SBX",
      "claim_office_code": null,
      "claim_timezone": null,
      "nea_facility_id": null,
      "binding_kind": "sandbox",
      "status": "ACTIVE",
      "display_name": null,
      "legal_name": null,
      "address_line1": null,
      "address_line2": null,
      "city": null,
      "state": null,
      "postal_code": null,
      "country": null,
      "contact_name": null,
      "contact_email": null,
      "contact_phone": null,
      "partner_office_ref": null,
      "profile_source": null,
      "profile_version": null,
      "identity_review_status": null
    },
    {
      "facility_id": "fac_qnwrs4rq9tjtqwv37phe",
      "group_id": null,
      "billing_npi": "9999999995",
      "tin_type": "EI",
      "taxonomy": "1223G0001X",
      "control_prefix": "SB2",
      "claim_office_code": null,
      "claim_timezone": null,
      "nea_facility_id": null,
      "binding_kind": "sandbox",
      "status": "ACTIVE",
      "display_name": null,
      "legal_name": null,
      "address_line1": null,
      "address_line2": null,
      "city": null,
      "state": null,
      "postal_code": null,
      "country": null,
      "contact_name": null,
      "contact_email": null,
      "contact_phone": null,
      "partner_office_ref": null,
      "profile_source": null,
      "profile_version": null,
      "identity_review_status": null
    }
  ]
}
403No office granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "No office access is granted.",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks read
403FACILITY_NOT_GRANTEDA group or office scope resolves to no office
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
InfoNot returned: the TIN, your officeId, the transport armed switch and the created time.
InfoThe current gateway build also returns office profile fields (for example display_name, city, state, partner_office_ref, identity_review_status) that this reference does not yet document. Treat them as unstable.
API reference

Create an office

POSThttps://api.claimhouse.ai/v1/facilities

Register one billing office under your seller in sandbox binding.

Call it during onboarding for each billing location before submitting claims for it. The gateway checks the NPI check digit, allocates a control prefix unless you import one, and creates the office as sandbox and ACTIVE.

The body is strict: unknown fields are refused with 422 and string values are trimmed.

Permission
admin
Idempotency
none
Side effects
Creates the office, reserves its control prefix gateway-wide and seeds its control number counters in one transaction. No vendor call.
In the dashboard
Settings > Organization > Offices > Add offices

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Request body

NameTypeDescription
billingNpi*stringThe office's billing NPI. Ten digits with a valid NPI check digit.
e.g. 1234567893
tin*stringThe billing TIN, nine digits with no dashes. Stored for billing identity checks and never returned.
e.g. 000000000
tinTypestringThe TIN type. Defaults to EI.
one of: EI · SY
taxonomystringBilling taxonomy code, up to 10 characters. Defaults to empty string. The API does not check the pattern.
e.g. 1223G0001X
groupIdstringA group of your seller to place the office in. Up to 64 characters. There is no route to create or list groups.
neaFacilityIdstringAn NEA facility id you already hold, up to 64 characters. Setting it makes NEA registration refuse this office with 409.
officeIdstringYour own office reference, up to 64 characters. Stored but never returned.
e.g. your-office-17
controlPrefixstringImport a control prefix that already stamps claims elsewhere: 3 or 4 uppercase letters or digits, not already registered. Omit to let the gateway allocate the next free code.
claimOfficeCodestringClaim office code, matching ^[A-Z][A-Z0-9]{1,7}$. Send together with claimTimezone.
e.g. NORTH
claimTimezonestringA valid IANA timezone, up to 64 characters. Send together with claimOfficeCode.
e.g. America/New_York

Request example

{
  "billingNpi": "9999900241",
  "tin": "990000024",
  "tinType": "EI",
  "taxonomy": "1223G0001X",
  "officeId": "your-office-17",
  "claimOfficeCode": "NORTH",
  "claimTimezone": "America/New_York"
}

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000102
facility_id*stringThe new office id.
e.g. fac_EXAMPLE0000000000001
seller_id*stringYour seller id.
e.g. sel_EXAMPLE0000000000001
group_idstringThe group id. Null when none was sent.
billing_npi*stringThe billing NPI.
e.g. 1234567893
tin_type*stringThe TIN type.
one of: EI · SY
control_prefix*stringThe allocated or imported control prefix.
e.g. AAB
claim_office_codestringThe claim office code. Null when not sent.
claim_timezonestringThe claim timezone. Null when not sent.
binding_kind*stringAlways sandbox for offices created through the API.
one of: sandbox
status*stringAlways ACTIVE on create.
one of: ACTIVE
address_line1*stringReturned by the current gateway build; not yet described in this reference.
address_line2*stringReturned by the current gateway build; not yet described in this reference.
capabilities*arrayReturned by the current gateway build; not yet described in this reference.
capability*stringReturned by the current gateway build; not yet described in this reference.
state*stringReturned by the current gateway build; not yet described in this reference.
city*stringReturned by the current gateway build; not yet described in this reference.
contact_email*stringReturned by the current gateway build; not yet described in this reference.
contact_name*stringReturned by the current gateway build; not yet described in this reference.
contact_phone*stringReturned by the current gateway build; not yet described in this reference.
country*stringReturned by the current gateway build; not yet described in this reference.
display_name*stringReturned by the current gateway build; not yet described in this reference.
legal_namestringReturned by the current gateway build; not yet described in this reference. Can be null.
partner_office_refstringReturned by the current gateway build; not yet described in this reference. Can be null.
postal_code*stringReturned by the current gateway build; not yet described in this reference.
profile_source*stringReturned by the current gateway build; not yet described in this reference.
profile_version*integerReturned by the current gateway build; not yet described in this reference.
state*stringReturned by the current gateway build; not yet described in this reference.

Responses

201Created
{
  "request_id": "evt_nxh76fn21ap0j5ans7at",
  "facility_id": "fac_2b5g4kw42z7zqt0y8046",
  "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
  "group_id": null,
  "billing_npi": "9999900241",
  "tin_type": "EI",
  "control_prefix": "NEW",
  "claim_office_code": "NORTH",
  "claim_timezone": "America/New_York",
  "binding_kind": "sandbox",
  "status": "ACTIVE",
  "display_name": "",
  "legal_name": null,
  "address_line1": "",
  "address_line2": "",
  "city": "",
  "state": "",
  "postal_code": "",
  "country": "US",
  "contact_name": "",
  "contact_email": "",
  "contact_phone": "",
  "partner_office_ref": null,
  "profile_source": "customer_supplied",
  "profile_version": 1,
  "capabilities": [
    {
      "capability": "claims",
      "state": "UNCONFIGURED"
    },
    {
      "capability": "attachments",
      "state": "UNCONFIGURED"
    },
    {
      "capability": "eligibility",
      "state": "UNCONFIGURED"
    },
    {
      "capability": "remittances",
      "state": "UNCONFIGURED"
    }
  ]
}
409Office exists
{
  "error": "FACILITY_EXISTS",
  "message": "A facility identity or claim office code is already reserved",
  "errors": [],
  "request_id": "evt_944a31r81kthrt83daef"
}
422NPI check digit
{
  "error": "VALIDATION",
  "message": "billingNpi fails the 80840 Luhn check digit",
  "errors": [
    {
      "billingNpi": "check digit invalid"
    }
  ],
  "request_id": "evt_d6sx71qdq9t9ctx4t3j3"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks admin
409FACILITY_EXISTSSame NPI and TIN, or the claim office code, already used in your seller
422VALIDATIONNPI check digit fails, or groupId is not a group of your seller
422INVALID_REQUESTMissing field, wrong pattern, unknown field, office code without timezone, unknown timezone
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
ImportantNot idempotent. If a create response is lost, call List offices to find the office before retrying; a repeat returns 409 FACILITY_EXISTS.
InfoThe route is seller wide: an office or group scoped admin key can create offices it cannot reach afterwards.
ImportantA controlPrefix that is malformed (for example AB) or already registered currently returns an unhandled 500 with a plain text body. Nothing is created.
Infotaxonomy and neaFacilityId are accepted but not echoed; read them back with List offices.
InfoThe current gateway build also accepts optional office profile fields (displayName, legalName, addressLine1, addressLine2, city, state, postalCode, country, contactName, contactEmail, contactPhone, partnerOfficeRef) and echoes profile fields plus a capabilities list. This reference does not yet document them.
API reference

Import offices in bulk

POSThttps://api.claimhouse.ai/v1/facilities/import

Create up to 500 offices in one call with a verdict for each row.

Each row is created in its own transaction, in order, so one bad row does not stop the rest. The response is 200 OK even when rows are created.

The whole body is schema checked first: a row with a schema problem (an 11-digit NPI, an unknown field, an office code without a timezone) fails the entire request with 422 and creates nothing. Only NPI check digit, duplicate and group checks are judged per row.

Permission
admin
Idempotency
none
Side effects
Creates each accepted office exactly like Create an office. Rows created before a later failure stay created. No vendor call.
In the dashboard
Settings > Organization > Offices > Add offices

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Request body

NameTypeDescription
facilities*arrayOffice rows, 1 to 500, processed in order. Each row takes the same fields and constraints as the Create an office body.
billingNpi*stringThe office's billing NPI. Ten digits with a valid NPI check digit.
e.g. 1234567893
tin*stringThe billing TIN, nine digits with no dashes. Stored for billing identity checks and never returned.
e.g. 000000000
tinTypestringThe TIN type. Defaults to EI.
one of: EI · SY
taxonomystringBilling taxonomy code, up to 10 characters. Defaults to empty string. The API does not check the pattern.
e.g. 1223G0001X
groupIdstringA group of your seller to place the office in. Up to 64 characters. There is no route to create or list groups.
neaFacilityIdstringAn NEA facility id you already hold, up to 64 characters. Setting it makes NEA registration refuse this office with 409.
officeIdstringYour own office reference, up to 64 characters. Stored but never returned.
e.g. your-office-17
controlPrefixstringImport a control prefix that already stamps claims elsewhere: 3 or 4 uppercase letters or digits, not already registered. Omit to let the gateway allocate the next free code.
claimOfficeCodestringClaim office code, matching ^[A-Z][A-Z0-9]{1,7}$. Send together with claimTimezone.
e.g. NORTH
claimTimezonestringA valid IANA timezone, up to 64 characters. Send together with claimOfficeCode.
e.g. America/New_York

Request example

{
  "facilities": [
    {
      "billingNpi": "9999900258",
      "tin": "990000025"
    },
    {
      "billingNpi": "1234567890",
      "tin": "990000778"
    },
    {
      "billingNpi": "9999900258",
      "tin": "990000025"
    },
    {
      "billingNpi": "9999900266",
      "tin": "990000026"
    }
  ]
}

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000103
received*integerNumber of rows in the request.
e.g. 3
created*integerNumber of rows with status created.
e.g. 1
results*arrayOne entry per row, in request order.
index*integerZero-based row index.
e.g. 0
status*stringThe verdict for this row.
one of: created · duplicate · invalid
facility_idstringThe new office id. Present only when created.
control_prefixstringThe allocated or imported control prefix. Present only when created.
errorstringWhy the row was refused. Present only when invalid: billingNpi fails the 80840 Luhn check digit, groupId names a group this seller does not have, or rejected for any other failure.

Responses

200OK
{
  "request_id": "evt_ynxr9rqjmte6a8cr2ch4",
  "received": 4,
  "created": 2,
  "results": [
    {
      "index": 0,
      "status": "created",
      "facility_id": "fac_rk50recfer1p4h8rrb4w",
      "control_prefix": "NEW"
    },
    {
      "index": 1,
      "status": "invalid",
      "error": "billingNpi fails the 80840 Luhn check digit"
    },
    {
      "index": 2,
      "status": "duplicate"
    },
    {
      "index": 3,
      "status": "created",
      "facility_id": "fac_2b3hhwz195xxnc9myfch",
      "control_prefix": "NEW"
    }
  ]
}
422Schema failure
{
  "error": "INVALID_REQUEST",
  "message": "the request body is not valid",
  "errors": [
    {
      "code": "string_pattern_mismatch",
      "location": "body.facilities.1.billingNpi",
      "message": "String should match pattern",
      "value_redacted": true
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks admin
422INVALID_REQUESTEmpty or over 500 rows, or any row fails the schema
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
ImportantNot idempotent. Sending the same rows again reports the already created ones as duplicate.
InfoThe dashboard accepts a CSV with columns billing_npi, tin, tin_type, taxonomy, control_prefix, group_id, partner_office_ref, claim_office_code, claim_timezone and converts it to this JSON body. The API itself takes JSON only.
InfoA bad or already registered controlPrefix is reported per row as invalid with error rejected.
API reference

Get office capabilities

GEThttps://api.claimhouse.ai/v1/facilities/{facility_id}/capabilities

Read the readiness state of each Claim House service for one office.

Returns one row per service (claims, attachments, eligibility, remittances) with its readiness state for the office, ordered by capability name. Offices created through the API get all four rows at UNCONFIGURED.

Today this route answers only signed-in dashboard sessions whose membership holds offices.read and covers the office. API keys always receive 404.

Permission
Dashboard session with read and offices.read (API keys receive 404)
Idempotency
none
Side effects
Read only. No audit entry and no vendor call.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token as Bearer <session token>. API keys are refused (404).
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature

Path parameters

NameTypeDescription
facility_id*stringThe office id (fac_...). Must be covered by the session's office access.
e.g. fac_tycrfy0cs0qc7sq95eme

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000104
capabilities*arrayOne row per capability for this office. Empty when the office has no capability rows.
seller_id*stringYour seller id.
e.g. sel_EXAMPLE0000000000001
facility_id*stringThe office id.
e.g. fac_EXAMPLE0000000000001
capability*stringThe service the row describes.
one of: attachments · claims · eligibility · remittances
state*stringThe office's readiness for this service. New offices start at UNCONFIGURED.
one of: UNCONFIGURED · PENDING_VERIFICATION · READY · BLOCKED · SUSPENDED
reason_codestringWhy the capability is in its state. Null when not set.
evidence_referencestringReference to the evidence reviewed for this state. Null when not set.
reviewed_bystringWho last reviewed the capability. Null when never reviewed.
reviewed_atdatetimeWhen the capability was last reviewed. Null when never reviewed.
version*integerRow version, starting at 1.
e.g. 1

Responses

200OK
{
  "request_id": "evt_4205ze8wkh0wgkrrzb72",
  "capabilities": [
    {
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "capability": "attachments",
      "state": "UNCONFIGURED",
      "reason_code": null,
      "evidence_reference": null,
      "reviewed_by": null,
      "reviewed_at": null,
      "version": 1
    },
    {
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "capability": "claims",
      "state": "UNCONFIGURED",
      "reason_code": null,
      "evidence_reference": null,
      "reviewed_by": null,
      "reviewed_at": null,
      "version": 1
    },
    {
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "capability": "eligibility",
      "state": "UNCONFIGURED",
      "reason_code": null,
      "evidence_reference": null,
      "reviewed_by": null,
      "reviewed_at": null,
      "version": 1
    },
    {
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "capability": "remittances",
      "state": "UNCONFIGURED",
      "reason_code": null,
      "evidence_reference": null,
      "reviewed_by": null,
      "reviewed_at": null,
      "version": 1
    }
  ]
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such office",
  "errors": [],
  "request_id": "evt_hvmk24krwqtwq9dbndsy"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks read
404NOT_FOUNDAny API key, a session without offices.read, or an office outside the session's access
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
ImportantBeta. API key callers always get 404 NOT_FOUND, even for their own offices.
InfoNo partner route changes a capability state, so rows stay UNCONFIGURED until Claim House verifies the service for the office.
InfoThe route does not check that the office exists: a session with access to all offices gets 200 with an empty capabilities array for an unknown id. Offices created before capability tracking also return an empty array.
API reference

Set claim naming

PUThttps://api.claimhouse.ai/v1/facilities/{facility_id}/claim-naming

Set the permanent office code and timezone used to build human claim references.

Sets the code and timezone the gateway uses to give each claim a reference such as NORTH-20260914-0001 when its file's transport is confirmed. Set it once per office before its first production claims.

The setting is permanent. Sending the same values again returns 200 and changes nothing; different values after they are set return 409.

Permission
admin
Idempotency
none
Side effects
Writes the office's claim naming once and records one audit entry. No vendor call.
In the dashboard
Settings > Organization > Offices > Office detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
facility_id*stringThe office id (fac_...), 1 to 64 characters. Must be an office of your seller inside your key's grants.
e.g. fac_bkzth74mqx09wergap5c

Request body

NameTypeDescription
claimOfficeCode*stringOffice code used in references. Two to eight characters matching ^[A-Z][A-Z0-9]{1,7}$, unique inside your seller.
e.g. NORTH
claimTimezone*stringA valid IANA timezone, 1 to 64 characters. Decides which calendar day a reference carries.
e.g. America/New_York

Request example

{
  "claimOfficeCode": "NORTH",
  "claimTimezone": "America/New_York"
}

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000105
facility_id*stringThe office id.
e.g. fac_EXAMPLE0000000000001
claim_office_code*stringThe code you sent.
e.g. NORTH
claim_timezone*stringThe timezone you sent.
e.g. America/New_York

Responses

200OK
{
  "request_id": "evt_vcdcf0wr9c9n71mzpn74",
  "facility_id": "fac_bkzth74mqx09wergap5c",
  "claim_office_code": "NORTH",
  "claim_timezone": "America/New_York"
}
409Naming already fixed
{
  "error": "CLAIM_NAMING_IMMUTABLE",
  "message": "The office naming configuration is already fixed",
  "errors": [],
  "request_id": "evt_ddyvwhyaeqvx4j4ayhja"
}
409Code taken
{
  "error": "OFFICE_CODE_TAKEN",
  "message": "Office code is already reserved",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks admin
403FACILITY_NOT_GRANTEDThe office is outside the credential's grants
404NOT_FOUNDNo such office in your seller
409OFFICE_CODE_TAKENAnother office of your seller uses this code
409CLAIM_NAMING_IMMUTABLEThe office already has a different code or timezone
422INVALID_REQUESTMissing field, bad code pattern, unknown timezone or unknown field
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
InfoClaims transported before naming was set never receive a reference; there is no backfill.
InfoThe reference appears as claim_reference on claim reads, GET /v1/claims?tenant_claim_id= also matches it, and single-claim artifact downloads use it in their filenames.
ImportantRarely, a timezone name the gateway accepts is refused by the database and the call returns an unhandled 500.
API reference

List office enrollments

GEThttps://api.claimhouse.ai/v1/facilities/{facility_id}/enrollments

List the office's enrollment state with each payer for each transaction type.

Check it before sending production claims to a payer. For every payer in a claim file, a missing dental_claim row or any state other than LIVE or NOT_REQUIRED refuses the submission with 409 PAYER_NOT_ENROLLED.

Returns at most 500 rows, ordered by payer_id then transaction_type. An office with no rows returns an empty array.

Permission
read
Idempotency
none
Side effects
Records one audit entry. No vendor call.
In the dashboard
Settings > Organization > Offices > Office detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
facility_id*stringThe office id (fac_...), 1 to 64 characters. Must be an office of your seller inside your key's grants.
e.g. fac_tycrfy0cs0qc7sq95eme

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000106
facility_id*stringThe office id.
e.g. fac_EXAMPLE0000000000001
enrollments*arrayOne row per payer and transaction type.
payer_id*stringThe payer id the enrollment is keyed on.
e.g. EXAMPLEPAYER1
transaction_type*stringThe transaction the enrollment covers. Only dental_claim gates claim intake today.
one of: dental_claim · claim_status · eligibility · remittance
state*stringThe enrollment state.
one of: NOT_REQUIRED · REQUESTED · PENDING · LIVE · REJECTED
requested_atdatetimeWhen enrollment was requested. Null when never requested.
live_atdatetimeWhen the enrollment went live. Null until live.

Responses

200OK
{
  "request_id": "evt_8dsqp0xza7ev5np54cwa",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "enrollments": [
    {
      "payer_id": "TESTPAYER1",
      "transaction_type": "dental_claim",
      "state": "LIVE",
      "requested_at": "2026-09-05T14:30:00+00:00",
      "live_at": "2026-09-05T14:30:00+00:00"
    }
  ]
}
403Office not granted
{
  "error": "FACILITY_NOT_GRANTED",
  "message": "this key's grants do not cover that facility",
  "errors": [
    {
      "facility_id": "fac_qnwrs4rq9tjtqwv37phe"
    }
  ],
  "request_id": "evt_w7r75h0tw0pcyt2p6yc6"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks read
403FACILITY_NOT_GRANTEDThe office is outside the credential's grants
404NOT_FOUNDNo such office in your seller
422INVALID_REQUESTfacility_id longer than 64 characters
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
InfoEnrollment rows are set by the Claim House enrollment desk. No route creates or requests an enrollment.
API reference

Register an office with NEA

POSThttps://api.claimhouse.ai/v1/facilities/{facility_id}/nea-registration

Register the office with the NEA attachment network so its claim attachments can be sent there.

Send it once per office with practice details, never patient data. The gateway makes exactly one registration attempt with the attachment network and never retries it. On success it stores the network's facility id on the office.

The office's registered TIN is always sent as the doctor tax id. taxId in the body is only a confirmation and must match it. This response is camelCase and has no request_id field; read the X-Request-Id header instead.

Permission
admin (seller scoped credential only)
Idempotency
none
Side effects
Contacts the NEA attachment network exactly once, stores the returned facility id with the practice profile in a sealed record, and records one audit entry. Never retried.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
facility_id*stringThe office id (fac_...), 1 to 64 characters. Must be an office of your seller inside your key's grants.
e.g. fac_j0h554r1qy2nzrkvspb4

Request body

NameTypeDescription
practiceName*stringPractice name, 1 to 100 characters.
e.g. Sample Dental Group
address1*stringStreet address, 1 to 100 characters.
e.g. 100 Sample Street
city*stringCity, 1 to 50 characters.
e.g. Sampleville
state*stringTwo-letter state. Sent uppercased.
e.g. OH
zip*stringZIP code, five or nine digits with no dash.
e.g. 44000
phone*stringPractice phone, 10 to 20 characters.
e.g. 5555550100
faxstringPractice fax, up to 20 characters. Sent only when present.
contactNamestringContact person, up to 100 characters. Defaults to empty string.
e.g. Office Manager
contactEmailstringContact email, up to 100 characters. Defaults to empty string.
e.g. office@example.com
doctorFirstNamestringDoctor first name, up to 50 characters. Defaults to empty string.
e.g. Sample
doctorLastNamestringDoctor last name, up to 50 characters. Defaults to empty string.
e.g. Provider
taxIdstringOptional confirmation of the office's TIN, nine digits. If sent it must equal the registered TIN.
specialtyCodeIdintegerAttachment network specialty code, 0 or more. Defaults to 0.
e.g. 0
partnerCustomerIdstringYour customer id at the attachment network, up to 64 characters. Defaults to the facility_id.
promoCodestringAttachment network promo code, up to 32 characters. Defaults to empty string.
usernamestringAttachment network username, up to 64 characters. Defaults to empty string. No password is sent or stored.

Request example

{
  "practiceName": "Sample Dental Group",
  "address1": "100 Sample Street",
  "city": "Sampleville",
  "state": "OH",
  "zip": "44000",
  "phone": "5555550100",
  "contactName": "Office Manager",
  "contactEmail": "office@example.com",
  "doctorFirstName": "Sample",
  "doctorLastName": "Provider"
}

Response fields

NameTypeDescription
facilityId*stringThe office id.
e.g. fac_EXAMPLE0000000000001
neaFacilityId*stringThe attachment network's facility id for this office.
e.g. EXAMPLE-NEA-0001
credentialId*stringId of the sealed registration record (art_...).
e.g. art_EXAMPLE0000000000001
registeredAt*datetimeWhen the gateway recorded the registration.
responseKeysarrayArray of strings. The sorted top-level key names the network returned, with no values.
recordedOnFacilitybooleanFalse when the id was sealed but the office already carried an id from a concurrent registration. Claim House reconciles it.
e.g. true

Responses

201Created
{
  "facilityId": "fac_j0h554r1qy2nzrkvspb4",
  "neaFacilityId": "NEAFAC-0001",
  "credentialId": "art_e019bmb636ej658wy065",
  "registeredAt": "2026-09-05T14:30:00+00:00",
  "responseKeys": [
    "facilityId"
  ],
  "recordedOnFacility": true
}
502Registration failed or uncertain
{
  "error": "NEA_REGISTRATION_FAILED",
  "message": "the attachment network refused or did not answer the registration; one attempt was made and it is not repeated automatically",
  "errors": [
    {
      "error_class": "NeaTransportError"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
409Already registered
{
  "error": "NEA_FACILITY_ALREADY_REGISTERED",
  "message": "this facility already carries an NEA facility id",
  "errors": [],
  "request_id": "evt_4sxr7065y35003arwn90"
}

Errors

StatusCodeWhen
422INVALID_REQUESTBody or path fails the schema (checked first)
503NEA_REGISTRATION_UNAVAILABLENEA registration is not configured on this gateway
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks admin
403SELLER_KEY_REQUIREDThe credential is group or office scoped
404NOT_FOUNDNo such office in your seller
409NEA_FACILITY_ALREADY_REGISTEREDThe office already has an nea_facility_id
403BILLING_IDENTITY_MISMATCHtaxId was sent and differs from the office's TIN
502NEA_REGISTRATION_FAILEDThe network refused, answered without an id, or did not answer
500NEA_CREDENTIAL_NOT_SEALEDRegistered at the network but the record did not save
ImportantNever repeat a failed call. After 502 NEA_REGISTRATION_FAILED with NeaTransportError or NeaResponseError the network may already have created the facility, and a second call can register the practice twice. Contact Claim House support with the X-Request-Id.
ImportantAfter 500 NEA_CREDENTIAL_NOT_SEALED the practice is registered but not recorded on the office. Do not call again; send the X-Request-Id and the nea_facility_id from errors to Claim House support.
Infoerrors[0].error_class on a 502 is one of NeaAuthError, NeaRequestError, NeaTransportError, NeaResponseError.
InfoThe body is lenient: unknown fields are ignored and strings are trimmed. This route is not counted against the per-key rate limits.
InfoAn office created with neaFacilityId already set is refused with 409.
API reference

Read an office's plan choice

GEThttps://api.claimhouse.ai/v1/facilities/{facility_id}/payer-plans/{payer_id}

The plan this office chose for this payer, if any.

Returns plan: null when the office has not chosen one. That is a 200, not a 404.

Permission
read
Idempotency
none
Side effects
Writes an audit read row. No vendor call.
In the dashboard
Attachments > New attachment (plan picker)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
facility_id*stringThe office. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
payer_id*stringThe claim payer id.
e.g. CX014

Request example

null

Response fields

NameTypeDescription
facility_id*stringEchoed office id.
payer_id*stringEchoed payer id.
planobjectplan_id and name, or null.
chosen_bystringWho chose it.
chosen_atstringWhen it was chosen.

Responses

200OK
{
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "payer_id": "CX014",
  "plan": {
    "plan_id": "apl_2a156835fbfcc398",
    "name": "DQ/Emblem (Emblem Health Medicaid)"
  },
  "chosen_by": "key_7qm2x9d4hs3ve6kt8wbn",
  "chosen_at": "2026-09-22T14:05:11Z"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
403FACILITY_NOT_GRANTEDYour key has no access to that office
API reference

Remember an office's plan choice

PUThttps://api.claimhouse.ai/v1/facilities/{facility_id}/payer-plans/{payer_id}

Save the plan for this office so the question is asked once.

Later requirement lookups that pass facility_id resolve plan sensitive codes on their own, and report plan_selection: remembered.

Permission
read
Idempotency
none
Side effects
Stores the choice for this office and writes an audit row. No vendor call.
In the dashboard
Attachments > New attachment (Remember for this office)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
facility_id*stringThe office. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
payer_id*stringThe claim payer id.
e.g. CX014

Request body

NameTypeDescription
plan_id*stringAn opaque plan id belonging to that payer.
e.g. apl_2a156835fbfcc398

Request example

{
  "plan_id": "apl_2a156835fbfcc398"
}

Response fields

NameTypeDescription
facility_id*stringEchoed office id.
payer_id*stringEchoed payer id.
plan*objectThe saved plan.
chosen_by*stringWho chose it.
chosen_at*stringWhen it was chosen.

Responses

200OK
{
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "payer_id": "CX014",
  "plan": {
    "plan_id": "apl_2a156835fbfcc398",
    "name": "DQ/Emblem (Emblem Health Medicaid)"
  },
  "chosen_by": "key_7qm2x9d4hs3ve6kt8wbn",
  "chosen_at": "2026-09-22T14:05:11Z"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
422PLAN_NOT_FOR_PAYERThat plan does not belong to this payer
403FACILITY_NOT_GRANTEDYour key has no access to that office
API reference

Forget an office's plan choice

DELETEhttps://api.claimhouse.ai/v1/facilities/{facility_id}/payer-plans/{payer_id}

Remove the saved plan for this office and payer.

Returns 204 whether or not a choice was stored.

Permission
read
Idempotency
none
Side effects
Removes the stored choice and writes an audit row. No vendor call.
In the dashboard
Attachments > New attachment (plan picker)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
facility_id*stringThe office. Must be granted to your key.
e.g. fac_tycrfy0cs0qc7sq95eme
payer_id*stringThe claim payer id.
e.g. CX014

Request example

null

Responses

204No content
null

Errors

StatusCodeWhen
401UNAUTHORIZEDKey missing, malformed, unknown or revoked
403PERMISSION_DENIEDKey lacks read
403FACILITY_NOT_GRANTEDYour key has no access to that office
API reference

Events and files

Pull the append-only event feed and download the raw X12 and PDF bytes behind each event.

Every change Claim House records about your claims, eligibility checks and linked attachment sends is an event. Events are append-only and never updated or deleted. Webhooks push the same rows this feed returns. Claim events produced by the state machine carry kind, from_state, applied (true when the event moved the claim), state (after) and verdict_state (what the event implies on its own) in summary.

TypeMeaningClaim state effectartifact_idKey summary fields
claim.receivedSubmission stored but held at intake for review.Created in HOLD.nonesubmission_id, state, finding_codes
claim.queuedPassed intake, waiting for a transport window.Created in QUEUED.nonesubmission_id, state, finding_codes
submission.rejected_pre_transportIntake edits rejected the submission, one event per claim. Nothing sent.Created in REJECTED_PRE_TRANSPORT (terminal).nonesubmission_id, state, finding_codes
claim.transportedThe outbound 837D file was uploaded once and verified by read-back.Moves to TRANSPORTED.The 837D file as sentfile_id, file_stem, window_id, session_id
claim.transport_ambiguousThe single upload attempt failed or could not be verified. Never resent automatically.Moves to TRANSPORT_AMBIGUOUS.The 837D file attemptedfile_id, reason (put_failed:<ErrorClass> or readback_mismatch)
claim.operator_resolvedAn operator settled an ambiguous file with the clearinghouse.TRANSPORTED (delivered), QUEUED (not delivered) or CLOSED (dead).noneresolution, resolved_by, file_id, note
claim.ack_997A 997 acknowledgment arrived.Accepted to ACK_997_ACCEPTED; rejected to NEEDS_CORRECTION.The 997 filegroup_code, transaction_code, ack_kind, errors, error_count
claim.ack_999A 999 acknowledgment arrived.Same as claim.ack_997.The 999 fileSame as claim.ack_997, ack_kind 999
claim.status_277A 277 or 277CA claim status arrived.By STC01-1 category: received, accepted, rejected (to NEEDS_CORRECTION), pending or final. Unknown category has category_unmapped: true and no change.The 277 filecategory_code, status_code, entity_code, effective_date, payer_claim_number
claim.stalled_997No 997 within the window after transport (default 4 hours). Raised once.STALLED_997, only from TRANSPORTED.noneexpected_event_kind, due_at, sla_seconds
claim.stalled_277No 277 within the window after transport (default 2 days). Raised once.STALLED_277, only from TRANSPORTED.noneexpected_event_kind, due_at, sla_seconds
claim.paidAn 835 line paid the claim fully or partly.Moves to PAID (not from REJECTED_PRE_TRANSPORT).The 835 fileclaim_status_code, remittance_id, payment_id, charged_amount, paid_amount, patient_responsibility, trace_number, payment_date
claim.deniedAn 835 line with CLP02 4 denied the claim.Moves to DENIED.The 835 fileSame as claim.paid
claim.reversedAn 835 line with CLP02 22 reversed a payment.Moves to REVERSED.The 835 fileSame as claim.paid
remittance.receivedAn 835 file included at least one of your claims. One per seller per file.None (applied: false).The 835 fileremittance_id, claim_count, paid_amount, claim_ids, trace_number, payment_method, payer_id
eligibility.checkedAn eligibility check finished.No claim (claim_id is null).noneeligibility_id, payer_id, status, outcome, cache, mode
eligibility.pdf_generatedA PDF of an eligibility answer was generated.No claim.The PDFeligibility_id, artifact_id, filename, bytes
attachment.sentAn attachment packet was delivered and named a resolvable claim.None (applied: false).nonepacket_id, payer_id, door, outcome (OK), reference, send_id
attachment.failedAn attachment send failed or was ambiguous and named a resolvable claim.None.noneSame as attachment.sent, outcome FAILED or AMBIGUOUS
attachment.packet_createdA packet was created.Written only to the packet's own timeline, so it does not appear in this feed today.nonepacket_id, attachment_count, deduplicated_count, kinds
  1. Load your stored cursor (start at 0). The cursor is a plain integer and never expires.
  2. Call GET /v1/events?since_cursor=<cursor>&limit=500. Events come back oldest first in ascending sequence.
  3. Process each event, skipping any event_id you already processed. sequence is shared by every seller, so gaps are normal.
  4. Store next_cursor only after processing succeeds. On an empty page it echoes your since_cursor.
  5. If has_more is true, repeat immediately; otherwise wait 30 to 60 seconds. Resume from a cursor a few hundred sequence numbers behind your last one to catch late-committing events, and de-duplicate by event_id.
  • Out-of-order evidence is normal: a 277 can arrive before its 997. Use applied and state, not the event type, to know the current claim state.
  • occurred_at is not monotonic with sequence. Never order or resume by time.
  • One inbound 997, 999, 277 or 835 file is copied once per seller and office, so several claims can point at the same artifact_id. Parse it by your patient control number.
  • Summaries never contain patient names, dates of birth or member ids. They can contain control numbers and amounts.
RecordIdWhere you find the idWhat you get
Artifactart_...artifact_id on events, remittance records and the eligibility PDF responseA short-lived download URL for one stored document (837D, 997, 999, 277, 835 or eligibility PDF).
Filefile_...transport.file_id on a submission, or summary.file_id on transport eventsThe transport record of one outbound 837D file (control numbers, hash, state) plus a download URL when one exists.
API reference

List events

GEThttps://api.claimhouse.ai/v1/events

Page through your events after a cursor, oldest first.

Use it to keep a local copy of claim status in sync without webhooks, or to catch up after a webhook outage. Events are returned in ascending sequence after since_cursor; there is no newest-first option.

There are no facility_id, type, claim_id or time filters. Unknown query parameters are silently ignored, so sending facility_id still returns every office your key can see.

Permission
read
Idempotency
none
Side effects
Records one audit entry listing the returned event ids. No vendor call.
In the dashboard
Overview > Recent updates > View all

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
since_cursorintegerReturn events with sequence strictly greater than this. 0 or more. Defaults to 0, the start of your history.
e.g. 1040
limitintegerMaximum events per page, 1 to 500. Defaults to 100.
e.g. 500

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id. Starts with evt_ but is not an event id.
e.g. evt_EXAMPLE0000000000201
events*arrayEvents in ascending sequence order.
event_id*stringUnique event id (evt_...). Use it to de-duplicate.
e.g. evt_EXAMPLE0000000000001
sequence*integerPosition in the global event order. Shared by every seller, so gaps are normal.
e.g. 1102
type*stringThe event type. See the event types table in the group guide.
seller_id*stringYour seller id.
e.g. sel_EXAMPLE0000000000001
facility_id*stringThe office the event belongs to.
e.g. fac_EXAMPLE0000000000001
claim_idstringThe claim id. Null for eligibility events.
submission_idstringThe submission that created the claim. Null when there is no claim.
tenant_claim_idstringYour own claim id from the claim. Null when there is no claim or you did not send one.
artifact_idstringThe downloadable file behind the event (art_...). Null when the event has none.
occurred_at*datetimeWhen the event happened, not when it was recorded. Not monotonic with sequence.
summary*objectThe event payload. Keys depend on type; never contains patient names, dates of birth or member ids.
next_cursor*integerThe sequence of the last event on this page, or your since_cursor echoed back when the page is empty. Pass it as since_cursor next time.
e.g. 1180
has_more*booleanTrue when the page is full. A full page can be followed by an empty one.
e.g. true

Responses

200OK
{
  "request_id": "evt_d5n518639ebw3xje0skd",
  "events": [
    {
      "event_id": "evt_2mqqtwmx1yjkbdyahj8b",
      "sequence": 1101,
      "type": "claim.received",
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "claim_id": "clm_4nadnpmb8qf0yghd67tt",
      "submission_id": "sub_vpv0qhdf31xgbnwmhwgf",
      "tenant_claim_id": "SYN0029",
      "artifact_id": null,
      "occurred_at": "2026-09-05T14:30:00+00:00",
      "summary": {
        "verdict": "ACCEPTED"
      }
    },
    {
      "event_id": "evt_b216762rs28w5267tkjr",
      "sequence": 1102,
      "type": "claim.queued",
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "claim_id": "clm_4nadnpmb8qf0yghd67tt",
      "submission_id": "sub_vpv0qhdf31xgbnwmhwgf",
      "tenant_claim_id": "SYN0029",
      "artifact_id": null,
      "occurred_at": "2026-09-05T14:30:00+00:00",
      "summary": {
        "verdict": "ACCEPTED"
      }
    },
    {
      "event_id": "evt_bf0tw4q7kp4x91e64ysf",
      "sequence": 1103,
      "type": "claim.transported",
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "claim_id": "clm_4nadnpmb8qf0yghd67tt",
      "submission_id": "sub_vpv0qhdf31xgbnwmhwgf",
      "tenant_claim_id": "SYN0029",
      "artifact_id": "art_yra20t2p1y5vc2j3tbb1",
      "occurred_at": "2026-09-05T14:30:00+00:00",
      "summary": {
        "verdict": "ACCEPTED"
      }
    },
    {
      "event_id": "evt_bz9e3h69114b9nw3wvq8",
      "sequence": 1104,
      "type": "claim.ack_997",
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "claim_id": "clm_4nadnpmb8qf0yghd67tt",
      "submission_id": "sub_vpv0qhdf31xgbnwmhwgf",
      "tenant_claim_id": "SYN0029",
      "artifact_id": "art_6j8hta8983300cx6kw9s",
      "occurred_at": "2026-09-05T14:30:00+00:00",
      "summary": {
        "verdict": "ACCEPTED"
      }
    }
  ],
  "next_cursor": 1104,
  "has_more": false
}
422Invalid cursor or limit
{
  "error": "INVALID_REQUEST",
  "message": "the request body is not valid",
  "errors": [
    {
      "code": "less_than_equal",
      "location": "query.limit",
      "message": "Input should be less than or equal to 500",
      "value_redacted": true
    }
  ],
  "request_id": "evt_ajj4gkae9a6vfggbcg4f"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks read
403FACILITY_NOT_GRANTEDThe key has no office grant at all
422INVALID_REQUESTsince_cursor negative or not an integer, or limit outside 1 to 500
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
ImportantSequence numbers are assigned at insert, not at commit, so an event with a lower sequence can become visible after a higher one. Resume from a cursor somewhat behind your last next_cursor and drop event_ids you already processed.
InfoOffice and group scoped keys see only events in their granted offices. remittance.received is attached to the first of your claims in the 835, so a key for a different office may not see it.
InfoThe cursor is a plain integer that never expires. There is no INVALID_CURSOR error on this route.
ImportantIn the current build, claim.transported and claim.transport_ambiguous events may not be recorded, so do not treat their absence as proof a claim was not sent.
API reference

Get an artifact

GEThttps://api.claimhouse.ai/v1/artifacts/{artifact_id}

Get a short-lived download URL for one stored document.

Returns a freshly signed URL to one stored 837D, 997, 999, 277, 835 or eligibility PDF, plus the claim and event it belongs to. Call it at the moment a user clicks download, not when you render a page.

Inbound 997, 999, 277 and 835 artifacts are the whole file as received, not a slice for one claim. When one artifact covers several claims, claim_id is the claim of the first referencing event only.

Permission
read
Idempotency
none
Side effects
Signs a new URL locally and records one audit entry. No vendor or clearinghouse call.
In the dashboard
Claims > Claim detail

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
artifact_id*stringThe artifact id (art_...), 1 to 64 characters. Must be referenced by an event in your seller and grants.
e.g. art_5ajgzw1wwge9x43v86b2

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000202
artifact_id*stringThe id you asked for.
e.g. art_EXAMPLE0000000000277
download_filenamestringSuggested file name such as NORTH-20260912-0007_277_s01_r01.edi. Null unless the first event is an 837D, 997, 999 or 277 for exactly one claim that has a claim_reference.
claim_idstringClaim of the first event that references this artifact. Null for an eligibility PDF.
facility_id*stringOffice of that first event.
e.g. fac_EXAMPLE0000000000001
event_kind*stringType of that first event.
e.g. claim.status_277
url*stringThe download URL. Anyone holding it can download the file until it expires, so never log or store it.
expires_in_seconds*integerLifetime of url in seconds.
e.g. 600

Responses

200OK
{
  "request_id": "evt_1jnvj4ackt0ndkm9awpm",
  "artifact_id": "art_5ajgzw1wwge9x43v86b2",
  "download_filename": null,
  "claim_id": "clm_sey00k954s2ctghndb3h",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "event_kind": "claim.transported",
  "url": "https://archive.example/sellers/sel_s4zf3w33k6x8j1e1z6sh/fac_tycrfy0cs0qc7sq95eme/artifacts/art_5ajgzw1wwge9x43v86b2?ttl=600",
  "expires_in_seconds": 600
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such artifact",
  "errors": [],
  "request_id": "evt_21qntm5psys23mxtpbqd"
}
503URL unavailable
{
  "error": "ARTIFACT_URL_UNAVAILABLE",
  "message": "this archive store cannot produce a retrieval URL",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks read
403FACILITY_NOT_GRANTEDThe key has no office grant at all
404NOT_FOUNDNo event in your seller and grants references this id
422INVALID_REQUESTartifact_id longer than 64 characters
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
503ARTIFACT_URL_UNAVAILABLEThe archive store cannot produce a URL
InfoFilenames use <claim_reference>_<KIND>_s01.edi for the 837D and <claim_reference>_<KIND>_s01_rNN.edi for responses, where NN counts that kind of response for the claim. 835 artifacts and eligibility PDFs never get a filename.
ImportantEach call signs a new URL that expires after expires_in_seconds (600 today). Do not cache it, log it or put it in page HTML.
InfoA local development gateway returns a file:// path on the gateway machine instead of a signed URL.
API reference

Get an outbound file

GEThttps://api.claimhouse.ai/v1/files/{file_id}

Get the transport record of one outbound 837D file and a download link when one exists.

Use it to reconcile with a clearinghouse by ISA13, GS06, file name or hash, or to see why a file is ambiguous. One file carries one office's claims for one transport window.

Find a file_id in transport.file_id on GET /v1/submissions/{submission_id}, or in summary.file_id on claim.transported, claim.transport_ambiguous and claim.operator_resolved events.

Permission
read
Idempotency
none
Side effects
Signs a new URL when a download exists and records one audit entry. No vendor call.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
file_id*stringThe file id (file_...), 1 to 64 characters. Must belong to your seller and grants.
e.g. file_mgrexn8c9nxscydn6z3y

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000203
file_id*stringThe file id.
e.g. file_EXAMPLE0000000000001
facility_id*stringThe office the file was built for.
e.g. fac_EXAMPLE0000000000001
state*stringSTAGED (archived, not yet uploaded), TRANSPORTED (uploaded once and verified), AMBIGUOUS (the single attempt failed or could not be verified; never resent automatically) or DEAD (confirmed not delivered).
one of: STAGED · TRANSPORTED · AMBIGUOUS · DEAD
remote_filename*stringThe name the file was given at the clearinghouse.
e.g. AAB_20260912140500_001.837
isa13*integerInterchange control number (ISA13).
e.g. 100000123
gs06*integerGroup control number (GS06).
e.g. 123
size_bytes*integerSize of the file bytes.
e.g. 4821
sha256*stringSHA-256 of the file bytes, lowercase hex.
transaction_count*integerNumber of 837D transaction sets from your submissions in this file.
e.g. 3
ambiguity_reason_codestringWhy the file is ambiguous, for example put_failed:TimeoutError or readback_mismatch. Null otherwise.
transported_atdatetimeWhen the verified upload completed. Null until transported.
created_at*datetimeWhen the file record was created.
updated_at*datetimeLast change to the record.
downloadobjectLink to the 837D bytes. Null for STAGED, AMBIGUOUS and DEAD files and whenever no transport event recorded an artifact.
artifact_id*stringArtifact id of the file bytes.
e.g. art_EXAMPLE0000000000837
url*stringSigned download URL. No download_filename is applied here.
expires_in_seconds*integerLifetime of url in seconds.
e.g. 600

Responses

200OK
{
  "request_id": "evt_hh3w0jvg5b6zxyrfvvez",
  "file_id": "file_mgrexn8c9nxscydn6z3y",
  "facility_id": "fac_tycrfy0cs0qc7sq95eme",
  "state": "TRANSPORTED",
  "remote_filename": "SBX_20260905143000_0001.837",
  "isa13": 101,
  "gs06": 55,
  "size_bytes": 1234,
  "sha256": "abababababababababababababababababababababababababababababababab",
  "transaction_count": 1,
  "ambiguity_reason_code": null,
  "transported_at": "2026-09-05T14:30:00+00:00",
  "created_at": "2026-09-05T14:30:00+00:00",
  "updated_at": "2026-09-05T14:30:00+00:00",
  "download": {
    "artifact_id": "art_mgyh1ky9zktqskcrensj",
    "url": "https://archive.example/sellers/sel_s4zf3w33k6x8j1e1z6sh/fac_tycrfy0cs0qc7sq95eme/artifacts/art_mgyh1ky9zktqskcrensj?ttl=600",
    "expires_in_seconds": 600
  }
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such file",
  "errors": [],
  "request_id": "evt_d5xc02v810w935gr8k8p"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks read
403FACILITY_NOT_GRANTEDThe key has no office grant at all
404NOT_FOUNDNo file with that id in your seller and grants
422INVALID_REQUESTfile_id longer than 64 characters
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
503ARTIFACT_URL_UNAVAILABLEA download exists but the archive store cannot produce a URL
ImportantAn AMBIGUOUS file is never resent automatically. Do not resubmit its claims; wait for Claim House to resolve it with the clearinghouse.
InfoIn the current build download can be null even for TRANSPORTED files, because the transport event that carries the artifact may not be recorded.
API reference

Webhooks

Register an HTTPS endpoint to receive signed claim, acknowledgment, payment and eligibility events, then inspect, replay and re-sign deliveries.

ImportantWebhook endpoints belong to the seller, not to a key. Every webhook route needs the webhooks permission and ignores the caller's office or group scope, and an endpoint receives events for every office of the seller. A seller can register at most 4 endpoints (disabled ones count) and no live route lists, edits or deletes them, so store the endpoint_id and signing secret from the create response. Subscribe to exact event names; wildcards such as claim.* are refused. The same events can also be polled with GET /v1/events.
Event typeEmitted when
claim.receivedA claim was accepted at intake but placed on hold.
claim.queuedA claim passed intake and is queued for the next dispatch window.
claim.transportedDelivery of the file carrying the claim to the clearinghouse was confirmed.
claim.transport_ambiguousThe transport outcome for the claim's file is uncertain. It is not resent automatically; a Claim House operator resolves it.
claim.operator_resolvedAn operator recorded a verdict on an ambiguous file.
claim.ack_997A 997 functional acknowledgment covering the claim arrived.
claim.ack_999A 999 implementation acknowledgment covering the claim arrived.
claim.status_277A 277 claim status response for the claim arrived.
claim.stalled_997No acknowledgment arrived within the expected window (default 4 hours after transport).
claim.stalled_277No claim status arrived within the expected window (default 2 days).
submission.rejected_pre_transportA submission was refused at intake and will never be transported.
eligibility.checkedAn eligibility check completed (including vendor errors and cache hits). Not tied to a claim.
eligibility.pdf_generatedA PDF was generated for an eligibility result.
attachment.packet_createdAn attachment packet was created. Delivered only when the event names a claim, which at creation it normally does not.
attachment.sentAn attachment send succeeded. Delivered only when the send resolved to a claim.
attachment.failedAn attachment send failed or its outcome is uncertain. Delivered only when the send resolved to a claim.
claim.paidAn 835 paid the claim.
claim.deniedAn 835 denied the claim.
claim.reversedAn 835 reversed an earlier payment.
remittance.receivedAn 835 file was processed. Sent once per seller per file, attached to the first of your claims in that file.
Payload fieldTypeDescription
event_idstringUnique event id, evt_.... Deduplicate on it.
typestringOne of the event types above.
occurred_atstringWhen the event happened, UTC, formatted YYYY-MM-DD HH:MM:SS.ffffff+00:00 with a space rather than T.
seller_idstringYour seller id.
facility_idstringOffice id.
submission_idstring or nullSubmission id when known.
claim_idstringClaim id. An empty string, not null, for events with no claim such as eligibility.checked.
tenant_claim_idstring or nullYour own claim id, when you supplied one.
artifact_idstring or nullRelated raw artifact (for example an acknowledgment file), downloadable through GET /v1/artifacts/{artifact_id}.
summaryobjectPresent only when the event carries keys such as verdict, category_code, status_code, entity_code, claim_status_code, remittance_id, payment_id, eligibility_id, payer_id, status, outcome, cache, age_seconds, vendor_latency_ms or error_class. Read the resource for more.
Delivery ruleBehavior
RequestOne POST per delivery, Content-Type: application/json, compact JSON with keys sorted alphabetically. Identifiers only, never patient data or amounts.
GuaranteeAt least once. The same event can arrive more than once.
SuccessOnly a 2xx within the 5 second timeout. A 3xx is a failure and redirects are not followed.
Retry scheduleAfter a failed attempt the next is due 1, 5, 30, 120, then 600 seconds later: six attempts over roughly 13 minutes at the fastest.
ParkingIf the sixth attempt fails the delivery is PARKED and never attempted again unless you replay it.
BacklogA new endpoint receives every past event of its subscribed types, not only new ones.
Timing and orderA worker sends in passes (by default every 30 seconds, up to 100 deliveries per pass), so delays are minimums and events can arrive out of order. Use occurred_at and resource reads, not arrival order.
Secret rotationRotating keeps the previous secret signing for 24 hours; during that window each delivery carries two v1 signatures.
# Every delivery carries this header:
#   X-BlueLine-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>
#
# v1 = lowercase hex HMAC-SHA256 of "<t>." + raw body, keyed with the whole
# signing secret (including the whsec_ prefix) as UTF-8 bytes. During a secret
# rotation the header carries two v1 values; accept if either matches.
# t is stamped once per worker pass and can be several minutes old, so allow
# 15 minutes of skew. Verify the raw bytes (never re-serialized JSON), dedupe on
# event_id, enqueue, and return 2xx within 5 seconds.
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 900


def verify_claimhouse_signature(raw_body: bytes, header: str, secret: str) -> bool:
    timestamp = None
    signatures = []
    for part in (header or "").split(","):
        name, _, value = part.strip().partition("=")
        if name == "t":
            try:
                timestamp = int(value)
            except ValueError:
                return False
        elif name == "v1":
            signatures.append(value)
    if timestamp is None or not signatures:
        return False
    if abs(time.time() - timestamp) > TOLERANCE_SECONDS:
        return False
    signed = str(timestamp).encode("ascii") + b"." + raw_body
    expected = hmac.new(secret.encode("utf-8"), signed, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(expected, candidate) for candidate in signatures)
API reference

Create a webhook endpoint

POSThttps://api.claimhouse.ai/v1/webhook-endpoints

Registers one HTTPS endpoint for a list of event types and returns its signing secret once.

Registers an HTTPS URL to receive signed event deliveries for every office of your seller. The response is the only place the signing secret appears; store it and the endpoint_id right away.

The delivery worker starts on its next pass and also sends the backlog of past events that match the subscribed types.

Permission
webhooks
Idempotency
none
Side effects
Stores the endpoint with its secret encrypted at rest; the delivery worker begins sending matching events, including past ones, on its next pass. No vendor call.
In the dashboard
Settings > Developer > Webhooks

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json.
e.g. application/json

Request body

NameTypeDescription
url*stringYour endpoint URL. 8 to 2000 characters and, after trimming, must start with https://. No other URL checks are made.
e.g. https://hooks.example.com/claimhouse
event_types*arrayArray of strings. At least one exact event type from the event types table; wildcards are refused. Duplicates are stored as sent.
one of: claim.received · claim.queued · claim.transported · claim.transport_ambiguous · claim.operator_resolved · claim.ack_997 · claim.ack_999 · claim.status_277 · claim.stalled_997 · claim.stalled_277 · submission.rejected_pre_transport · eligibility.checked · eligibility.pdf_generated · attachment.packet_created · attachment.sent · attachment.failed · claim.paid · claim.denied · claim.reversed · remittance.received
e.g. ["claim.transported","claim.ack_999","claim.status_277","claim.paid","claim.denied"]

Request example

{
  "url": "https://hooks.example.com/claimhouse",
  "event_types": [
    "claim.transported",
    "claim.ack_999",
    "claim.status_277",
    "claim.paid",
    "claim.denied"
  ]
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
endpoint*objectThe endpoint that was created.
endpoint_id*stringEndpoint id, whk_.... Store it; no live route lists endpoints.
e.g. whk_EXAMPLE0000000000000001
url*stringThe endpoint URL, trimmed.
e.g. https://hooks.example.com/claimhouse
event_types*arrayArray of strings. The subscribed event types.
e.g. ["claim.transported","claim.ack_999","claim.status_277","claim.paid","claim.denied"]
secret_version*integerVersion of the current signing secret. 1 at creation, incremented by each rotation.
e.g. 1
status*stringEndpoint status. Always ACTIVE at creation.
one of: ACTIVE · DISABLED
e.g. ACTIVE
created_at*datetimeISO 8601 creation time.
e.g. 2026-09-14T15:10:00+00:00
signing_secret*stringThe signing secret, whsec_ followed by 43 URL-safe characters. Shown only in this response.
e.g. whsec_EXAMPLE
signing_secret_note*stringReminder that the secret is shown once and cannot be read back.
e.g. shown once; it is stored encrypted and cannot be read back

Responses

201Created
{
  "request_id": "evt_m0t7q5dnex75ewyanfgn",
  "endpoint": {
    "endpoint_id": "whk_4kkfm6bme0n9nf2kf4hs",
    "url": "https://hooks.example.com/claimhouse",
    "event_types": [
      "claim.transported",
      "claim.ack_999",
      "claim.status_277",
      "claim.paid",
      "claim.denied"
    ],
    "secret_version": 1,
    "status": "ACTIVE",
    "created_at": "2026-09-05T14:30:00+00:00"
  },
  "signing_secret": "whsec_EXAMPLEsigningsecret0000000000000000000",
  "signing_secret_note": "shown once; it is stored encrypted and cannot be read back"
}
400Unknown event type
{
  "error": "UNKNOWN_EVENT_TYPE",
  "message": "one or more event types are not published by this gateway",
  "errors": [
    {
      "event_types": [
        "claim.*"
      ]
    }
  ],
  "request_id": "evt_qp8qgwm3ztwd2k7x3d0r"
}
409Endpoint limit reached
{
  "error": "WEBHOOK_ENDPOINT_LIMIT",
  "message": "a seller may register at most 4 endpoints",
  "errors": [],
  "request_id": "evt_j6fspw8jxs8kgb3weyxc"
}

Errors

StatusCodeWhen
400INVALID_URLurl does not start with https://
400UNKNOWN_EVENT_TYPEone or more event types are not published; errors[0].event_types lists them
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks webhooks
409WEBHOOK_ENDPOINT_LIMITseller already has 4 endpoints, active or disabled
422INVALID_REQUESTurl length out of range, or event_types missing or empty
429TOO_MANY_REQUESTSrate or concurrency limit reached
500ENDPOINT_NOT_CREATEDthe endpoint could not be stored
ImportantNot idempotent. Repeating the call with the same URL fails with an unmapped 500 (URLs are unique per seller), and repeating it with a different URL creates another endpoint that counts toward the lifetime limit of 4.
ImportantNo API route disables, edits or deletes an endpoint. Ask Claim House support to disable one.
InfoThe dashboard Webhooks page is preview only today because it first reads the planned endpoint list; use the API to manage webhooks. The dashboard form is also stricter than the API (it refuses URLs with embedded credentials or a fragment).
API reference

List webhook endpoints

GEThttps://api.claimhouse.ai/v1/webhook-endpoints

Lists the webhook endpoints registered for your seller.

Returns every webhook endpoint registered for your seller, ordered by endpoint_id.

The signing secret is never returned. It is shown exactly once, in the create or rotate-secret response.

Permission
webhooks
Idempotency
none
Side effects
None. Read only.
In the dashboard
Settings > Developer > Webhooks

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
endpoints*arrayRegistered webhook endpoints, ordered by endpoint_id.
endpoint_id*stringEndpoint id, whk_....
e.g. whk_EXAMPLE0000000000000001
url*stringThe https URL events are posted to.
e.g. https://example.com/hooks/claimhouse
event_types*arrayEvent types this endpoint is subscribed to.
e.g. ["claim.status_changed"]
secret_version*integerSigning secret version; increments on each rotation.
e.g. 2
status*stringEndpoint state.
one of: ACTIVE · DISABLED
e.g. ACTIVE
created_at*datetimeISO 8601 creation time.
e.g. 2026-09-05T14:30:00+00:00

Responses

200OK
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "endpoints": [
    {
      "endpoint_id": "whk_35ka7045j25e5w5h2a32",
      "url": "https://example.com/hooks/claimhouse",
      "event_types": [
        "claim.status_changed",
        "remittance.posted"
      ],
      "secret_version": 2,
      "status": "ACTIVE",
      "created_at": "2026-09-05T14:30:00+00:00"
    }
  ]
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks webhooks
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoEndpoint ids are always recoverable here — you do not need to store the create response. The signing secret is the only field that is shown once and never read back.
API reference

List webhook deliveries

GEThttps://api.claimhouse.ai/v1/webhook-endpoints/{endpoint_id}/deliveries

Returns the delivery attempts recorded for one endpoint, newest first.

Returns one row per delivery attempt, ordered by attempted_at descending. Use it to debug your receiver and to find PARKED deliveries to replay.

There is no cursor, so only the newest limit attempts are reachable.

Permission
webhooks
Idempotency
none
Side effects
Records an audit entry for the read. No vendor call.
In the dashboard
Settings > Developer > Webhooks

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
endpoint_id*stringThe whk_... endpoint id returned when the endpoint was created. 1 to 64 characters.
e.g. whk_35ka7045j25e5w5h2a32

Query parameters

NameTypeDescription
limitintegerMaximum attempts to return, 1 to 500. Defaults to 100.
e.g. 100

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
endpoint_id*stringThe endpoint id.
e.g. whk_EXAMPLE0000000000000001
deliveries*arrayArray of attempt objects, newest first.
delivery_id*stringAttempt row id, whd_....
e.g. whd_EXAMPLE0000000000000006
event_id*stringThe event this attempt delivered.
e.g. evt_EXAMPLE0000000000000040
attempt*integerAttempt number for this event on this endpoint, starting at 1.
e.g. 6
outcome*stringDELIVERED, FAILED (another attempt is scheduled) or PARKED (no more attempts unless replayed).
one of: DELIVERED · FAILED · PARKED
e.g. PARKED
status_codeintegerHTTP status your endpoint returned. Null on timeout, connection error, or a replay marker row.
e.g. 503
error_typestringFailure class, for example TimeoutError or ConnectError. OperatorReplay marks a row written by a replay request. Null when delivered.
e.g. TimeoutError
attempted_at*datetimeISO 8601 time of the attempt.
e.g. 2026-09-14T15:24:40+00:00
next_attempt_atdatetimeWhen the next attempt is due. Null for DELIVERED and PARKED.
e.g. 2026-09-14T15:24:35+00:00

Responses

200OK
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "endpoint_id": "whk_35ka7045j25e5w5h2a32",
  "deliveries": [
    {
      "delivery_id": "whd_sgjfdhcek5dcxd02rzh8",
      "event_id": "evt_fhnrqxwezs5rkevhmkc3",
      "attempt": 1,
      "outcome": "FAILED",
      "status_code": 503,
      "error_type": "HTTP_503",
      "attempted_at": "2026-09-05T14:30:00+00:00",
      "next_attempt_at": null
    }
  ]
}
404Endpoint not found
{
  "error": "NOT_FOUND",
  "message": "no such webhook endpoint",
  "errors": [],
  "request_id": "evt_1v843gx5jrd72t8hz96z"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks webhooks
404NOT_FOUNDno such endpoint in your seller
422INVALID_REQUESTlimit out of range or endpoint_id too long
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoA row with error_type: "OperatorReplay" is not an HTTP attempt. It marks a replay request, and the real attempt that follows is recorded with the next attempt number.
API reference

Replay a webhook delivery

POSThttps://api.claimhouse.ai/v1/webhook-endpoints/{endpoint_id}/deliveries/{delivery_id}/replay

Makes the event behind a delivery due again so the worker attempts it on its next pass.

Use it after fixing your receiver to recover PARKED deliveries. delivery_id can be any attempt of the event on this endpoint; the replay continues from the event's latest attempt.

A parked event gets exactly one more attempt and parks again at once if it fails. An event still in its retry schedule is attempted on the next pass instead of waiting. A delivered event is sent again, which your receiver sees as a duplicate.

Permission
webhooks
Idempotency
none
Side effects
Appends a FAILED replay marker row (error_type: OperatorReplay) due immediately; the worker sends the event to your own endpoint on its next pass. No vendor call.
In the dashboard
Settings > Developer > Webhooks

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
endpoint_id*stringThe whk_... endpoint id returned when the endpoint was created. 1 to 64 characters.
e.g. whk_wfrqqqwhwwpzmrxvnvna
delivery_id*stringAny whd_... attempt id of the event on this endpoint. 1 to 64 characters.
e.g. whd_kbzmrtyjjrd2p000m3g0

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
endpoint_id*stringThe endpoint id.
e.g. whk_EXAMPLE0000000000000001
delivery_id*stringThe delivery id you sent.
e.g. whd_EXAMPLE0000000000000006
event_id*stringThe event that will be attempted.
e.g. evt_EXAMPLE0000000000000040
attempt*integerAttempt number of the replay marker row. The real attempt is recorded as attempt + 1.
e.g. 7
queued_for*datetimeISO 8601 time the attempt became due (now). It is sent on the worker's next pass after this time.
e.g. 2026-09-14T15:40:00+00:00

Responses

200OK
{
  "request_id": "evt_9x00h6s3av4xz8bwfw32",
  "endpoint_id": "whk_wfrqqqwhwwpzmrxvnvna",
  "delivery_id": "whd_kbzmrtyjjrd2p000m3g0",
  "event_id": "evt_fhnrqxwezs5rkevhmkc3",
  "attempt": 2,
  "queued_for": "2026-09-05T14:30:00+00:00"
}
404Delivery not found
{
  "error": "NOT_FOUND",
  "message": "no such delivery on that endpoint",
  "errors": [],
  "request_id": "evt_k2f7ax1bbrnxc0rycc4c"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks webhooks
404NOT_FOUNDno such endpoint in your seller, or the delivery is not on that endpoint
422INVALID_REQUESTa path value is longer than 64 characters
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoNot idempotent. Several calls before the worker's next pass still produce one attempt, but each call consumes an attempt number.
InfoOn a disabled endpoint the call succeeds and records the marker, but nothing is sent while the endpoint stays disabled.
API reference

Rotate a webhook signing secret

POSThttps://api.claimhouse.ai/v1/webhook-endpoints/{endpoint_id}/rotate-secret

Issues a new signing secret and keeps the previous one signing for 24 hours.

Use it on a schedule or when a secret may have leaked. For 24 hours each delivery carries two v1 signatures, one per secret, so a receiver that accepts either keeps working.

To rotate without dropping deliveries: call rotate, deploy the new secret (or accept both), and finish within 24 hours.

Permission
webhooks
Idempotency
none
Side effects
Moves the current secret to a previous slot that expires in 24 hours, stores the new secret encrypted and increments secret_version. Works on disabled endpoints too. No vendor call.
In the dashboard
Settings > Developer > Webhooks

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
endpoint_id*stringThe whk_... endpoint id returned when the endpoint was created. 1 to 64 characters.
e.g. whk_qzmjdkyqkp8abf16wk0w

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
endpoint*objectThe endpoint after rotation.
endpoint_id*stringEndpoint id, whk_.... Store it; no live route lists endpoints.
e.g. whk_EXAMPLE0000000000000001
url*stringThe endpoint URL, trimmed.
e.g. https://hooks.example.com/claimhouse
event_types*arrayArray of strings. The subscribed event types.
e.g. ["claim.transported","claim.ack_999","claim.status_277","claim.paid","claim.denied"]
secret_version*integerThe new secret version.
e.g. 2
status*stringEndpoint status.
one of: ACTIVE · DISABLED
e.g. ACTIVE
created_at*datetimeISO 8601 creation time.
e.g. 2026-09-14T15:10:00+00:00
signing_secret*stringThe new whsec_... signing secret. Shown only in this response.
e.g. whsec_EXAMPLE2
previous_secret_expires_at*datetimeISO 8601 time the previous secret stops signing (now plus 24 hours).
e.g. 2026-09-15T15:45:00+00:00

Responses

200OK
{
  "request_id": "evt_cvrq1yyzzvdtrdy8t9r1",
  "endpoint": {
    "endpoint_id": "whk_qzmjdkyqkp8abf16wk0w",
    "url": "https://hooks.example.com/claimhouse",
    "event_types": [
      "claim.transported",
      "claim.paid"
    ],
    "secret_version": 2,
    "status": "ACTIVE",
    "created_at": "2026-09-05T14:30:00+00:00"
  },
  "signing_secret": "whsec_EXAMPLEsigningsecret0000000000000000000",
  "previous_secret_expires_at": "2026-09-06T14:30:00+00:00"
}
404Endpoint not found
{
  "error": "NOT_FOUND",
  "message": "no such webhook endpoint",
  "errors": [],
  "request_id": "evt_g2qys47wvp393mz3179x"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks webhooks
404NOT_FOUNDno such endpoint in your seller
422INVALID_REQUESTendpoint_id longer than 64 characters
429TOO_MANY_REQUESTSrate or concurrency limit reached
ImportantDo not retry a rotate blindly. Every call issues a new secret, and rotating again inside the 24 hour window stops the older of the two secrets immediately. If a rotate response is lost, its secret cannot be recovered; rotate once more and deploy that secret at once.
API reference

Account and team

Confirm who a credential is, onboard a new organization, and manage the people who can use it.

Route familyCredentialNeeds an organization membership
GET /v1/meAPI key or dashboard session with readSessions: yes, active
/v1/onboarding/*Signed-in dashboard session onlyNo. Works before any organization exists
/v1/members, /v1/invitations, /v1/access-requestsDashboard session only, admin plus a member actionYes, active. API keys are refused with 403
  1. Sign in to the dashboard, then call GET /v1/onboarding/state.
  2. New company: create an application, then submit it. Joining an existing company: request access with its partner reference.
  3. Claim House reviews the application. When the organization is READY, the state route returns allowed_actions: ["enter_partner"].
  4. Organization administrators prepare invitations for teammates and adjust each member's actions and office access.
RecordStates
ApplicationDRAFT > PENDING_REVIEW > NEEDS_INFORMATION | APPROVED > PROVISIONING > READY, or DECLINED / BLOCKED
Access requestPENDING > APPROVED_FOR_INVITATION | DECLINED
InvitationPREPARED > DELIVERY_PENDING > SENT > ACCEPTED > RECONCILED; also REVOKED, EXPIRED, DELIVERY_FAILED, OUTCOME_UNCERTAIN

A member's rights are a set of actions (for example claims.read, claims.submit, members.manage) plus an office scope: ALL (every current and future office), SUBSET (listed offices) or NONE (no office data). The role is a label. An administrator can only grant actions and offices they hold themselves; anything wider is refused with 403 DELEGATION_EXCEEDED.

ImportantEvery change route uses optimistic concurrency: send the version (or a member's authorization_version) you last read as expected_version. A mismatch returns 409 STALE_VERSION_OR_STATE; reload and try again. Everything here except GET /v1/me is beta: it needs dashboard sign-in configured, and invitation email delivery is not connected yet.
API reference

Get the current identity

GEThttps://api.claimhouse.ai/v1/me

Return who the presented credential is: seller, principal, mode, scope and reachable offices.

Call it once at startup to confirm a key is wired to the seller and mode you expect.

The body has two shapes. An API key gets seller, key id, scope and reachable office ids. A signed-in dashboard session gets the person, organization, role, member actions and office scope instead.

Permission
read
Idempotency
none
Side effects
Writes one audit row naming the credential and route. No vendor call.
In the dashboard
Header and shell (seller name and environment dot); Settings > Organization > Team and Offices (role gate)

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id, same as the X-Request-Id header.
seller_idstringAPI key only. Your seller id, sel_....
seller_namestringAPI key only. The seller's legal name.
seller_statusstringAPI key only. Seller status, for example ACTIVE.
principal*objectWho is calling.
kind*stringapi_key or clerk (a dashboard session).
one of: api_key · clerk
key_idstringAPI key only. The key_... id.
subjectstringSession only. A stable pseudonymous id clerk:<24 hex> derived from the signed-in user.
mode*stringtest or production. For a key, the key's mode. For a session, the gateway's configured session mode.
one of: test · production
scopeobjectAPI key only. The key's grants.
kind*stringScope kind.
one of: seller · group · facility
ids*arrayArray of strings. The raw grant ids. For a seller scope, [seller_id].
permissions*arrayArray of strings. Sorted key permissions.
one of: admin · read · submit · webhooks
reachable_facility_idsarrayAPI key only. Array of strings. Office ids the key reaches. For seller scope, every office of the seller (first 500).
userobjectSession only. The signed-in person.
display_name*stringName from the session token. Empty string when the token carries none.
email*stringEmail from the session token. Empty string when the token carries none.
organizationobjectSession only. The organization the session is signed into.
clerk_org_id*stringThe sign-in organization id.
seller_id*stringThe Claim House seller the organization is bound to.
name*stringSeller display name, or legal name when no display name is set.
onboarding_state*stringSeller onboarding status. Always READY for a session that can call this route.
one of: APPLICATION · PROVISIONING · READY · BLOCKED
rolestringSession only. The membership role label.
one of: owner · admin · submitter · viewer
permissionsarraySession only. Array of strings. Sorted member actions, for example claims.read.
one of: claims.read · claims.submit · attachments.read · attachments.manage · eligibility.read · eligibility.check · remittances.read · members.read · members.manage · offices.read · offices.manage · keys.read · keys.manage · webhooks.read · webhooks.manage · usage.read · billing.read
office_scopeobjectSession only. The member's office access.
kind*stringALL, SUBSET or NONE.
one of: ALL · SUBSET · NONE
facility_ids*arrayArray of strings. For ALL, every office of the seller. For SUBSET, the granted offices. For NONE, empty.
authorization_versionintegerSession only. The membership's authorization version.
capabilitiesobjectSession only. Reserved. Always an empty object today.

Responses

200OK (API key)
{
  "request_id": "evt_f8cwc9ew49wa6bjpqdyd",
  "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
  "seller_name": "Sample Dental Group LLC",
  "seller_status": "ACTIVE",
  "principal": {
    "kind": "api_key",
    "key_id": "key_26z6a4mswbz9c34jjd9n"
  },
  "mode": "test",
  "scope": {
    "kind": "seller",
    "ids": [
      "sel_s4zf3w33k6x8j1e1z6sh"
    ],
    "permissions": [
      "admin",
      "read",
      "submit",
      "webhooks"
    ]
  },
  "reachable_facility_ids": [
    "fac_q5s09nzww25ysd5a2f3g",
    "fac_w9qxypnbcw7b308jbbhh",
    "fac_tycrfy0cs0qc7sq95eme",
    "fac_qnwrs4rq9tjtqwv37phe",
    "fac_2b5g4kw42z7zqt0y8046",
    "fac_rk50recfer1p4h8rrb4w",
    "fac_2b3hhwz195xxnc9myfch",
    "fac_bkzth74mqx09wergap5c",
    "fac_j0h554r1qy2nzrkvspb4"
  ]
}
200OK (dashboard session)
{
  "request_id": "evt_qrspxxcadxgt581h8wkc",
  "principal": {
    "kind": "clerk",
    "subject": "clerk:0123456789abcdef01234567"
  },
  "user": {
    "display_name": "Sample Member",
    "email": "sample.member@example.com"
  },
  "organization": {
    "clerk_org_id": "org_EXAMPLE0000000000001",
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "name": "Sample Dental Group",
    "onboarding_state": "READY"
  },
  "mode": "production",
  "role": "submitter",
  "permissions": [
    "claims.read",
    "claims.submit",
    "offices.read"
  ],
  "office_scope": {
    "kind": "SUBSET",
    "facility_ids": [
      "fac_tycrfy0cs0qc7sq95eme"
    ]
  },
  "authorization_version": 3,
  "capabilities": {}
}
403Permission denied
{
  "error": "PERMISSION_DENIED",
  "message": "permission denied",
  "errors": [
    {
      "permission": "read"
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
401UNAUTHORIZEDSession with no active membership in a bound organization
403PERMISSION_DENIEDCredential lacks read, or the session's membership, seller or onboarding is not active
403FACILITY_NOT_GRANTEDAPI key whose group or office grants resolve to no office
404NOT_FOUNDAPI key whose seller record does not exist
429TOO_MANY_REQUESTSRate or concurrency limit reached
InfoFields marked API key only or Session only are omitted, not null, for the other principal.
InfoA dashboard session gets read when it holds any *.read action. A session whose membership is not active, or whose organization is not fully onboarded, holds no actions and is refused with 403.
ImportantThe account reference page describes an earlier session shape (with seller_name, scope.office_scope and principal.role). The shapes above follow the current gateway; API key responses no longer carry scope.office_scope.
API reference

Get onboarding state

GEThttps://api.claimhouse.ai/v1/onboarding/state

Return where the signed-in person stands: ready, pending, blocked or no organization yet.

The first call after sign-in. It works for a person who has no organization or membership yet, and lists their own applications, access requests and any pending invitation addressed to their email.

Use allowed_actions to decide which screen to show next.

Permission
Signed-in dashboard session (no key permission, no membership needed)
Idempotency
none
Side effects
None. Reads only.
In the dashboard
Onboarding (after sign-in)

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id, same as the X-Request-Id header.
state*stringREADY when the session's organization is onboarded; BLOCKED or SUSPENDED from the organization; PENDING when the person has an application, access request or invitation; else NO_ORGANIZATION.
one of: READY · BLOCKED · SUSPENDED · PENDING · NO_ORGANIZATION
applications*arrayThe person's own applications, newest first. Each item has the same fields as Get an onboarding application.
application_id*stringApplication id, app_....
status*stringApplication status.
one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED
version*integerCurrent version.
display_name*stringDisplay name.
review_reasonstringReview guidance, or null.
invitationobjectThe most recent unexpired invitation addressed to the session's email in state SENT, ACCEPTED or OUTCOME_UNCERTAIN. Null when there is none or the session token carries no email.
invitation_id*stringInvitation id, inv_....
organization_name*stringThe inviting organization's name.
intended_email*stringThe email the invitation was addressed to.
state*stringInvitation state.
one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN
version*integerInvitation version.
access_requests*arrayThe person's own access requests, newest first.
request_id*stringAccess request id, arq_.... Not the trace id.
organization_reference*stringThe organization reference the request named.
relationship_reason*stringWhy the person asked for access.
status*stringReview status.
one of: PENDING · APPROVED_FOR_INVITATION · DECLINED
version*integerCurrent version.
created_at*datetimeWhen it was created.
reviewed_atdatetimeWhen it was reviewed, or null.
allowed_actions*arrayArray of strings. ["enter_partner"] when state is READY, else ["create_application", "request_access"].

Responses

200OK
{
  "request_id": "evt_nj662q095m3fjmnsbj9r",
  "state": "READY",
  "applications": [],
  "invitation": null,
  "access_requests": [],
  "allowed_actions": [
    "enter_partner"
  ]
}
401Not a signed-in session
{
  "error": "UNAUTHORIZED",
  "message": "unauthorized",
  "errors": [],
  "request_id": "evt_devdp7p3r4z468arq3en"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDNo session token, an API key was presented, or session sign-in is not configured
InfoIdentity-only routes do not use the per-key rate limits and do not require an organization.
InfoSUSPENDED is declared but not produced by the current organization statuses. An organization still in APPLICATION or PROVISIONING reports PENDING or NO_ORGANIZATION.
ImportantBeta: needs dashboard sign-in configured on the gateway. Without it every call returns 401. When the onboarding registry is not wired the lists come back empty rather than erroring.
API reference

Create an onboarding application

POSThttps://api.claimhouse.ai/v1/onboarding/applications

Save a company application as a DRAFT owned by the signed-in person.

Registers a new organization for review. The application starts in DRAFT and is not reviewed until you submit it.

The contact email comes from the session token, not the body.

Permission
Signed-in dashboard session (no key permission, no membership needed)
Idempotency
required
Side effects
Stores one draft application and nothing else. Grants no access and creates no organization.
In the dashboard
Onboarding > Register a company

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Idempotency-Key*string1 to 200 characters. Scoped to the signed-in person. Replaying the same key returns the record the first call created.
e.g. 3f0c2a4e-EXAMPLE-idempotency-key
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Request body

NameTypeDescription
legal_name*stringThe company or practice legal name. 1 to 240 characters.
e.g. Sample Dental Group LLC
display_name*stringThe name shown in the dashboard. 1 to 240 characters.
e.g. Sample Dental Group
company_type*stringFree text describing the kind of company, for example a dental group or software vendor. 1 to 80 characters.
e.g. Dental group
applicant_name*stringThe person applying. 1 to 160 characters.
e.g. Sample Applicant
applicant_title*stringThe applicant's job title. 1 to 120 characters.
e.g. Operations Director
countrystringTwo-letter country code. Defaults to US.
e.g. US
expected_office_countintegerHow many offices you expect to connect. 1 to 10000. Defaults to 1.
e.g. 3
requested_capabilitiesarrayArray of strings. The rails you want enabled. Defaults to an empty array.
one of: claims · attachments · eligibility · remittances
e.g. ["claims","eligibility"]
existing_account_referencestringOptional reference to an existing Claim House relationship. Up to 120 characters, or null.
e.g. null

Request example

{
  "legal_name": "Sample Dental Group LLC",
  "display_name": "Sample Dental Group",
  "company_type": "Dental group",
  "applicant_name": "Sample Applicant",
  "applicant_title": "Operations Director",
  "country": "US",
  "expected_office_count": 3,
  "requested_capabilities": [
    "claims",
    "eligibility"
  ]
}

Response fields

NameTypeDescription
application_id*stringThe application id, app_....
applicant_issuer*stringThe sign-in issuer of the person who owns the application.
applicant_user_id*stringThe signed-in user id that owns the application. Only that person can read or change it.
contact_email_digest*stringSHA-256 hex digest of the lowercased contact email.
contact_email_display*stringThe contact email, taken from the session token, lowercased.
legal_name*stringLegal company or practice name.
display_name*stringDisplay name.
company_type*stringKind of company.
applicant_name*stringApplicant name.
applicant_title*stringApplicant title.
country*stringTwo-letter country code.
expected_office_count*integerExpected number of offices.
requested_capabilities*arrayArray of strings. Requested rails.
one of: claims · attachments · eligibility · remittances
existing_account_referencestringExisting relationship reference, or null.
idempotency_key*stringThe Idempotency-Key the application was created with.
status*stringWhere the application is in review.
one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED
version*integerIncrements on every change. Send it back as expected_version.
submitted_atdatetimeWhen it was submitted for review, or null.
reviewed_atdatetimeWhen Claim House reviewed it, or null.
reviewed_bystringReviewer reference, or null.
review_reasonstringReview guidance, for example what information is missing, or null.
created_at*datetimeWhen the application was created.
updated_at*datetimeWhen it last changed.

Responses

201Created
{
  "application_id": "app_pwefwt0r0bhaq97gsycf",
  "applicant_issuer": "https://issuer.EXAMPLE",
  "applicant_user_id": "user_EXAMPLE0000000000001",
  "contact_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
  "contact_email_display": "sample.applicant@example.com",
  "legal_name": "Sample Dental Group LLC",
  "display_name": "Sample Dental Group",
  "company_type": "Dental group",
  "applicant_name": "Sample Applicant",
  "applicant_title": "Operations Director",
  "country": "US",
  "expected_office_count": 3,
  "requested_capabilities": [
    "claims",
    "eligibility"
  ],
  "existing_account_reference": null,
  "idempotency_key": "3f0c2a4e-EXAMPLE-idempotency-key",
  "status": "DRAFT",
  "version": 1,
  "submitted_at": null,
  "reviewed_at": null,
  "reviewed_by": null,
  "review_reason": null,
  "created_at": "2026-09-14T15:04:05+00:00",
  "updated_at": "2026-09-14T15:04:05+00:00"
}
422Invalid request
{
  "error": "INVALID_REQUEST",
  "message": "the request body is not valid",
  "errors": [
    {
      "code": "missing",
      "location": "body.legal_name",
      "message": "Field required",
      "value_redacted": true
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
401Not a signed-in session
{
  "error": "UNAUTHORIZED",
  "message": "unauthorized",
  "errors": [],
  "request_id": "evt_y2hdpr67z1an7rymed9s"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDNo session token, an API key was presented, or session sign-in is not configured
422INVALID_REQUESTBody, path or header does not fit the schema
422INVALID_REQUESTIdempotency-Key header missing or longer than 200 characters
503IDENTITY_REGISTRY_UNAVAILABLEOnboarding registry is not wired on this gateway
InfoReplaying the same Idempotency-Key returns the original application with 201, even if the body differs. No 409 is raised.
InfoThe response is the application record itself and carries no request_id field; read X-Request-Id from the headers.
ImportantBeta: a known server fault currently makes this route return a plain-text 500 before anything is stored. It also returns 500 when the session token carries no email claim.
API reference

Get an onboarding application

GEThttps://api.claimhouse.ai/v1/onboarding/applications/{application_id}

Return one application owned by the signed-in person.

Use it to read the current status, version and any review_reason before updating or submitting.

Permission
Signed-in dashboard session (no key permission, no membership needed)
Idempotency
none
Side effects
None. Reads only.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature

Path parameters

NameTypeDescription
application_id*stringThe application id, app_....
e.g. app_pwefwt0r0bhaq97gsycf

Request example

null

Response fields

NameTypeDescription
application_id*stringThe application id, app_....
applicant_issuer*stringThe sign-in issuer of the person who owns the application.
applicant_user_id*stringThe signed-in user id that owns the application. Only that person can read or change it.
contact_email_digest*stringSHA-256 hex digest of the lowercased contact email.
contact_email_display*stringThe contact email, taken from the session token, lowercased.
legal_name*stringLegal company or practice name.
display_name*stringDisplay name.
company_type*stringKind of company.
applicant_name*stringApplicant name.
applicant_title*stringApplicant title.
country*stringTwo-letter country code.
expected_office_count*integerExpected number of offices.
requested_capabilities*arrayArray of strings. Requested rails.
one of: claims · attachments · eligibility · remittances
existing_account_referencestringExisting relationship reference, or null.
idempotency_key*stringThe Idempotency-Key the application was created with.
status*stringWhere the application is in review.
one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED
version*integerIncrements on every change. Send it back as expected_version.
submitted_atdatetimeWhen it was submitted for review, or null.
reviewed_atdatetimeWhen Claim House reviewed it, or null.
reviewed_bystringReviewer reference, or null.
review_reasonstringReview guidance, for example what information is missing, or null.
created_at*datetimeWhen the application was created.
updated_at*datetimeWhen it last changed.

Responses

200OK
{
  "application_id": "app_pwefwt0r0bhaq97gsycf",
  "applicant_issuer": "https://issuer.EXAMPLE",
  "applicant_user_id": "user_EXAMPLE0000000000001",
  "contact_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
  "contact_email_display": "sample.applicant@example.com",
  "legal_name": "Sample Dental Group LLC",
  "display_name": "Sample Dental Group",
  "company_type": "Dental group",
  "applicant_name": "Sample Applicant",
  "applicant_title": "Operations Director",
  "country": "US",
  "expected_office_count": 3,
  "requested_capabilities": [
    "claims",
    "eligibility"
  ],
  "existing_account_reference": null,
  "idempotency_key": "3f0c2a4e-EXAMPLE-idempotency-key",
  "status": "DRAFT",
  "version": 1,
  "submitted_at": null,
  "reviewed_at": null,
  "reviewed_by": null,
  "review_reason": null,
  "created_at": "2026-09-14T15:04:05+00:00",
  "updated_at": "2026-09-14T15:04:05+00:00"
}
404Not found
{
  "error": "NOT_FOUND",
  "message": "no such application",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDNo session token, an API key was presented, or session sign-in is not configured
404NOT_FOUNDUnknown id, or an application owned by someone else
503IDENTITY_REGISTRY_UNAVAILABLEOnboarding registry is not wired on this gateway
InfoNo request_id field in the body; read X-Request-Id from the headers.
ImportantBeta: needs dashboard sign-in configured on the gateway.
API reference

Update an onboarding application

PATCHhttps://api.claimhouse.ai/v1/onboarding/applications/{application_id}

Replace the details of a DRAFT or NEEDS_INFORMATION application.

Despite the method, the body is a full replacement: every required create field must be sent again, plus expected_version.

Allowed only while the application is DRAFT or NEEDS_INFORMATION. The version increments on success.

Permission
Signed-in dashboard session (no key permission, no membership needed)
Idempotency
none
Side effects
Updates the stored application and records a change event. Does not change its status.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
application_id*stringThe application id, app_....
e.g. app_pwefwt0r0bhaq97gsycf

Request body

NameTypeDescription
expected_version*integerThe application version you last read. The update is refused if the record has changed since.
e.g. 1
legal_name*stringThe company or practice legal name. 1 to 240 characters.
e.g. Sample Dental Group LLC
display_name*stringThe name shown in the dashboard. 1 to 240 characters.
e.g. Sample Dental Group
company_type*stringFree text describing the kind of company, for example a dental group or software vendor. 1 to 80 characters.
e.g. Dental group
applicant_name*stringThe person applying. 1 to 160 characters.
e.g. Sample Applicant
applicant_title*stringThe applicant's job title. 1 to 120 characters.
e.g. Operations Director
countrystringTwo-letter country code. Defaults to US. Omitting it resets it to the default.
e.g. US
expected_office_countintegerHow many offices you expect to connect. 1 to 10000. Defaults to 1. Omitting it resets it to the default.
e.g. 3
requested_capabilitiesarrayArray of strings. The rails you want enabled. Defaults to an empty array. Omitting it resets it to the default.
one of: claims · attachments · eligibility · remittances
e.g. ["claims","eligibility"]
existing_account_referencestringUp to 120 characters. Sending null leaves the stored value unchanged; it cannot be cleared.
e.g. null

Request example

{
  "expected_version": 1,
  "legal_name": "Sample Dental Group LLC",
  "display_name": "Sample Dental Group",
  "company_type": "Dental group",
  "applicant_name": "Sample Applicant",
  "applicant_title": "Operations Director",
  "country": "US",
  "expected_office_count": 4,
  "requested_capabilities": [
    "claims",
    "eligibility"
  ]
}

Response fields

NameTypeDescription
application_id*stringThe application id, app_....
applicant_issuer*stringThe sign-in issuer of the person who owns the application.
applicant_user_id*stringThe signed-in user id that owns the application. Only that person can read or change it.
contact_email_digest*stringSHA-256 hex digest of the lowercased contact email.
contact_email_display*stringThe contact email, taken from the session token, lowercased.
legal_name*stringLegal company or practice name.
display_name*stringDisplay name.
company_type*stringKind of company.
applicant_name*stringApplicant name.
applicant_title*stringApplicant title.
country*stringTwo-letter country code.
expected_office_count*integerExpected number of offices.
requested_capabilities*arrayArray of strings. Requested rails.
one of: claims · attachments · eligibility · remittances
existing_account_referencestringExisting relationship reference, or null.
idempotency_key*stringThe Idempotency-Key the application was created with.
status*stringWhere the application is in review.
one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED
version*integerIncrements on every change. Send it back as expected_version.
submitted_atdatetimeWhen it was submitted for review, or null.
reviewed_atdatetimeWhen Claim House reviewed it, or null.
reviewed_bystringReviewer reference, or null.
review_reasonstringReview guidance, for example what information is missing, or null.
created_at*datetimeWhen the application was created.
updated_at*datetimeWhen it last changed.

Responses

200OK
{
  "application_id": "app_pwefwt0r0bhaq97gsycf",
  "applicant_issuer": "https://issuer.EXAMPLE",
  "applicant_user_id": "user_EXAMPLE0000000000001",
  "contact_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
  "contact_email_display": "sample.applicant@example.com",
  "legal_name": "Sample Dental Group LLC",
  "display_name": "Sample Dental Group",
  "company_type": "Dental group",
  "applicant_name": "Sample Applicant",
  "applicant_title": "Operations Director",
  "country": "US",
  "expected_office_count": 4,
  "requested_capabilities": [
    "claims",
    "eligibility"
  ],
  "existing_account_reference": null,
  "idempotency_key": "3f0c2a4e-EXAMPLE-idempotency-key",
  "status": "DRAFT",
  "version": 2,
  "submitted_at": null,
  "reviewed_at": null,
  "reviewed_by": null,
  "review_reason": null,
  "created_at": "2026-09-14T15:04:05+00:00",
  "updated_at": "2026-09-14T15:10:00+00:00"
}
409Stale version or state
{
  "error": "STALE_VERSION_OR_STATE",
  "message": "application changed; reload before retrying",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDNo session token, an API key was presented, or session sign-in is not configured
422INVALID_REQUESTBody, path or header does not fit the schema
409STALE_VERSION_OR_STATEVersion mismatch, status not editable, or unknown or foreign id
503IDENTITY_REGISTRY_UNAVAILABLEOnboarding registry is not wired on this gateway
InfoAn unknown application id returns 409 STALE_VERSION_OR_STATE, not 404. Reload with Get an onboarding application.
ImportantBeta: needs dashboard sign-in configured on the gateway.
API reference

Submit an onboarding application

POSThttps://api.claimhouse.ai/v1/onboarding/applications/{application_id}/submit

Move a DRAFT or NEEDS_INFORMATION application to PENDING_REVIEW.

Hands the application to Claim House for review and stamps submitted_at. Review, approval and provisioning happen on the Claim House side; poll Get onboarding state for the outcome.

Permission
Signed-in dashboard session (no key permission, no membership needed)
Idempotency
none
Side effects
Changes the application status to PENDING_REVIEW and records a transition event. Grants no access.
In the dashboard
Onboarding > Submit application for review

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
application_id*stringThe application id, app_....
e.g. app_pwefwt0r0bhaq97gsycf

Request body

NameTypeDescription
expected_version*integerThe application version you last read. Minimum 1.
e.g. 1

Request example

{
  "expected_version": 1
}

Response fields

NameTypeDescription
application_id*stringThe application id, app_....
applicant_issuer*stringThe sign-in issuer of the person who owns the application.
applicant_user_id*stringThe signed-in user id that owns the application. Only that person can read or change it.
contact_email_digest*stringSHA-256 hex digest of the lowercased contact email.
contact_email_display*stringThe contact email, taken from the session token, lowercased.
legal_name*stringLegal company or practice name.
display_name*stringDisplay name.
company_type*stringKind of company.
applicant_name*stringApplicant name.
applicant_title*stringApplicant title.
country*stringTwo-letter country code.
expected_office_count*integerExpected number of offices.
requested_capabilities*arrayArray of strings. Requested rails.
one of: claims · attachments · eligibility · remittances
existing_account_referencestringExisting relationship reference, or null.
idempotency_key*stringThe Idempotency-Key the application was created with.
status*stringWhere the application is in review.
one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED
version*integerIncrements on every change. Send it back as expected_version.
submitted_atdatetimeWhen it was submitted for review, or null.
reviewed_atdatetimeWhen Claim House reviewed it, or null.
reviewed_bystringReviewer reference, or null.
review_reasonstringReview guidance, for example what information is missing, or null.
created_at*datetimeWhen the application was created.
updated_at*datetimeWhen it last changed.

Responses

200OK
{
  "application_id": "app_pwefwt0r0bhaq97gsycf",
  "applicant_issuer": "https://issuer.EXAMPLE",
  "applicant_user_id": "user_EXAMPLE0000000000001",
  "contact_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
  "contact_email_display": "sample.applicant@example.com",
  "legal_name": "Sample Dental Group LLC",
  "display_name": "Sample Dental Group",
  "company_type": "Dental group",
  "applicant_name": "Sample Applicant",
  "applicant_title": "Operations Director",
  "country": "US",
  "expected_office_count": 3,
  "requested_capabilities": [
    "claims",
    "eligibility"
  ],
  "existing_account_reference": null,
  "idempotency_key": "3f0c2a4e-EXAMPLE-idempotency-key",
  "status": "PENDING_REVIEW",
  "version": 2,
  "submitted_at": "2026-09-14T15:10:00+00:00",
  "reviewed_at": null,
  "reviewed_by": null,
  "review_reason": null,
  "created_at": "2026-09-14T15:04:05+00:00",
  "updated_at": "2026-09-14T15:10:00+00:00"
}
409Stale version or state
{
  "error": "STALE_VERSION_OR_STATE",
  "message": "application changed; reload before retrying",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDNo session token, an API key was presented, or session sign-in is not configured
422INVALID_REQUESTBody, path or header does not fit the schema
409STALE_VERSION_OR_STATEVersion mismatch, already submitted, or unknown or foreign id
503IDENTITY_REGISTRY_UNAVAILABLEOnboarding registry is not wired on this gateway
InfoSafe against double submission: a second call with the old version returns 409 because the version has moved.
ImportantBeta: needs dashboard sign-in configured on the gateway. There is no partner route to withdraw an application.
API reference

Accept an organization invitation

POSThttps://api.claimhouse.ai/v1/onboarding/invitations/{invitation_id}/accept

Create one local membership from a sent invitation after the signed-in Clerk issuer, organization and verified email all match exactly.

The request body contains only expected_version. Identity and email come from the verified Clerk session and cannot be supplied by the caller.

Actions and explicit office grants are copied from the invitation in the same transaction. A replay or stale version is refused and never creates a second membership.

Permission
Signed-in dashboard session for the invited Clerk organization and verified email
Idempotency
one SENT to RECONCILED transition guarded by expected_version
Side effects
Creates one local membership and grants, then reconciles the invitation. It sends no email and contacts no external service.
In the dashboard
Onboarding > Accept invitation
API reference

Request access to an organization

POSThttps://api.claimhouse.ai/v1/onboarding/access-requests

Ask an existing Claim House organization to let the signed-in person join.

Name the organization by its partner reference and say why you need access. The organization's administrators see the request in List access requests.

The response is the same whether or not the reference matches an organization, so the route cannot be used to discover organizations.

Permission
Signed-in dashboard session (no key permission, no membership needed)
Idempotency
required
Side effects
Stores a PENDING access request when the reference matches an organization. Grants no access and sends no notification.
In the dashboard
Onboarding > Request access

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Idempotency-Key*string1 to 200 characters. Scoped to the signed-in person. Replaying the same key returns the record the first call created.
e.g. 3f0c2a4e-EXAMPLE-idempotency-key
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Request body

NameTypeDescription
organization_reference*stringThe organization's partner reference, as the organization gave it to you. 1 to 160 characters.
e.g. SAMPLE-DENTAL-GROUP
relationship_reason*stringWhy you need access. 1 to 1000 characters.
e.g. I manage billing for two Sample Dental Group offices.

Request example

{
  "organization_reference": "SAMPLE-DENTAL-GROUP",
  "relationship_reason": "I manage billing for two Sample Dental Group offices."
}

Response fields

NameTypeDescription
request_id*stringThe access request id, arq_.... This is not the trace id; read X-Request-Id for that.
status*stringReview status. PENDING on creation.
one of: PENDING · APPROVED_FOR_INVITATION · DECLINED
version*integerCurrent version.

Responses

201Created
{
  "request_id": "arq_EXAMPLE0000000000001",
  "status": "PENDING",
  "version": 1
}
422Invalid request
{
  "error": "INVALID_REQUEST",
  "message": "the request body is not valid",
  "errors": [
    {
      "code": "missing",
      "location": "header.Idempotency-Key",
      "message": "Field required",
      "value_redacted": true
    }
  ],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
401Not a signed-in session
{
  "error": "UNAUTHORIZED",
  "message": "unauthorized",
  "errors": [],
  "request_id": "evt_ah241wdyh220530kxd2z"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDNo session token, an API key was presented, or session sign-in is not configured
422INVALID_REQUESTBody, path or header does not fit the schema
503IDENTITY_REGISTRY_UNAVAILABLEOnboarding registry is not wired on this gateway
InfoReplaying the same Idempotency-Key returns the stored request. When the reference matches no organization nothing is stored and each call returns a fresh id.
ImportantBeta: a known server fault currently makes this route return a plain-text 500 before anything is stored. There is also no route yet for an administrator to approve or decline a request.
API reference

List access requests

GEThttps://api.claimhouse.ai/v1/access-requests

List every access request made to your organization, newest first.

For organization administrators reviewing who asked to join. Seller wide, not narrowed by office scope. Not paginated.

Permission
admin, from a dashboard session holding the members.manage action (API keys are refused)
Idempotency
none
Side effects
None. Reads only.
In the dashboard
Settings > Organization > Team > Invite and review

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id, same as the X-Request-Id header.
access_requests*arrayAccess requests, newest first.
seller_id*stringYour seller id.
request_id*stringThe access request id, arq_....
requester_issuer*stringSign-in issuer of the requester.
requester_user_id*stringSign-in user id of the requester.
organization_reference*stringThe reference the requester typed.
relationship_reason*stringThe requester's stated reason.
status*stringReview status.
one of: PENDING · APPROVED_FOR_INVITATION · DECLINED
reviewer_idstringReviewer, or null.
review_reasonstringReview reason, or null.
version*integerCurrent version.
idempotency_key*stringThe key the requester sent.
created_at*datetimeWhen it was created.
reviewed_atdatetimeWhen it was reviewed, or null.

Responses

200OK
{
  "request_id": "evt_smqb8kmqf7gk589mq1ys",
  "access_requests": [
    {
      "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
      "request_id": "arq_EXAMPLE0000000000001",
      "requester_issuer": "https://issuer.EXAMPLE",
      "requester_user_id": "user_EXAMPLE0000000000001",
      "organization_reference": "SAMPLE-DENTAL-GROUP",
      "relationship_reason": "I manage billing for two Sample Dental Group offices.",
      "status": "PENDING",
      "reviewer_id": null,
      "review_reason": null,
      "version": 1,
      "idempotency_key": "3f0c2a4e-EXAMPLE-idempotency-key",
      "created_at": "2026-09-14T15:04:05+00:00",
      "reviewed_at": null
    }
  ]
}
403API key or missing member action
{
  "error": "PERMISSION_DENIED",
  "message": "members.manage is required",
  "errors": [],
  "request_id": "evt_vaqnezmamm7k8ty2e30h"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
403PERMISSION_DENIEDLacks admin, is an API key, or the session lacks members.manage
429TOO_MANY_REQUESTSRate or concurrency limit reached
InfoEach item's request_id is the access request id, while the top-level request_id is the trace id.
ImportantBeta: needs a dashboard session with an active membership. No route exists yet to approve or decline a request.
API reference

List members

GEThttps://api.claimhouse.ai/v1/members

List every person with a membership in your organization, with role, actions and office access.

Seller wide and not paginated. Read authorization_version from here before calling Update a member.

Permission
read, from a dashboard session holding the members.read action (API keys are refused)
Idempotency
none
Side effects
None. Reads only.
In the dashboard
Settings > Organization > Team

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id, same as the X-Request-Id header.
members*arrayMembers ordered by membership id.
membership_id*stringThe membership id, mem_....
name*stringCurrently the member's sign-in user id, not a person name.
email*stringAlways an empty string today.
role*stringRole label.
one of: owner · admin · submitter · viewer
status*stringMembership status.
one of: PENDING · ACTIVE · SUSPENDED · REVOKED
actions*arrayArray of strings. Active member actions, sorted.
one of: claims.read · claims.submit · attachments.read · attachments.manage · eligibility.read · eligibility.check · remittances.read · members.read · members.manage · offices.read · offices.manage · keys.read · keys.manage · webhooks.read · webhooks.manage · usage.read · billing.read
scope_kind*stringOffice access kind.
one of: ALL · SUBSET · NONE
scope_ids*arrayArray of strings. Granted office ids. Empty for ALL and NONE.
scope_label*stringHuman label: All offices, <n> offices or No office access.
authorization_version*integerSend it back as expected_version when updating the member.
last_sign_in_atdatetimeLast time the membership was reconciled with sign-in, or null. Not a true last sign-in time.

Responses

200OK
{
  "request_id": "evt_smqb8kmqf7gk589mq1ys",
  "members": [
    {
      "membership_id": "mem_1w2pqxr89mss07rbry6s",
      "name": "user_EXAMPLE0000000000001",
      "email": "",
      "role": "submitter",
      "status": "ACTIVE",
      "actions": [
        "claims.read",
        "claims.submit",
        "offices.read"
      ],
      "scope_kind": "SUBSET",
      "scope_ids": [
        "fac_tycrfy0cs0qc7sq95eme"
      ],
      "scope_label": "1 offices",
      "authorization_version": 3,
      "last_sign_in_at": null
    }
  ]
}
403Missing member action
{
  "error": "PERMISSION_DENIED",
  "message": "members.read is required",
  "errors": [],
  "request_id": "evt_c0g0xxwhg11bv0p0yc26"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
403PERMISSION_DENIEDLacks read, is an API key, or the session lacks members.read
429TOO_MANY_REQUESTSRate or concurrency limit reached
ImportantBeta: needs a dashboard session with an active membership. name and email are placeholders until profile data is joined.
API reference

Update a member

PATCHhttps://api.claimhouse.ai/v1/members/{membership_id}

Replace a member's role, actions and office access.

A full replacement: the actions and office grants you send become the member's complete set, and any others are revoked.

You can only hand out what you hold. Every action must be one you have, ALL needs your own scope to be ALL, and SUBSET offices must be inside your own offices.

Permission
admin, from a dashboard session holding the members.manage action (API keys are refused)
Idempotency
none
Side effects
Rewrites the member's actions and office grants, increments authorization_version, and records an audit event with your reason. Takes effect on the member's next request.
In the dashboard
Settings > Organization > Team > Manage access

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
membership_id*stringThe membership id, mem_....
e.g. mem_1w2pqxr89mss07rbry6s

Request body

NameTypeDescription
expected_version*integerThe member's authorization_version from List members. Minimum 1.
e.g. 3
role*stringThe role label to record.
one of: owner · admin · submitter · viewer
e.g. viewer
actionsarrayArray of strings. The member's complete action set. Up to 100. Each must be one you hold. Defaults to empty, which removes every action.
one of: claims.read · claims.submit · attachments.read · attachments.manage · eligibility.read · eligibility.check · remittances.read · members.read · members.manage · offices.read · offices.manage · keys.read · keys.manage · webhooks.read · webhooks.manage · usage.read · billing.read
e.g. ["claims.read","offices.read"]
scope_kind*stringALL for every current and future office, SUBSET for the listed offices, NONE for no office data.
one of: ALL · SUBSET · NONE
e.g. SUBSET
office_idsarrayArray of strings. Required and non-empty for SUBSET; must be empty otherwise. Up to 500 office ids of your seller.
e.g. ["fac_EXAMPLE0000000000001"]
reason*stringWhy the access changed. 3 to 1000 characters. Stored with the audit event.
e.g. Moved to front desk role.

Request example

{
  "expected_version": 3,
  "role": "viewer",
  "actions": [
    "claims.read",
    "offices.read"
  ],
  "scope_kind": "SUBSET",
  "office_ids": [
    "fac_tycrfy0cs0qc7sq95eme"
  ],
  "reason": "Moved to front desk role."
}

Response fields

NameTypeDescription
seller_id*stringYour seller id.
membership_id*stringThe membership id, mem_....
clerk_issuer*stringThe sign-in issuer of the member.
clerk_user_id*stringThe member's sign-in user id.
clerk_org_id*stringThe sign-in organization the membership belongs to.
role_template*stringThe member's role label after the change.
one of: owner · admin · submitter · viewer
status*stringMembership status.
one of: PENDING · ACTIVE · SUSPENDED · REVOKED
authorization_version*integerIncrements on every access change. Send it back as expected_version.
all_current_and_future_offices*booleanTrue when the new scope is ALL.
invitation_idstringThe invitation the membership came from, or null.
source*stringHow the membership was created, for example invitation.
activated_atdatetimeWhen it became active, or null.
suspended_atdatetimeWhen it was suspended, or null.
revoked_atdatetimeWhen it was revoked, or null.
last_reconciled_clerk_eventstringLast sign-in provider event applied, or null.
last_reconciled_atdatetimeWhen it was last reconciled, or null.

Responses

200OK
{
  "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
  "membership_id": "mem_1w2pqxr89mss07rbry6s",
  "clerk_issuer": "https://issuer.EXAMPLE",
  "clerk_user_id": "user_EXAMPLE0000000000001",
  "clerk_org_id": "org_EXAMPLE0000000000001",
  "role_template": "viewer",
  "status": "ACTIVE",
  "authorization_version": 4,
  "all_current_and_future_offices": false,
  "invitation_id": "inv_c78sg2v0veqke88d7h7s",
  "source": "invitation",
  "activated_at": "2026-09-14T15:04:05+00:00",
  "suspended_at": null,
  "revoked_at": null,
  "last_reconciled_clerk_event": null,
  "last_reconciled_at": null
}
403Delegation exceeded
{
  "error": "DELEGATION_EXCEEDED",
  "message": "membership proposal exceeds caller offices",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
409Change refused
{
  "error": "MEMBERSHIP_CHANGE_REFUSED",
  "message": "the final active owner cannot be demoted",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
403PERMISSION_DENIEDLacks admin, is an API key, or the session lacks members.manage
403DELEGATION_EXCEEDEDActions or offices exceed yours, or office_ids do not fit scope_kind
409STALE_VERSION_OR_STATEexpected_version is stale or the membership id is unknown
409MEMBERSHIP_CHANGE_REFUSEDDemoting the last active owner, or an office id that is not yours
422INVALID_REQUESTBody, path or header does not fit the schema
429TOO_MANY_REQUESTSRate or concurrency limit reached
InfoThe response is the membership record and carries no request_id, actions or office list. Call List members to see the new access.
Inforole is a label. What the member can do comes from actions and office scope.
ImportantBeta: needs a dashboard session with an active membership. An unknown membership id returns 409, not 404.
API reference

List invitations

GEThttps://api.claimhouse.ai/v1/invitations

List every invitation your organization has prepared, newest first.

Seller wide and not paginated. Read version from here before sending or revoking.

Permission
admin, from a dashboard session holding the members.manage action (API keys are refused)
Idempotency
none
Side effects
None. Reads only.
In the dashboard
Settings > Organization > Team > Invite and review

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature

Request example

null

Response fields

NameTypeDescription
request_id*stringRequest id, same as the X-Request-Id header.
invitations*arrayInvitations, newest first.
invitation_id*stringThe invitation id, inv_....
organization_name*stringYour organization's display name, or legal name.
intended_email*stringThe invitee email.
email*stringSame value as intended_email.
role*stringProposed role label.
one of: owner · admin · submitter · viewer
office_scope*objectProposed office access.
kind*stringScope kind.
one of: ALL · SUBSET · NONE
facility_idsarrayArray of strings. Present only when kind is SUBSET.
state*stringInvitation state.
one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN
version*integerCurrent version.
created_at*datetimeWhen it was prepared.

Responses

200OK
{
  "request_id": "evt_smqb8kmqf7gk589mq1ys",
  "invitations": [
    {
      "invitation_id": "inv_c78sg2v0veqke88d7h7s",
      "organization_name": "Sample Dental Group",
      "intended_email": "sample.teammate@example.com",
      "email": "sample.teammate@example.com",
      "role": "submitter",
      "office_scope": {
        "kind": "SUBSET",
        "facility_ids": [
          "fac_tycrfy0cs0qc7sq95eme"
        ]
      },
      "state": "PREPARED",
      "version": 1,
      "created_at": "2026-09-14T15:04:05+00:00"
    }
  ]
}
403API key or missing member action
{
  "error": "PERMISSION_DENIED",
  "message": "members.manage is required",
  "errors": [],
  "request_id": "evt_52t06r6aw1f55ava9sp3"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
403PERMISSION_DENIEDLacks admin, is an API key, or the session lacks members.manage
429TOO_MANY_REQUESTSRate or concurrency limit reached
InfoThis list uses a shaped view. Create, send and revoke return the raw invitation record instead (intended_email_display, role_template, proposed_scope_kind).
ImportantBeta: needs a dashboard session with an active membership.
API reference

Prepare an invitation

POSThttps://api.claimhouse.ai/v1/invitations

Record a PREPARED invitation for a teammate without sending anything.

Stores who to invite, with the role, actions and office access they will get. Nothing is emailed; delivery is a separate, confirmed step (Send an invitation).

You can only offer what you hold: every action must be yours, ALL needs your own scope to be ALL, and offices must be inside your own.

Permission
admin, from a dashboard session holding the members.manage action (API keys are refused)
Idempotency
none
Side effects
Stores one PREPARED invitation with its actions and office grants. Sends no email and contacts no sign-in provider.
In the dashboard
Settings > Organization > Team > Invite and review (Prepare invitation)

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Request body

NameTypeDescription
email*stringThe invitee email. 3 to 254 characters, must contain @. Stored lowercased.
e.g. sample.teammate@example.com
role*stringThe role label to propose.
one of: owner · admin · submitter · viewer
e.g. submitter
scope_kind*stringProposed office access.
one of: ALL · SUBSET · NONE
e.g. SUBSET
office_idsarrayArray of strings. Required and non-empty for SUBSET; must be empty otherwise. Up to 500, each inside your own office access.
e.g. ["fac_EXAMPLE0000000000001"]
actionsarrayArray of strings. The actions the invitee will hold. Up to 100, each one you hold.
one of: claims.read · claims.submit · attachments.read · attachments.manage · eligibility.read · eligibility.check · remittances.read · members.read · members.manage · offices.read · offices.manage · keys.read · keys.manage · webhooks.read · webhooks.manage · usage.read · billing.read
e.g. ["claims.read","claims.submit","offices.read"]
expires_at*datetimeWhen the invitation expires. The dashboard uses seven days from now.
e.g. 2026-09-21T15:04:05+00:00

Request example

{
  "email": "sample.teammate@example.com",
  "role": "submitter",
  "scope_kind": "SUBSET",
  "office_ids": [
    "fac_tycrfy0cs0qc7sq95eme"
  ],
  "actions": [
    "claims.read",
    "claims.submit",
    "offices.read"
  ],
  "expires_at": "2026-09-21T15:04:05+00:00"
}

Response fields

NameTypeDescription
seller_id*stringYour seller id, sel_....
invitation_id*stringThe invitation id, inv_....
intended_email_digest*stringSHA-256 hex digest of the lowercased invitee email.
intended_email_display*stringThe invitee email, lowercased.
role_template*stringThe role label proposed for the invitee.
one of: owner · admin · submitter · viewer
proposed_scope_kind*stringThe office scope proposed for the invitee.
one of: ALL · SUBSET · NONE
expires_at*datetimeWhen the invitation stops being valid.
clerk_invitation_idstringThe sign-in provider's invitation id once delivered, else null.
delivery_operation_idstringId of the single delivery attempt once one starts, else null.
state*stringInvitation state.
one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN
version*integerIncrements on every change. Send it back as expected_version.
created_by*stringThe user id of the administrator who prepared it.
created_at*datetimeWhen it was prepared.
revoked_atdatetimeWhen it was revoked, else null.
accepted_atdatetimeWhen acceptance was observed, else null.
reconciled_atdatetimeWhen membership was reconciled, else null.

Responses

201Created
{
  "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
  "invitation_id": "inv_c78sg2v0veqke88d7h7s",
  "intended_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
  "intended_email_display": "sample.teammate@example.com",
  "role_template": "submitter",
  "proposed_scope_kind": "SUBSET",
  "expires_at": "2026-09-21T15:04:05+00:00",
  "clerk_invitation_id": null,
  "delivery_operation_id": null,
  "state": "PREPARED",
  "version": 1,
  "created_by": "user_EXAMPLE0000000000001",
  "created_at": "2026-09-14T15:04:05+00:00",
  "revoked_at": null,
  "accepted_at": null,
  "reconciled_at": null
}
403Delegation exceeded
{
  "error": "DELEGATION_EXCEEDED",
  "message": "membership proposal exceeds caller actions",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}
403API key
{
  "error": "PERMISSION_DENIED",
  "message": "human member administration is required",
  "errors": [],
  "request_id": "evt_a83nh7hhkqk6kgqkghrh"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
403PERMISSION_DENIEDLacks admin, or is an API key
403DELEGATION_EXCEEDEDSession lacks members.manage, actions or offices exceed yours, or office_ids do not fit scope_kind
404NOT_FOUNDAn office id outside your own office access
422INVALID_REQUESTBody, path or header does not fit the schema
429TOO_MANY_REQUESTSRate or concurrency limit reached
503REGISTRY_WRITE_FAILEDThe invitation was not stored
InfoNot idempotent: each call prepares a new invitation, even for the same email.
ImportantBeta: a known server fault currently makes this route return a plain-text 500 after the permission checks pass, before anything is stored.
API reference

Send an invitation

POSThttps://api.claimhouse.ai/v1/invitations/{invitation_id}/send

Deliver a PREPARED invitation through the sign-in provider, once.

Requires explicit confirmation. Moves the invitation to DELIVERY_PENDING, makes exactly one delivery attempt, then records SENT.

If the attempt fails or its outcome is unclear, the invitation becomes OUTCOME_UNCERTAIN and the route answers 502. Do not resend.

Permission
admin, from a dashboard session holding the members.manage action (API keys are refused)
Idempotency
none
Side effects
When delivery is configured, makes one invitation delivery attempt and records the outcome. On this gateway delivery is not configured, so nothing is sent and the state does not change.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
invitation_id*stringThe invitation id, inv_....
e.g. inv_c78sg2v0veqke88d7h7s

Request body

NameTypeDescription
expected_version*integerThe invitation version you last read. Minimum 1.
e.g. 1
confirmed*booleanMust be true. Confirms you intend a real delivery.
e.g. true

Request example

{
  "expected_version": 1,
  "confirmed": true
}

Response fields

NameTypeDescription
seller_id*stringYour seller id, sel_....
invitation_id*stringThe invitation id, inv_....
intended_email_digest*stringSHA-256 hex digest of the lowercased invitee email.
intended_email_display*stringThe invitee email, lowercased.
role_template*stringThe role label proposed for the invitee.
one of: owner · admin · submitter · viewer
proposed_scope_kind*stringThe office scope proposed for the invitee.
one of: ALL · SUBSET · NONE
expires_at*datetimeWhen the invitation stops being valid.
clerk_invitation_idstringThe sign-in provider's invitation id once delivered, else null.
delivery_operation_idstringId of the single delivery attempt once one starts, else null.
state*stringInvitation state.
one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN
version*integerIncrements on every change. Send it back as expected_version.
created_by*stringThe user id of the administrator who prepared it.
created_at*datetimeWhen it was prepared.
revoked_atdatetimeWhen it was revoked, else null.
accepted_atdatetimeWhen acceptance was observed, else null.
reconciled_atdatetimeWhen membership was reconciled, else null.

Responses

503Delivery not configured
{
  "error": "INVITATION_DELIVERY_UNCONFIGURED",
  "message": "invitation delivery is not configured",
  "errors": [],
  "request_id": "evt_3ss6bwba7cb1y4etkcwf"
}
200Sent (when delivery is configured)
{
  "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
  "invitation_id": "inv_c78sg2v0veqke88d7h7s",
  "intended_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
  "intended_email_display": "sample.teammate@example.com",
  "role_template": "submitter",
  "proposed_scope_kind": "SUBSET",
  "expires_at": "2026-09-21T15:04:05+00:00",
  "clerk_invitation_id": "inv_s6kv7qsg8edf9b9me10y_EXAMPLE",
  "delivery_operation_id": "op_fh92fzqy215mtxw02a6d",
  "state": "SENT",
  "version": 3,
  "created_by": "user_EXAMPLE0000000000001",
  "created_at": "2026-09-14T15:04:05+00:00",
  "revoked_at": null,
  "accepted_at": null,
  "reconciled_at": null
}
502Outcome uncertain
{
  "code": "DELIVERY_OUTCOME_UNCERTAIN",
  "message": "delivery may have succeeded; reconcile instead of resending",
  "invitation": {
    "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
    "invitation_id": "inv_c78sg2v0veqke88d7h7s",
    "intended_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
    "intended_email_display": "sample.teammate@example.com",
    "role_template": "submitter",
    "proposed_scope_kind": "SUBSET",
    "expires_at": "2026-09-21T15:04:05+00:00",
    "clerk_invitation_id": null,
    "delivery_operation_id": null,
    "state": "OUTCOME_UNCERTAIN",
    "version": 3,
    "created_by": "user_EXAMPLE0000000000001",
    "created_at": "2026-09-14T15:04:05+00:00",
    "revoked_at": null,
    "accepted_at": null,
    "reconciled_at": null
  }
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
403PERMISSION_DENIEDLacks admin, is an API key, or the session lacks members.manage
409CONFIRMATION_REQUIREDconfirmed is not true
409STALE_VERSION_OR_STATEVersion mismatch, not PREPARED, or unknown id
502DELIVERY_OUTCOME_UNCERTAINDelivery failed or could not be recorded; do not resend
503INVITATION_DELIVERY_UNCONFIGUREDNo delivery provider is configured (the current default)
422INVALID_REQUESTBody, path or header does not fit the schema
429TOO_MANY_REQUESTSRate or concurrency limit reached
ImportantBeta: invitation delivery is not configured on the gateway today, so every confirmed call returns 503 INVITATION_DELIVERY_UNCONFIGURED and no email is sent.
ImportantThe 502 body is not the standard envelope: it has code, message and invitation, with no error or request_id. No route can clear OUTCOME_UNCERTAIN yet; contact Claim House support.
API reference

Revoke an invitation

POSThttps://api.claimhouse.ai/v1/invitations/{invitation_id}/revoke

Mark a PREPARED or SENT invitation as REVOKED so it cannot be used.

Records the revocation in Claim House. A revoked invitation cannot be sent or reused; prepare a new one instead.

Permission
admin, from a dashboard session holding the members.manage action (API keys are refused)
Idempotency
none
Side effects
Sets the invitation state to REVOKED and stamps revoked_at. Does not contact the sign-in provider.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token (a Clerk session JWT) as Bearer <token>. API keys are refused on this route.
e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature
Content-Type*stringMust be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.
e.g. application/json

Path parameters

NameTypeDescription
invitation_id*stringThe invitation id, inv_....
e.g. inv_c78sg2v0veqke88d7h7s

Request body

NameTypeDescription
expected_version*integerThe invitation version you last read. Minimum 1.
e.g. 1
reason*stringWhy it is revoked. 1 to 1000 characters.
e.g. Invited the wrong address.

Request example

{
  "expected_version": 1,
  "reason": "Invited the wrong address."
}

Response fields

NameTypeDescription
seller_id*stringYour seller id, sel_....
invitation_id*stringThe invitation id, inv_....
intended_email_digest*stringSHA-256 hex digest of the lowercased invitee email.
intended_email_display*stringThe invitee email, lowercased.
role_template*stringThe role label proposed for the invitee.
one of: owner · admin · submitter · viewer
proposed_scope_kind*stringThe office scope proposed for the invitee.
one of: ALL · SUBSET · NONE
expires_at*datetimeWhen the invitation stops being valid.
clerk_invitation_idstringThe sign-in provider's invitation id once delivered, else null.
delivery_operation_idstringId of the single delivery attempt once one starts, else null.
state*stringInvitation state.
one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN
version*integerIncrements on every change. Send it back as expected_version.
created_by*stringThe user id of the administrator who prepared it.
created_at*datetimeWhen it was prepared.
revoked_atdatetimeWhen it was revoked, else null.
accepted_atdatetimeWhen acceptance was observed, else null.
reconciled_atdatetimeWhen membership was reconciled, else null.

Responses

200OK
{
  "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
  "invitation_id": "inv_c78sg2v0veqke88d7h7s",
  "intended_email_digest": "0000000000000000000000000000000000000000000000000000000000000000",
  "intended_email_display": "sample.teammate@example.com",
  "role_template": "submitter",
  "proposed_scope_kind": "SUBSET",
  "expires_at": "2026-09-21T15:04:05+00:00",
  "clerk_invitation_id": null,
  "delivery_operation_id": null,
  "state": "REVOKED",
  "version": 2,
  "created_by": "user_EXAMPLE0000000000001",
  "created_at": "2026-09-14T15:04:05+00:00",
  "revoked_at": "2026-09-14T15:10:00+00:00",
  "accepted_at": null,
  "reconciled_at": null
}
409Stale version or state
{
  "error": "STALE_VERSION_OR_STATE",
  "message": "invitation changed; reload before retrying",
  "errors": [],
  "request_id": "evt_bkfrar1bddj3efgmw6s0"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, revoked or unverifiable credential
403PERMISSION_DENIEDLacks admin, is an API key, or the session lacks members.manage
409STALE_VERSION_OR_STATEVersion mismatch, not PREPARED or SENT, or unknown id
422INVALID_REQUESTBody, path or header does not fit the schema
429TOO_MANY_REQUESTSRate or concurrency limit reached
Inforeason is required but is not stored today.
ImportantBeta: needs a dashboard session with an active membership. Revoking a SENT invitation does not withdraw the email already delivered by the sign-in provider.
API reference

Read notification preferences

GEThttps://api.claimhouse.ai/v1/notification-preferences

Read the signed-in member's email notification preferences.

Returns the member's current preference document with its optimistic-concurrency version, or defaults with persisted: false when nothing is saved yet.

Permission
session
Idempotency
none
Side effects
None. Read only.
In the dashboard
Settings > Notifications

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token as Bearer <token>. API keys are not accepted on this route.
e.g. Bearer <session token>

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
version*integerCurrent preference version; pass back as expected_version on save.
e.g. 3
persisted*booleanFalse while the member still sees defaults.
e.g. true
delivery_enabled*booleanWhether email delivery is active on this deployment.
e.g. false
organization_name*stringThe member's organization name.
e.g. Blue Line Dental
mode*stringThe member's organization mode.
one of: test · live
e.g. live
settings*objectThe preference document.
enabled*booleanMaster switch.
e.g. true
timezone*stringIANA timezone used for daily and weekly digests.
e.g. America/New_York
max_emails_per_day*integer0–50.
e.g. 5
office_selection*stringall_authorized or selected.
one of: all_authorized · selected
e.g. all_authorized
office_ids*arrayPractice ids when office_selection is selected. Empty otherwise.
e.g. []
categories*objectPer-category frequency, off | immediate | daily | weekly.
e.g. {"claim_status":"immediate","remittances":"daily"}

Responses

200OK
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "version": 3,
  "persisted": true,
  "delivery_enabled": false,
  "organization_name": "Blue Line Dental",
  "mode": "live",
  "settings": {
    "enabled": true,
    "timezone": "America/New_York",
    "max_emails_per_day": 5,
    "office_selection": "all_authorized",
    "office_ids": [],
    "categories": {
      "claim_status": "immediate",
      "remittances": "daily",
      "onboarding": "off",
      "submission_receipts": "immediate",
      "claim_attention": "immediate",
      "eligibility_results": "immediate",
      "eligibility_attention": "immediate",
      "attachment_updates": "immediate",
      "team_updates": "weekly"
    }
  }
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403MEMBERSHIP_REQUIREDthe session has no active organization membership
503NOTIFICATION_SETTINGS_UNAVAILABLEnotification settings are not connected
429TOO_MANY_REQUESTSrate or concurrency limit reached
API reference

Save notification preferences

PUThttps://api.claimhouse.ai/v1/notification-preferences

Replace the signed-in member's notification preferences, guarded by a version check.

Sends the whole settings document plus the version last read. If the document changed since, the save is refused with STALE_VERSION_OR_STATE — reload, then retry.

All categories keys must be present: onboarding, submission_receipts, claim_status, claim_attention, eligibility_results, eligibility_attention, attachment_updates, remittances, team_updates.

Permission
session
Idempotency
version-guarded
Side effects
Updates the member's notification settings; may change future email delivery.
In the dashboard
Settings > Notifications

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token as Bearer <token>. API keys are not accepted on this route.
e.g. Bearer <session token>

Request body

NameTypeDescription
expected_version*integerThe version from the last read.
e.g. 3
settings*objectThe full preference document — every field is required.
enabled*booleanMaster switch.
e.g. true
timezone*stringIANA timezone.
e.g. America/New_York
max_emails_per_day*integer0–50.
e.g. 5
office_selection*stringall_authorized or selected.
one of: all_authorized · selected
e.g. all_authorized
office_ids*arrayPractice ids when office_selection is selected; must be offices in your current access.
e.g. []
categories*objectAll nine categories, each off | immediate | daily | weekly.
e.g. {"claim_status":"immediate"}

Request example

{
  "expected_version": 3,
  "settings": {
    "enabled": true,
    "timezone": "America/New_York",
    "max_emails_per_day": 5,
    "office_selection": "all_authorized",
    "office_ids": [],
    "categories": {
      "onboarding": "off",
      "submission_receipts": "immediate",
      "claim_status": "immediate",
      "claim_attention": "immediate",
      "eligibility_results": "immediate",
      "eligibility_attention": "immediate",
      "attachment_updates": "immediate",
      "remittances": "daily",
      "team_updates": "weekly"
    }
  }
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
version*integerThe new version after this save.
e.g. 4
persisted*booleanAlways true after a save.
e.g. true
delivery_enabled*booleanWhether email delivery is active on this deployment.
e.g. false
organization_name*stringThe member's organization name.
e.g. Blue Line Dental
mode*stringThe member's organization mode.
one of: test · live
e.g. live
settings*objectThe stored preference document, echoed back.
e.g. {"enabled":true}

Responses

200Saved
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "version": 4,
  "persisted": true,
  "delivery_enabled": false,
  "organization_name": "Blue Line Dental",
  "mode": "live",
  "settings": {
    "enabled": true,
    "timezone": "America/New_York",
    "max_emails_per_day": 5,
    "office_selection": "all_authorized",
    "office_ids": [],
    "categories": {
      "onboarding": "off",
      "submission_receipts": "immediate",
      "claim_status": "immediate",
      "claim_attention": "immediate",
      "eligibility_results": "immediate",
      "eligibility_attention": "immediate",
      "attachment_updates": "immediate",
      "remittances": "daily",
      "team_updates": "weekly"
    }
  }
}
409Stale version
{
  "error": "STALE_VERSION_OR_STATE",
  "message": "Notification settings changed; reload before saving",
  "errors": [],
  "request_id": "evt_ang0n1gexynjw0dmfh8v"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403MEMBERSHIP_REQUIREDthe session has no active organization membership
403FACILITY_NOT_GRANTEDoffice_ids names a practice outside your current access
409STALE_VERSION_OR_STATEsettings changed since your read — reload and retry
422VALIDATIONa field, timezone, practice id or category frequency is invalid
503NOTIFICATION_SETTINGS_UNAVAILABLEnotification settings are not connected
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoDisabling everything while adding offices to office_ids is refused for offices you could not already see — the check compares against previously retained ids.
API reference

Request a product entitlement

POSThttps://api.claimhouse.ai/v1/entitlement-requests

Ask Claim House to enable a product for your organization.

Records the request and returns 201 the first time, 200 on repeats. Requesting does not grant the product — a Claim House operator reviews and entitles it.

Permission
session
Idempotency
repeat-safe
Side effects
Records the request for operator review.
In the dashboard
Settings > products

Headers

NameTypeDescription
Authorization*stringA signed-in dashboard session token as Bearer <token>. API keys are not accepted on this route.
e.g. Bearer <session token>

Request body

NameTypeDescription
product*stringThe product to enable.
one of: dental_claim · eligibility · attachment_packet · remittance
e.g. eligibility

Request example

{
  "product": "eligibility"
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
event_id*stringThe recorded request event.
e.g. evt_EXAMPLE0000000000000040
product*stringThe product requested.
e.g. eligibility
kind*stringAlways REQUEST.
e.g. REQUEST

Responses

201Recorded
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "event_id": "evt_fhnrqxwezs5rkevhmkc3",
  "product": "eligibility",
  "kind": "REQUEST"
}
200Already requested
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "event_id": "evt_fhnrqxwezs5rkevhmkc3",
  "product": "eligibility",
  "kind": "REQUEST"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDthe session is not an owner or admin, or an API key is presented
422VALIDATIONproduct is not one of the four products
503REGISTRY_WRITE_FAILEDentitlement requests are not configured on this gateway
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoRequires an owner or admin dashboard session — API keys are refused by design, since only a person decides which products an organization buys.
API reference

Look up candidate organizations

GEThttps://api.claimhouse.ai/v1/onboarding/organizations

Resolve which of the candidate organizations the signed-in identity belongs to.

During sign-up the dashboard may hold candidate organization ids (for example from an invitation link). This route returns only the candidates where the verified identity actually holds a membership — naming another identity's organization reveals nothing.

Answers private, no-store. Use it to pre-fill organization choice during onboarding; it is not a directory and cannot be probed for other organizations.

Permission
Signed-in dashboard session (no key permission, no membership needed)
Idempotency
none
Side effects
None. Read only.
In the dashboard
Sign-up > choose organization

Headers

NameTypeDescription
Authorization*stringA signed-in session token as Bearer <token>. This is an onboarding route — it answers before membership exists, so API keys are not used.
e.g. Bearer <session token>

Query parameters

NameTypeDescription
organization_ids*stringComma-separated candidate organization ids — 1 to 100 values, each at most 200 characters with no surrounding whitespace.
e.g. org_EXAMPLE00000000001,org_EXAMPLE00000000002

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
organizations*arrayThe candidates this identity belongs to, in candidate order.
organization_id*stringThe organization id.
e.g. org_EXAMPLE0000000000000001
name*stringOrganization display name.
e.g. Blue Line Dental
state*stringMembership readiness for this identity.
one of: READY · SUSPENDED · PENDING · BLOCKED
e.g. READY
authorization_version*integerThe organization's authorization version at read time.
e.g. 3
can_enter*booleanTrue when the identity may enter this organization now (active membership, active organization, onboarding ready).
e.g. true

Responses

200OK
{
  "request_id": "evt_ang0n1gexynjw0dmfh8v",
  "organizations": [
    {
      "organization_id": "org_35ka7045j25e5w5h2a32",
      "name": "Blue Line Dental",
      "state": "READY",
      "authorization_version": 3,
      "can_enter": true
    }
  ]
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or expired session token
422INVALID_ORGANIZATION_CANDIDATESzero or more than 100 ids, an empty value, or an id over 200 characters
503IDENTITY_REGISTRY_UNAVAILABLEorganization access cannot be verified
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoOnly memberships of the signed-in identity are returned. The route answers a locator question during sign-up; it is not an organization search.
API reference

API keys

Mint, audit and revoke the keys your servers use to authenticate, each with one mode, one scope and a set of permissions.

A key is <key_id>.<secret>, sent as Authorization: Bearer <key>. The key_id (key_ plus 20 lowercase characters) is safe to log. The secret is returned only once, by Create an API key, and is stored as a salted digest that cannot be shown again. Examples use the placeholder key_test_EXAMPLE.secret_EXAMPLE.

SettingValuesMeaning
Modetest, productionA test key submits only to sandbox offices, a production key only to production offices. Reads are not mode filtered.
Scope sellerOmit scopeIdsEvery office of the seller, including offices created later.
Scope groupGroup ids of your sellerEvery office currently in those groups, re-evaluated on each request.
Scope facilityOffice ids of your sellerExactly those offices.
Permissionssubmit, read, webhooks, adminAny non-empty subset. GET /v1/me needs read, so a submit-only key cannot call it.
Minting callerMay create
Any callerOnly permissions it holds
test mode callerOnly test keys
seller scopeAny scope
group scopegroup keys for its own groups, or facility keys for offices in its groups. Not seller keys.
facility scopeOnly facility keys for its own offices
InfoMinting rules apply to creation only. Listing and revoking are seller wide: any read credential lists every key, and any admin credential can revoke any key, including itself. There is no rotate route: create a new key with the same settings, deploy it, confirm traffic on it with last_used_at, then revoke the old key. Both keys work in between.
API reference

List API keys

GEThttps://api.claimhouse.ai/v1/keys

Returns metadata for every key your seller owns, active and revoked, newest first.

Use it to audit access and to find keys that have not been used recently. Secrets and digests are never returned.

Seller wide: any credential with read sees every key of the seller, whatever its own scope. There is no pagination.

Permission
read
Idempotency
none
Side effects
Records an audit entry listing the key ids returned. No vendor call.
In the dashboard
Settings > Developer > API keys

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
keys*arrayArray of key objects, every key of the seller, newest first.
key_id*stringKey id. Safe to log.
e.g. key_EXAMPLE0000000000000002
namestringLabel given at creation. Null when empty.
e.g. north office submitter
mode*stringKey mode.
one of: test · production
e.g. test
scope_kind*stringScope kind.
one of: seller · group · facility
e.g. facility
scope_ids*arrayArray of strings. Granted ids; [<seller id>] for seller keys.
e.g. ["fac_EXAMPLE0000000000000001"]
permissions*arrayArray of strings. Sorted permissions.
e.g. ["read","submit"]
created_bystringKey id of the creator, clerk:<id> for a dashboard user, or null when no creator was recorded (for example a key issued by Claim House).
e.g. key_EXAMPLE0000000000000001
created_at*datetimeISO 8601 creation time.
e.g. 2026-09-14T15:00:00+00:00
last_used_atdatetimeISO 8601 time of the last successful authentication. Null if never used.
e.g. null
revoked_atdatetimeISO 8601 revocation time. Null while active.
e.g. null
revoked_bystringKey id or clerk: id that revoked the key. Null while active.
e.g. null

Responses

200OK
{
  "request_id": "evt_6xsegfmnynpvgzbhgy1h",
  "keys": [
    {
      "key_id": "key_6h9tywc98yqhsy5h7t0n",
      "name": null,
      "mode": "production",
      "scope_kind": "seller",
      "scope_ids": [
        "sel_s4zf3w33k6x8j1e1z6sh"
      ],
      "permissions": [
        "read",
        "submit",
        "webhooks"
      ],
      "created_by": "bootstrap",
      "created_at": "2026-09-05T14:30:00+00:00",
      "last_used_at": null,
      "revoked_at": null,
      "revoked_by": null
    },
    {
      "key_id": "key_7jr01wnnggdpvnsy7a66",
      "name": null,
      "mode": "production",
      "scope_kind": "facility",
      "scope_ids": [
        "fac_q5s09nzww25ysd5a2f3g"
      ],
      "permissions": [
        "read",
        "submit",
        "webhooks"
      ],
      "created_by": "bootstrap",
      "created_at": "2026-09-05T14:30:00+00:00",
      "last_used_at": null,
      "revoked_at": null,
      "revoked_by": null
    },
    {
      "key_id": "key_26z6a4mswbz9c34jjd9n",
      "name": "docs verification",
      "mode": "test",
      "scope_kind": "seller",
      "scope_ids": [
        "sel_s4zf3w33k6x8j1e1z6sh"
      ],
      "permissions": [
        "admin",
        "read",
        "submit",
        "webhooks"
      ],
      "created_by": "bootstrap",
      "created_at": "2026-09-05T14:30:00+00:00",
      "last_used_at": null,
      "revoked_at": null,
      "revoked_by": null
    },
    {
      "key_id": "key_rajhp4p69d3266hejjtc",
      "name": null,
      "mode": "production",
      "scope_kind": "seller",
      "scope_ids": [
        "sel_s4zf3w33k6x8j1e1z6sh"
      ],
      "permissions": [
        "admin",
        "read",
        "submit",
        "webhooks"
      ],
      "created_by": "bootstrap",
      "created_at": "2026-09-05T14:30:00+00:00",
      "last_used_at": null,
      "revoked_at": null,
      "revoked_by": null
    },
    {
      "key_id": "key_7v1bynctfrjr08xahty0",
      "name": "docs verification",
      "mode": "test",
      "scope_kind": "facility",
      "scope_ids": [
        "fac_tycrfy0cs0qc7sq95eme"
      ],
      "permissions": [
        "admin",
        "read",
        "submit",
        "webhooks"
      ],
      "created_by": "bootstrap",
      "created_at": "2026-09-05T14:30:00+00:00",
      "last_used_at": null,
      "revoked_at": null,
      "revoked_by": null
    },
    {
      "key_id": "key_98p1kx2vsy54ehpr2jnq",
      "name": "docs verification",
      "mode": "production",
      "scope_kind": "seller",
      "scope_ids": [
        "sel_s4zf3w33k6x8j1e1z6sh"
      ],
      "permissions": [
        "admin",
        "read",
        "submit",
        "webhooks"
      ],
      "created_by": "bootstrap",
      "created_at": "2026-09-05T14:30:00+00:00",
      "last_used_at": null,
      "revoked_at": null,
      "revoked_by": null
    },
    {
      "key_id": "key_yhzvgbk8v6w4xa9ywy57",
      "name": "docs verification",
      "mode": "test",
      "scope_kind": "seller",
      "scope_ids": [
        "sel_s4zf3w33k6x8j1e1z6sh"
      ],
      "permissions": [
        "read"
      ],
      "created_by": "bootstrap",
      "created_at": "2026-09-05T14:30:00+00:00",
      "last_used_at": null,
      "revoked_at": null,
      "revoked_by": null
    }
  ]
}
403Permission denied
{
  "error": "PERMISSION_DENIED",
  "message": "permission denied",
  "errors": [
    {
      "permission": "read"
    }
  ],
  "request_id": "evt_7rfv80tdkpyymp1xkewh"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks read
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoThe office detail page in the dashboard also uses this list, filtered to active keys that reach that office.
API reference

Create an API key

POSThttps://api.claimhouse.ai/v1/keys

Mints one key inside the caller's own permissions, mode and scope, and returns its secret once.

Use it to issue a separate key per integration, office or environment. The minting rules apply: a key can never create a key stronger than itself.

The full key string is returned only in this response. created_at is not returned; read it from List API keys.

Permission
admin
Idempotency
none
Side effects
Creates the key and its grants and records a key created event. No vendor call.
In the dashboard
Settings > Developer > API keys

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret
Content-Type*stringMust be application/json.
e.g. application/json

Request body

NameTypeDescription
namestringLabel shown in key lists. Up to 120 characters. Defaults to an empty string.
e.g. north office submitter
modestringKey mode. Defaults to test.
one of: test · production
e.g. test
scopeKindstringScope kind. Defaults to seller.
one of: seller · group · facility
e.g. facility
scopeIdsarrayArray of strings. Group ids for group keys, office ids for facility keys; omit for seller keys. Up to 64 items; duplicates are collapsed.
e.g. ["fac_EXAMPLE0000000000000001"]
permissions*arrayArray of strings. At least one of submit, read, webhooks, admin, each held by the caller.
one of: submit · read · webhooks · admin
e.g. ["submit","read"]

Request example

{
  "name": "north office submitter",
  "mode": "test",
  "scopeKind": "facility",
  "scopeIds": [
    "fac_tycrfy0cs0qc7sq95eme"
  ],
  "permissions": [
    "submit",
    "read"
  ]
}

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
key_id*stringThe new key id.
e.g. key_EXAMPLE0000000000000002
namestringLabel. Null when empty.
e.g. north office submitter
mode*stringKey mode.
one of: test · production
e.g. test
scope_kind*stringScope kind.
one of: seller · group · facility
e.g. facility
scope_ids*arrayArray of strings. Stored grant ids; [<seller id>] when none were sent.
e.g. ["fac_EXAMPLE0000000000000001"]
permissions*arrayArray of strings. Sorted permissions.
e.g. ["read","submit"]
secret*stringThe full key string <key_id>.<secret> to send as Authorization: Bearer. Shown only in this response.
e.g. key_EXAMPLE0000000000000002.secret_EXAMPLE
secret_shown_once*booleanAlways true.
e.g. true

Responses

201Created
{
  "request_id": "evt_e3j4hjahy20pegkgy56s",
  "key_id": "key_qh935chyky1z37jap1yj",
  "name": "north office submitter",
  "mode": "test",
  "scope_kind": "facility",
  "scope_ids": [
    "fac_tycrfy0cs0qc7sq95eme"
  ],
  "permissions": [
    "read",
    "submit"
  ],
  "secret": "key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret",
  "secret_shown_once": true
}
403Key escalation
{
  "error": "KEY_ESCALATION",
  "message": "a key cannot grant permissions it does not hold",
  "errors": [
    {
      "permissions": [
        "webhooks"
      ]
    }
  ],
  "request_id": "evt_xrysvceszptzvc7p2t95"
}
422Unknown scope ids
{
  "error": "VALIDATION",
  "message": "scopeIds name facilities this seller does not have",
  "errors": [
    {
      "scope_ids": [
        "fac_byjtcdyn6ftbhpxhczwr"
      ]
    }
  ],
  "request_id": "evt_5cx6w1pc0er88x0p46xn"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential
403PERMISSION_DENIEDcredential lacks admin
403FACILITY_NOT_GRANTEDthe caller's own group or office scope resolves to no office
403KEY_ESCALATIONrequested permissions, mode or scope beyond the caller's own
422VALIDATIONscopeIds name offices or groups your seller does not have
422INVALID_REQUESTmissing permissions, unknown permission or mode, more than 64 scopeIds, or an unknown field
429TOO_MANY_REQUESTSrate or concurrency limit reached
ImportantNot idempotent. A retried request creates a second key. If a response is lost, list keys and revoke any key you did not receive a secret for.
ImportantSend non-empty scopeIds for group and facility keys and omit them for seller keys. A group or facility key sent without scopeIds is created but reaches no office, and a seller key sent with foreign scopeIds is refused with 403 FACILITY_NOT_GRANTED on every scoped route.
InfoUnknown scope ids return code VALIDATION, not INVALID_REQUEST. The body is strict: unknown fields are refused and strings are trimmed.
API reference

Revoke an API key

DELETEhttps://api.claimhouse.ai/v1/keys/{key_id}

Revokes one of your seller's keys immediately and permanently.

The next request made with the key gets 401. Use it when a key leaks, an integration is retired, or after rotating to a new key.

Seller wide: any admin credential can revoke any key of the seller, including seller-scoped keys and itself. There is no un-revoke.

Permission
admin
Idempotency
none
Side effects
Sets revoked_at and revoked_by and records a key revoked event; requests already in flight finish, and the key cannot authenticate again. No vendor call.
In the dashboard
Settings > Developer > API keys

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Path parameters

NameTypeDescription
key_id*stringThe key_... id, not the full key string. 1 to 64 characters.
e.g. key_1n71fpwn02kz0k3m5bbw

Request example

null

Response fields

NameTypeDescription
request_id*stringUnique id for this request. Also returned in the X-Request-Id header.
e.g. evt_EXAMPLE0000000000000031
key_id*stringThe revoked key id.
e.g. key_EXAMPLE0000000000000002
revoked*booleanAlways true.
e.g. true

Responses

200OK
{
  "request_id": "evt_vc1amfhv11434kj9c446",
  "key_id": "key_1n71fpwn02kz0k3m5bbw",
  "revoked": true
}
409Already revoked
{
  "error": "KEY_ALREADY_REVOKED",
  "message": "that key is already revoked",
  "errors": [],
  "request_id": "evt_xv44qd2x2rp0sw9m8bs7"
}
404Key not found
{
  "error": "NOT_FOUND",
  "message": "no such key",
  "errors": [],
  "request_id": "evt_3ew2wdypbanvkpamjd1n"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDmissing, invalid or revoked credential, including a caller that revoked its own key earlier
403PERMISSION_DENIEDcredential lacks admin
404NOT_FOUNDno such key in your seller
409KEY_ALREADY_REVOKEDthe key is already revoked
422INVALID_REQUESTkey_id longer than 64 characters
429TOO_MANY_REQUESTSrate or concurrency limit reached
InfoEffectively idempotent in outcome: a second call on the same key returns 409 KEY_ALREADY_REVOKED and changes nothing.
ImportantRevoking the key you are calling with succeeds, and every later request with that key returns 401.
API reference

Usage

Read meter counts for your seller account over a date range.

Usage is a read of the ledger, not a bill. It counts submissions received, eligibility checks run, attachment packets created and offices on the account, with submissions broken down by office. Periods are whole UTC days.

MeterCountsDoes not count
claims_submittedSubmission records received, including held and rejected ones, in both test and production modes. A file with 12 claims counts as 1.Idempotent replays
eligibility_checksStored eligibility results, successful or vendor error.Answers served from cache
attachment_packetsAttachment packets created.
facilities_activeOffices on the account, regardless of status or period.
InfoThere are no meters for claims transported, remittances, attachments sent or amounts, and no pricing or statement data. The totals are always seller-wide, even for office or group scoped keys.
API reference

Get usage

GEThttps://api.claimhouse.ai/v1/usage

Read usage meters for your seller account between two dates.

Returns seller-wide meters for the period with submissions broken down by office. Use it for a usage widget or to reconcile against a statement.

Days are UTC days: the period runs from from at 00:00 UTC up to, not including, the day after to. There is no maximum range and no facility_id or month parameter.

Permission
read
Idempotency
none
Side effects
Records one audit entry naming your seller. No vendor call.
In the dashboard
API only

Headers

NameTypeDescription
Authorization*stringYour API key as Bearer key_<20 characters>.<secret> (the key id is key_ plus 20 lowercase base32 characters). A signed-in dashboard session token is also accepted.
e.g. Bearer key_7qm2x9d4hs3ve6kt8wbn.EXAMPLEsecretEXAMPLEsecret

Query parameters

NameTypeDescription
fromdateFirst day of the period, inclusive (YYYY-MM-DD). Defaults to the first day of the month that contains to.
e.g. 2026-09-01
todateLast day of the period, inclusive (YYYY-MM-DD). Defaults to today in UTC.
e.g. 2026-09-30

Request example

null

Response fields

NameTypeDescription
request_id*stringThe request id.
e.g. evt_EXAMPLE0000000000301
seller_id*stringYour seller id.
e.g. sel_EXAMPLE0000000000001
period*objectThe resolved period.
from*dateThe resolved first day.
e.g. 2026-09-01
to*dateThe resolved last day.
e.g. 2026-09-30
meters*objectCounts for the period. Every meter is 0 when there is nothing to count.
claims_submitted*integerSubmission records received, including held and rejected ones, in test and production modes. A file with many claims counts as 1. Idempotent replays are not counted.
e.g. 42
eligibility_checks*integerStored eligibility results created, successful or vendor error. Cache answers are not counted.
e.g. 130
attachment_packets*integerAttachment packets created.
e.g. 9
facilities_active*integerOffices on your account regardless of status or period, counted up to 5,000.
e.g. 3
by_facility*arrayOne entry per office with at least one submission in the period. Offices with none are omitted; order is not guaranteed.
facility_id*stringThe office id.
e.g. fac_EXAMPLE0000000000001
claims_submitted*integerSubmissions for that office. The entries sum to meters.claims_submitted.
e.g. 30

Responses

200OK
{
  "request_id": "evt_nkpjxkvjpdx58yvk4jgr",
  "seller_id": "sel_s4zf3w33k6x8j1e1z6sh",
  "period": {
    "from": "2026-09-01",
    "to": "2026-09-30"
  },
  "meters": {
    "claims_submitted": 13,
    "eligibility_checks": 0,
    "attachment_packets": 0,
    "facilities_active": 9
  },
  "by_facility": [
    {
      "facility_id": "fac_tycrfy0cs0qc7sq95eme",
      "claims_submitted": 13
    }
  ]
}
422Range reversed
{
  "error": "VALIDATION",
  "message": "to must not precede from",
  "errors": [],
  "request_id": "evt_gqn68xwksccy64p7wzct"
}

Errors

StatusCodeWhen
401UNAUTHORIZEDMissing, malformed, unknown or revoked key, or wrong secret
403PERMISSION_DENIEDThe credential lacks read
422VALIDATIONto is earlier than from
422INVALID_REQUESTfrom or to is not a valid date
429TOO_MANY_REQUESTSMore than 20 requests in flight or the read bucket is empty
ImportantBeta. Against a real database this route can currently return an unhandled 500; retrying does not help, so report it.
InfoA reversed range returns code VALIDATION, not INVALID_REQUEST.
InfoOffice grants are not applied: office and group scoped keys receive the same seller-wide totals and every office's breakdown, and 403 FACILITY_NOT_GRANTED is never returned.

Machine-readable contract: GET https://api.claimhouse.ai/openapi.json. Generated from the same canonical documentation data as the Claim House dashboard.