Claim House API
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.
| Area | What you do | Start with |
|---|---|---|
| Claims | Submit a finished 837D or a JSON claim, then track it from queued to paid | POST /v1/submissions |
| Claim drafts | Stage a claim, review findings and the rendered 837D, then send it once | POST /v1/claims/drafts |
| Attachments | Store x-rays, charts and narratives and send them to the attachment network once | POST /v1/attachment-drafts |
| Eligibility | Run one real-time eligibility check and download it as a PDF | POST /v1/eligibility |
| Payments | Read 835 remittances and what each claim was paid | GET /v1/remittances |
| Events and webhooks | Keep your system in sync without polling every claim | GET /v1/events |
Base URL
https://api.claimhouse.ai/v1
- Requests and responses are JSON over HTTPS.
- Every response carries an
X-Request-Idheader. Log it and quote it when you contact Claim House. - Claim House owns every clearinghouse and network credential. You never hold a vendor login.
key_test_EXAMPLE.secret_EXAMPLE with the key Claim House issues you.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"
- Confirm your key with
GET /v1/me:modeistest,scope.permissionsincludessubmit, andreachable_facility_idslists your office. - Find your office and payer. The claim must carry the office's billing NPI and TIN, and the payer's
primaryPayerId. - Submit the 837D with a fresh
Idempotency-Keystored before you send. A202means intake ran, not that the claim passed:QUEUEDwaits for a transport window,HOLDis under review (do not resubmit),REJECTED_PRE_TRANSPORTfailed an edit (fix it and use a new key). - Track the claim. Read
statefor logic andstatusfor 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"
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
admincan mint equal or weaker keys withPOST /v1/keysand revoke them withDELETE /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
| Permission | Unlocks |
|---|---|
read | Every read: claims, tracking, submissions, events, remittances, artifacts, offices, payers, eligibility results, drafts, keys, usage, GET /v1/me. |
submit | Claim submissions and drafts, eligibility checks and PDFs, attachment packets, drafts and sends. |
webhooks | Create webhook endpoints, rotate secrets, list and replay deliveries. |
admin | Mint 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
| Scope | Reaches |
|---|---|
seller | Every office in your organization. |
group | Every office in the granted groups, resolved on each request. |
facility | Exactly 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.
scopeIds when you mint a group or facility key. A key minted without them reaches no office.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_kind | Accepts |
|---|---|
sandbox | Test keys |
production | Production keys |
- Offices you create with
POST /v1/facilitiesare alwayssandbox. 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.
| Route | Mode rule | Refusal |
|---|---|---|
POST /v1/submissions | Body mode must equal the key mode and the office binding | 403 MODE_MISMATCH |
POST /v1/dental-claims/submission | usageIndicator T needs a test key, P a production key | 403 MODE_MISMATCH |
POST /v1/dental-claims/raw-x12-submission | The key mode must match the office binding | 403 MODE_MISMATCH |
| Claim drafts | The draft takes the key mode at create | 403 DRAFT_ACCESS_DENIED |
| Attachment drafts and providers | The office must be active in the key mode | 403 MODE_MISMATCH |
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.
| Route | Idempotency-Key |
|---|---|
POST /v1/submissions | Required |
POST /v1/dental-claims/submission | Required, 1 to 255 characters |
POST /v1/dental-claims/raw-x12-submission | Required, 1 to 255 characters |
POST /v1/eligibility | Required |
POST /v1/claims/drafts/{draft_id}/submit | Optional. The draft has its own key from create |
What a repeat returns
| You send | Result |
|---|---|
| Same key, same content | The stored answer with idempotent_replay: true. No new claim. |
| Same key, different content | 422 IDEMPOTENCY_KEY_REUSED |
| Same key while the first is still running | 409 IDEMPOTENCY_IN_PROGRESS, retry after 5 seconds |
| New key, identical content, same office, within 24 hours | 409 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
- Your HTTP call timed out or returned a plain
500: send the same body with the sameIdempotency-Key. You get the stored answer,IDEMPOTENCY_IN_PROGRESS, or a normal first answer if the first request never arrived. - A claim is
TRANSPORT_AMBIGUOUSorHOLD: do nothing. Claim House resolves it and you receiveclaim.operator_resolvedor the next claim event. - An attachment send returned
outcome: AMBIGUOUSor503 ATTACHMENT_SEND_UNCERTAIN: do not build a new packet for the same claim. Claim House reconciles it with the network. - 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.
Retry-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.500. Always use a new key for a new submission.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"
}| Field | Type | Description |
|---|---|---|
error | string | Stable code. |
message | string | Human-readable sentence. |
errors | array | Structured detail. Shape depends on the code. Never echoes a claim or patient value. |
request_id | string | Same value as the X-Request-Id header. |
Most important codes
| Status | Code | What to do |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Add an Idempotency-Key. Nothing was recorded. |
| 400 | X12_PARSE_ERROR | Fix the 837D structure and resubmit. |
| 401 | UNAUTHORIZED | Check the key. Do not loop: five failures in 60 seconds block the key for 60 seconds. |
| 403 | PERMISSION_DENIED | Use a key with the permission named in errors[0].permission. |
| 403 | FACILITY_NOT_GRANTED | Use an office from reachable_facility_ids on GET /v1/me. |
| 403 | MODE_MISMATCH | Use a test key for sandbox offices and a production key for production offices. |
| 403 | BILLING_IDENTITY_MISMATCH | Send the office's registered billing NPI and TIN. |
| 404 | NOT_FOUND | Check the id and your key's scope. |
| 409 | DUPLICATE_CONTENT | Do not resend. Track the original submission. |
| 409 | PAYER_NOT_ENROLLED | Complete enrollment with Claim House, then submit. |
| 422 | INVALID_REQUEST | Fix the fields named in errors[].location. |
| 422 | IDEMPOTENCY_KEY_REUSED | Use a new key for new content. |
| 429 | TOO_MANY_REQUESTS | Back off for Retry-After seconds and retry. |
| 502 | VENDOR_ERROR | One 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/submissionsalways answers202when intake ran. Readstate:QUEUED,HOLDorREJECTED_PRE_TRANSPORT.- The JSON and raw X12 claim routes answer
200withstatusACCEPTEDorHOLD, or400withstatusREJECTEDand findings inerrors[]. - Attachment packet sends answer
200withoutcomeOK,FAILEDorAMBIGUOUS.
500 with no envelope. Treat it like a timeout: look the resource up, or repeat with the same Idempotency-Key.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.
| Route | Style | Page size (default / max) | Order |
|---|---|---|---|
GET /v1/claims | cursor, next_cursor, has_more | 100 / 500 | Last updated, newest first |
GET /v1/remittances | cursor, next_cursor, has_more | 100 / 500 | Received, newest first |
GET /v1/events | since_cursor (integer), next_cursor, has_more | 100 / 500 | Sequence, oldest first |
GET /v1/payers | pageToken, nextPageToken | 50 / 50 | Payer id |
GET /v1/claims/drafts | cursor, next_cursor, has_more | 50 / 200 | Newest first |
GET /v1/attachment-drafts | cursor (last draft id), next_cursor | 100 / 100 | Draft id, descending |
GET /v1/facilities/{facility_id}/eligibility | since and limit, no cursor | 100 / 500 | Newest first |
GET /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
| Limit | Value | On breach |
|---|---|---|
| Submit bucket | 20 requests per second per key | 429 TOO_MANY_REQUESTS, Retry-After: 1 |
Read bucket (also webhooks and admin routes) | 100 requests per second per key | 429 TOO_MANY_REQUESTS, Retry-After: 1 |
| In flight | 20 concurrent requests per key | 429 TOO_MANY_REQUESTS, Retry-After: 1 |
| Daily claim quota | 50,000 claims per organization per UTC day | 429 QUOTA_EXCEEDED, Retry-After until UTC midnight |
| Failed authentication | 5 failures in 60 seconds per key and address | 401 UNAUTHORIZED, Retry-After: 60 |
| Payload | 6 MB of X12 | 413 PAYLOAD_TOO_LARGE |
- A
429from the rate guard is returned before any work, so the identical request is safe to retry afterRetry-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/eventsover tight polling.
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
| State | Meaning | What you do |
|---|---|---|
QUEUED | Passed every Claim House edit, waiting for a transport window. | Wait. |
TRANSPORTED | The file reached the clearinghouse and the upload was verified. | Wait for the 997. |
ACK_997_ACCEPTED | The 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, REVERSED | An 835 adjudicated the claim. | Read the remittance. |
HOLD | Stored but held for Claim House review. Never sent while held. | Do not resubmit. |
REJECTED_PRE_TRANSPORT | Failed a Claim House edit at submission. | Fix it and resubmit with a new key. |
TRANSPORT_AMBIGUOUS | Claim House cannot prove the file arrived. | Do not resubmit. An operator resolves it. |
NEEDS_CORRECTION | A 997, 999 or 277 rejected the claim. | Correct and resubmit as a new claim. |
STALLED_997, STALLED_277 | An 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
| File | From | Answers | Does not mean |
|---|---|---|---|
| Gateway 999 | Claim House, in the submission response | The file passed Claim House structural edits. | Nothing has been sent yet. |
| 999 / 997 | Clearinghouse | The file or transaction set was accepted or rejected. | The payer has seen the claim. |
| 277CA | Clearinghouse or payer | The claim was accepted into the payer's system. | A payment decision. |
| 277 | Payer | Claim status: received, accepted, pending, finalized or rejected. | Money. A final 277 is not a payment. |
| 835 | Payer | Paid 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.
SAMPLE-20260914-0001 on first transport. You can search for it with GET /v1/claims?tenant_claim_id=.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.
| Event | Meaning |
|---|---|
claim.queued | Passed intake, waiting for a transport window. |
claim.received | Stored on hold at intake. |
submission.rejected_pre_transport | Failed Claim House edits. It will never be sent. |
claim.transported | The file reached the clearinghouse, verified. |
claim.transport_ambiguous | Claim House cannot prove the file arrived. Do not resubmit. |
claim.operator_resolved | An operator recorded the outcome of an ambiguous file. |
claim.ack_997, claim.ack_999 | A clearinghouse acknowledgment arrived. |
claim.status_277 | A payer 277 or 277CA arrived. |
claim.stalled_997, claim.stalled_277 | An expected response is late. |
claim.paid, claim.denied, claim.reversed | An 835 adjudicated the claim. |
remittance.received | An 835 carrying your claims was processed. |
eligibility.checked, eligibility.pdf_generated | Eligibility activity. |
attachment.packet_created, attachment.sent, attachment.failed | Attachment 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_idand 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.
Claims
Submit dental claims as 837D or JSON, then track each claim from queued to paid.
| Path | Route | You send | Use it when |
|---|---|---|---|
| Raw 837D | POST /v1/submissions | A finished 837D (up to 1,000 claims) in a JSON envelope | Your system already produces 837D. The primary partner path. |
| JSON claim | POST /v1/dental-claims/submission | One claim as dental claim JSON | You hold claim fields and want Claim House to build the 837D. |
| Raw 837D, compat response | POST /v1/dental-claims/raw-x12-submission | {"x12": "..."} | You are migrating an existing clearinghouse integration. |
| Draft, build, submit | POST /v1/claims/drafts | X12, JSON or ADA form fields | A 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.
| Outcome | Meaning | What you do |
|---|---|---|
QUEUED / ACCEPTED | Passed every edit, waiting for a transport window. | Track the claim. |
HOLD | Stored but held for Claim House review (PACKET_PARITY, IDENTITY_HIERARCHY, or a builder hold). | Do not resubmit. Watch events. |
REJECTED_PRE_TRANSPORT / REJECTED | Failed an edit. Findings list the codes. | Correct and resubmit with a new Idempotency-Key. |
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.
Submit an 837D file
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Your unique value per logical submission. Same key and same X12 within 24 hours replays the stored receipt. e.g. idem-EXAMPLE-0001 |
| Content-Type* | string | Must be application/json. The 837D travels inside the JSON body.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office the file bills as. Must be granted to your key. 1 to 64 characters. e.g. fac_tycrfy0cs0qc7sq95eme |
| mode* | string | Must equal the key mode and the office binding. one of: production · test e.g. test |
| x12* | string | The 837D text, version 005010X224A2. At most 6 MB (UTF-8 bytes).e.g. ISA*00*...~ |
| format | string | Reserved. Only x12_837d, the default.one of: x12_837d |
| tenant_reference | string | Your reference for the whole submission, stored on the submission (not on each claim). At most 200 characters. e.g. batch-EXAMPLE-001 |
| attachments | array | Array 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_id | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id, also in X-Request-Id. |
| submission_id* | string | The submission id, sub_.... |
| state* | string | Submission state at intake. one of: QUEUED · HOLD · REJECTED_PRE_TRANSPORT |
| idempotent_replay* | boolean | true when this is the stored answer to an earlier request. |
| claims* | array | One entry per CLM in the file. |
| claim_id* | string | The claim id, clm_.... |
| tenant_claim_id* | string | Your original CLM01 as it appeared in the file. |
| pcn* | string | CLM01 as transmitted: <office prefix>-<CLM01>, tokenized when longer than 17 characters. |
| payer_id* | string | Loop 2010BB payer id from the file. |
| state* | string | Same as the submission state at intake. |
| was_tokenized* | boolean | true when the PCN is a 12-character token instead of your CLM01. |
| lines* | array | Service lines. |
| line_control_number* | string | REF*6R, filled by Claim House when absent. |
| procedure_code* | string | CDT code. |
| validation* | object | Intake findings. |
| status* | string | REJECTED when any finding has severity error (a hold finding included), otherwise ACCEPTED.one of: ACCEPTED · REJECTED |
| errors* | array | Every finding, errors and warnings. |
| code* | string | Finding code, for example CLM01_DUPLICATE, PWK_PARITY, LICENSE_RECOMMENDED, PACKET_PARITY. |
| severity* | string | Severity. one of: error · warning |
| location* | string | Where the finding applies, for example transaction[0]/claim[1], GS08, interchange. |
| message* | string | Explanation. Never contains a value from your file. |
| value_redacted* | boolean | Always true. |
| acknowledgment_999* | string | The Claim House 999 for this intake decision. Never transported. |
| hold_reason | string | PACKET_PARITY or IDENTITY_HIERARCHY when held. Null otherwise. |
| dispatch | object | Dispatch estimate. Null for held and rejected submissions. |
| window_id | string | Always null at intake. |
| expected_transport_by* | datetime | Estimated transport time: received_at plus the intake window. |
Responses
{
"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"
}
}{
"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
}{
"error": "BILLING_IDENTITY_MISMATCH",
"message": "billing identity mismatch",
"errors": [
{
"facility_id": "fac_tycrfy0cs0qc7sq95eme",
"field": "billing_npi"
}
],
"request_id": "evt_1rh01fbqrnwm6xfrbm03"
}{
"error": "DUPLICATE_CONTENT",
"message": "identical content was already submitted for this facility",
"errors": [
{
"submission_id": "sub_0n9j105ww2rjxwph6ef4"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Header missing or blank |
| 400 | X12_PARSE_ERROR | Text does not parse as 837D |
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route permission |
| 403 | FACILITY_NOT_GRANTED | Office unknown or outside your grants |
| 403 | MODE_MISMATCH | mode differs from the key or office binding |
| 403 | BILLING_IDENTITY_MISMATCH | A billing NPI or TIN is not the office's |
| 409 | PAYER_NOT_ENROLLED | A payer is not live for this office |
| 409 | IDEMPOTENCY_IN_PROGRESS | Same key still being written (Retry-After: 5) |
| 409 | DUPLICATE_CONTENT | Identical X12 under another key within 24 hours |
| 413 | PAYLOAD_TOO_LARGE | X12 over 6 MB |
| 422 | IDEMPOTENCY_KEY_REUSED | Same key, different X12, within 24 hours |
| 422 | INVALID_REQUEST | Body or parameters do not fit the schema |
| 429 | TOO_MANY_REQUESTS | Over 20 in flight or rate bucket empty |
| 429 | QUOTA_EXCEEDED | Seller daily claim quota used up |
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.409 PAYER_NOT_ENROLLED) if something changed.Idempotency-Key optional and lists only 202 and 422. The gateway requires the header.Submit a dental claim as JSON
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | 1 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Query parameters
| Name | Type | Description |
|---|---|---|
| include | string | Comma-separated extras. x12 returns the built 837D as renderedX12. At most 40 characters.e.g. x12 |
Request body
| Name | Type | Description |
|---|---|---|
| usageIndicator* | string | ISA15. T needs a test key and P a production key.one of: T · P e.g. T |
| tradingPartnerServiceId* | string | The payer's primary id, internal id or alias in the payer registry. Pattern ^[A-Za-z0-9.\-_ ]{1,80}$.e.g. PAYEREXAMPLE |
| tradingPartnerName* | string | Payer name for Loop 2010BB NM103, used as sent. 1 to 60 characters. e.g. Example Dental Plan |
| facilityId* | string | The office the claim bills as. Starts with fac_, 1 to 40 characters. Must be granted to your key.e.g. fac_tycrfy0cs0qc7sq95eme |
| tenantClaimId | string | Your own reference. Stored as the submission's tenant_reference. At most 80 characters.e.g. your-claim-EXAMPLE-3 |
| submitter | object | Accepted and ignored. Claim House writes Loop 1000A. |
| receiver | object | Accepted and ignored. Claim House writes Loop 1000B. |
| subscriber* | object | The policyholder (Loop 2010BA). |
| memberId* | string | The subscriber's member id on the plan. 2 to 80 characters. e.g. SYN000123456 |
| firstName* | string | First name. 1 to 35 characters. e.g. Sample |
| lastName* | string | Last name. 1 to 60 characters. e.g. Subscriber |
| middleName | string | Middle name. At most 25 characters. |
| dateOfBirth* | string | Date of birth as YYYYMMDD. Must be a real calendar date.e.g. 20260901 |
| gender* | string | Gender code. one of: M · F · U |
| groupNumber | string | Group number. At most 30 characters. |
| address* | object | Home address. |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| paymentResponsibilityLevelCode* | string | SBR01. Whether this plan pays first, second or third. one of: P · S · T |
| ssn | string | Social Security number, 9 digits. Optional. |
| dependent | object | The patient when the patient is not the subscriber (Loop 2010CA). Omit when the subscriber is the patient. |
| memberId | string | Accepted but never written. Loop 2010CA carries no id. 2 to 80 characters. |
| firstName* | string | First name. 1 to 35 characters. e.g. Sample |
| lastName* | string | Last name. 1 to 60 characters. e.g. Patient |
| middleName | string | Middle name. At most 25 characters. |
| dateOfBirth* | string | Date of birth as YYYYMMDD. Must be a real calendar date.e.g. 20260901 |
| gender* | string | Gender code. one of: M · F · U |
| groupNumber | string | Group number. At most 30 characters. |
| address* | object | Home address. |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| paymentResponsibilityLevelCode* | string | SBR01 of the covering subscriber. one of: P · S · T |
| ssn | string | Social Security number, 9 digits. Optional. |
| relationshipToSubscriberCode* | string | PAT01, 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* | object | The 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* | string | Billing NPI, 10 digits with a valid check digit. e.g. 1234567893 |
| organizationName | string | Organization name. At most 60 characters. Conditional, see above. e.g. Sample Dental Group |
| lastName | string | Last name for a person billing provider. At most 60 characters. |
| firstName | string | First name for a person billing provider. At most 35 characters. |
| taxonomyCode | string | Provider taxonomy, pattern ^[0-9A-Z]{9}X$. |
| employerId | string | Employer identification number, 9 digits. Conditional, see above. e.g. 000000000 |
| ssn | string | Tax SSN, 9 digits. Conditional, see above. |
| address* | object | Billing provider street address. |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| contactInformation | object | PER contact. |
| name | string | Contact name. At most 60 characters. |
| phoneNumber* | string | 10 digits, no punctuation. Required when contactInformation is sent. |
| string | Email. At most 256 characters. | |
| stateLicenseNumber | string | REF*0B license number. At most 50 characters. Leaving it out produces the LICENSE_RECOMMENDED warning. |
| payToAddress | object | Pay-to address (Loop 2010AB). |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| billingPayToAddressName | string | Pay-to name. At most 60 characters. |
| rendering | object | The rendering provider (Loop 2310B). |
| npi* | string | Rendering provider NPI, 10 digits with a valid check digit. e.g. 1234567893 |
| firstName* | string | First name. 1 to 35 characters. e.g. Sample |
| lastName* | string | Last name. 1 to 60 characters. e.g. Provider |
| middleName | string | Middle name. At most 25 characters. |
| taxonomyCode | string | Provider taxonomy, pattern ^[0-9A-Z]{9}X$. |
| stateLicenseNumber | string | State license number. At most 50 characters. |
| payerAddress | object | Payer address (Loop 2010BB N3/N4). |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| billingPayToAddressName | string | Pay-to name. At most 60 characters. Claim House moves it under billing. |
| claimInformation* | object | The claim (Loop 2300 and below). |
| patientControlNumber | string | CLM01, pattern ^[A-Za-z0-9.\-]{1,17}$. When omitted Claim House allocates 12 random characters. Always transmitted with your facility prefix. |
| claimChargeAmount* | string | CLM02 total charge. Amount as a string with exactly two decimals, for example 150.00.e.g. 150.00 |
| claimFrequencyCode* | string | CLM05-3: 1 original, 7 replacement, 8 void.one of: 1 · 7 · 8 e.g. 1 |
| placeOfServiceCode* | string | CLM05-1 place of service, 2 digits. e.g. 11 |
| signatureIndicator* | string | CLM06 provider signature on file. one of: Y · N |
| planParticipationCode* | string | CLM07 assignment or plan participation. one of: A · B · C |
| benefitsAssignmentCertificationIndicator* | string | CLM08 benefits assignment. one of: N · W · Y |
| releaseInformationCode* | string | CLM09 release of information. one of: Y · I |
| claimFilingCode* | string | SBR09 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 |
| predeterminationOfBenefits | boolean | CLM19 PB. true makes this a predetermination, which must not carry service dates. Defaults to false. |
| patientAmountPaid | string | Amount the patient already paid. Amount as a string with exactly two decimals, for example 150.00. |
| orthodonticTotalMonthsOfTreatment | integer | Total months of orthodontic treatment, 0 to 99. |
| orthodonticTreatmentMonthsCount | integer | DN102 months remaining, 0 to 99, not more than the total. |
| orthodonticTreatmentIndicator | boolean | DN104 orthodontic treatment indicator. |
| toothStatus | array | Missing, extracted or impacted teeth. At most 35. |
| toothNumber* | string | Tooth: 1 to 32, 51 to 82, A to T, or AS to TS. |
| toothStatusCode* | string | Status code. one of: E · I · M |
| healthCareCodeInformation | array | Diagnosis codes. At most 4. The first must be ABK and only one ABK is allowed. |
| diagnosisTypeCode* | string | ABK principal or ABF other.one of: ABK · ABF |
| diagnosisCode* | string | ICD-10-CM code without the decimal, 3 to 8 characters. |
| claimDateInformation | object | Claim-level dates. |
| serviceDate | string | YYYYMMDD. Must equal the earliest line serviceDate. Not allowed on a predetermination. |
| appliancePlacementDate | string | YYYYMMDD. |
| accidentDate | string | YYYYMMDD. Required exactly when relatedCausesCode is sent. |
| claimNotes | array | NTE claim notes. At most 5. |
| noteReferenceCode* | string | Note type. one of: ADD · CER · DCP · DGN · TPO |
| description* | string | Note text. 1 to 400 characters. |
| relatedCausesCode | array | Array of strings. At most 3 of AA, EM, OA. Requires accidentDate. |
| autoAccidentStateCode | string | CLM11-4, 2 letters. Required with AA, not allowed without it. |
| autoAccidentCountryCode | string | CLM11-5, 2 or 3 letters. Only with AA. |
| serviceFacilityLocation | object | Service facility (Loop 2310C). |
| organizationName* | string | Facility name. 1 to 60 characters. Required when the block is sent. |
| npi | string | Facility NPI with a valid check digit. |
| address* | object | Facility address. |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| claimSupplementalInformation | object | Reference numbers and attachment reports. |
| priorAuthorizationNumber | string | Prior authorization number. At most 50 characters. |
| claimControlNumber | string | The payer's claim control number. At most 50 characters. Required for frequency 7 and 8, not allowed for 1. |
| predeterminationIdentifier | string | REF*G3 predetermination id. At most 50 characters. |
| reportInformation | object | One PWK attachment report. |
| attachmentReportTypeCode* | string | PWK01 report type code (for example OZ, RB radiographs, DA dental models). NEA attachments use OZ.e.g. OZ |
| attachmentTransmissionCode* | string | PWK02 transmission code. AA carries no control number. NEA uses EL.one of: AA · BM · EL · EM · FT · FX e.g. EL |
| attachmentControlNumber | string | The NEA number you already hold. 1 to 50 characters. Not allowed together with attachmentId. |
| attachmentId | string | A pkt_ attachment packet id that Claim House resolves to its NEA number. 1 to 40 characters. Not allowed together with attachmentControlNumber. |
| reportInformations | array | More PWK attachment reports. At most 10. |
| attachmentReportTypeCode* | string | PWK01 report type code (for example OZ, RB radiographs, DA dental models). NEA attachments use OZ.e.g. OZ |
| attachmentTransmissionCode* | string | PWK02 transmission code. AA carries no control number. NEA uses EL.one of: AA · BM · EL · EM · FT · FX e.g. EL |
| attachmentControlNumber | string | The NEA number you already hold. 1 to 50 characters. Not allowed together with attachmentId. |
| attachmentId | string | A pkt_ attachment packet id that Claim House resolves to its NEA number. 1 to 40 characters. Not allowed together with attachmentControlNumber. |
| otherSubscriberInformation | array | Other coverage (Loop 2320/2330). At most 10. Rendered, then the claim holds with COB_TRANSPORT_GATED. |
| paymentResponsibilityLevelCode* | string | Payer order. one of: P · S · T |
| individualRelationshipCode* | string | SBR02 relationship. one of: 01 · 18 · 19 · 20 · 21 · 39 · 40 · 53 · G8 |
| groupNumber | string | Group number. At most 30 characters. |
| claimFilingIndicatorCode* | string | Claim 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* | string | Benefits assignment. one of: N · W · Y |
| releaseInformationCode* | string | Release of information. one of: Y · I |
| payerPaidAmount | string | Amount the other payer paid. Amount as a string with exactly two decimals, for example 150.00. |
| otherSubscriberName* | object | The other policyholder. |
| firstName* | string | 1 to 35 characters. |
| lastName* | string | 1 to 60 characters. |
| memberId* | string | 2 to 80 characters. |
| address | object | Address. |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| otherPayerName* | object | The other payer. |
| payerId* | string | 2 to 80 characters. |
| organizationName* | string | 1 to 60 characters. |
| address | object | Address. |
| address1* | string | Street line. 1 to 55 characters. e.g. 100 Sample Street |
| address2 | string | Second street line. At most 55 characters. Null when absent. |
| city* | string | City. 2 to 30 characters. e.g. Sampletown |
| state* | string | Two-letter state code, uppercased. e.g. OH |
| postalCode* | string | ZIP code, 5 or 9 digits. e.g. 44000 |
| serviceLines* | array | Service lines, 1 to 1,000. Claims over 50 lines are split into parts with suffixed control numbers (CLAIM_SPLIT finding). |
| serviceDate | string | DTP*472 YYYYMMDD. Required on every line of a claim, not allowed on a predetermination.e.g. 20260901 |
| providerControlNumber | string | REF*6R line control number, pattern ^[A-Za-z0-9.\-]{1,30}$. Filled by Claim House when absent. |
| renderingProvider | object | Line rendering provider. Folded when it equals the claim's rendering, otherwise the claim holds with LINE_RENDERING_UNSUPPORTED. |
| npi* | string | Rendering provider NPI, 10 digits with a valid check digit. e.g. 1234567893 |
| firstName* | string | First name. 1 to 35 characters. e.g. Sample |
| lastName* | string | Last name. 1 to 60 characters. e.g. Provider |
| middleName | string | Middle name. At most 25 characters. |
| taxonomyCode | string | Provider taxonomy, pattern ^[0-9A-Z]{9}X$. |
| stateLicenseNumber | string | State license number. At most 50 characters. |
| dentalService* | object | The procedure (SV3). |
| procedureCode* | string | SV301 CDT code, D plus 4 digits.e.g. D1110 |
| lineItemChargeAmount* | string | SV302 line charge. Amount as a string with exactly two decimals, for example 150.00.e.g. 150.00 |
| placeOfServiceCode | string | Line place of service, 2 digits. |
| procedureCount | integer | Units, 0 to 99. |
| oralCavityDesignation | array | Array of strings. At most 5 of 00 01 02 10 20 30 40. |
| prosthesisCrownOrInlayCode | string | SV305 initial or replacement. one of: I · R |
| procedureModifier | array | Array of strings. At most 4 modifiers. |
| description | string | Procedure description. At most 80 characters. |
| compositeDiagnosisCodePointers | object | Diagnosis pointers. A bare integer array is also accepted here. |
| diagnosisCodePointers | array | Array of integers. At most 4. |
| teethInformation | array | Teeth and surfaces. At most 32. |
| toothCode* | string | Tooth: 1 to 32, 51 to 82, A to T, or AS to TS. e.g. 3 |
| toothSurfaceCodes | array | Array of strings. At most 7 of B D F I L M O. No O on anterior teeth, no I on posterior teeth. |
| serviceLineDateInformation | object | Line dates. |
| priorPlacementDate | string | DTP*441 YYYYMMDD. |
| appliancePlacementDate | string | DTP*452 YYYYMMDD. |
| treatmentStartDate | string | YYYYMMDD. |
| treatmentCompletionDate | string | YYYYMMDD. |
| serviceLineSupplementalInformation | array | Line attachment reports (array or one object). At most 10. Folded to the claim level with a LINE_ATTACHMENT_FOLDED finding. |
| attachmentReportTypeCode* | string | PWK01 report type code (for example OZ, RB radiographs, DA dental models). NEA attachments use OZ.e.g. OZ |
| attachmentTransmissionCode* | string | PWK02 transmission code. AA carries no control number. NEA uses EL.one of: AA · BM · EL · EM · FT · FX e.g. EL |
| attachmentControlNumber | string | The NEA number you already hold. 1 to 50 characters. Not allowed together with attachmentId. |
| attachmentId | string | A pkt_ attachment packet id that Claim House resolves to its NEA number. 1 to 40 characters. Not allowed together with attachmentControlNumber. |
| lineAdjudicationInformation | array | Other payer line adjudication (Loop 2430). At most 15. The claim holds with LINE_ADJUDICATION_UNSUPPORTED. |
| otherPayerPrimaryIdentifier* | string | 2 to 80 characters. |
| serviceLinePaidAmount* | string | Amount as a string with exactly two decimals, for example 150.00. |
| procedureCode* | string | D plus 4 digits. |
| paidServiceUnitCount | integer | 0 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
| Name | Type | Description |
|---|---|---|
| status* | string | ACCEPTED (queued for transport), HOLD (held for operator review) or REJECTED (stopped by Claim House edits).one of: ACCEPTED · HOLD · REJECTED |
| controlNumber | string | ST02 of the built transaction. |
| tradingPartnerServiceId | string | Your value as sent. |
| claimReference* | object | Where to find the claim. |
| correlationId | string | The sub_ submission id. Null on a PAYER_NOT_FOUND refusal. |
| patientControlNumber | string | CLM01 as transmitted, including an allocated one, with your facility prefix. |
| payerId | string | Registry primary payer id when the payer resolves, otherwise the id from the file. |
| formatVersion* | string | Always 5010. |
| timeOfResponse* | datetime | When Claim House answered. |
| serviceLines* | array | Line control numbers. |
| lineItemControlNumber* | string | REF*6R line control number. |
| claimId | string | The clm_ claim id. |
| facilityId* | string | The resolved office. |
| payer* | object | The resolved payer. |
| payerId | string | Same rule as claimReference.payerId. |
| payerName | string | Registry display name. Null when the payer did not resolve. |
| x12 | string | The Claim House 999 for this intake decision. Never transported. |
| renderedX12 | string | The 837D as built, only with include=x12. Intake rewrites CLM01, REF*6R and REF*D9 before storing. |
| errors* | array | Intake findings plus builder findings (LINE_ATTACHMENT_FOLDED, TEXT_SANITIZED, CLAIM_SPLIT, PCN_TOKENIZED and others). Empty when there are none. |
| code* | string | Finding or hold code. |
| description* | string | Message. Never contains a value from your claim. |
| followupAction | string | What to do: correct and resubmit with a new key for errors, do not resubmit for a hold, no action for warnings. |
| location | string | Where the finding applies, with segment id and position when known. |
| value | string | Always null. |
| meta* | object | Request metadata. |
| traceId* | string | The request id, also in X-Request-Id. |
| applicationMode* | string | Mode of the key. one of: TEST · PRODUCTION |
| facilityId* | string | The resolved office. |
| idempotentReplay* | boolean | true when this is the stored answer to an earlier request. |
| httpStatusCode* | integer | 200 or 400, matching the HTTP status. |
Responses
{
"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
}{
"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
}{
"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
| Status | Code | When |
|---|---|---|
| 400 | PAYER_NOT_FOUND | Payer id not in the registry (JSON claim body) |
| 400 | IDEMPOTENCY_KEY_REQUIRED | Header missing or blank |
| 400 | IDEMPOTENCY_KEY_INVALID | Header longer than 255 characters |
| 400 | CLAIM_BUILD_FAILED | The claim model or builder refused the claim |
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route permission |
| 403 | FACILITY_NOT_GRANTED | Office unknown or outside your grants |
| 403 | MODE_MISMATCH | usageIndicator or office binding does not match the key |
| 403 | BILLING_IDENTITY_MISMATCH | Built billing NPI or TIN is not the office's |
| 409 | PAYER_NOT_ENROLLED | Payer not live for this office |
| 409 | IDEMPOTENCY_IN_PROGRESS | Same key still being written (Retry-After: 5) |
| 409 | DUPLICATE_CONTENT | Identical body under another key within 24 hours |
| 413 | PAYLOAD_TOO_LARGE | Built X12 over 6 MB |
| 422 | ATTACHMENT_REFERENCE_AMBIGUOUS | More than one distinct attachmentId |
| 422 | IDEMPOTENCY_KEY_REUSED | Same key, different body |
| 422 | INVALID_REQUEST | Body or parameters do not fit the schema |
| 429 | TOO_MANY_REQUESTS | Over 20 in flight or rate bucket empty |
| 429 | QUOTA_EXCEEDED | Seller daily claim quota used up |
| 502 | CLAIM_BUILD_INVALID | Builder output cannot be parsed or transported |
| 503 | CLAIM_BUILDER_UNAVAILABLE | Builder not configured on this gateway |
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.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.CLAIM_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.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.PAYER_NOT_FOUND refusal stores nothing, so after fixing the payer you may reuse the same Idempotency-Key.Submit raw X12 with a compat response
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | 1 to 255 characters. Same key and same X12 within 24 hours replays with meta.idempotentReplay: true.e.g. idem-EXAMPLE-0001 |
| Content-Type* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Query parameters
| Name | Type | Description |
|---|---|---|
| include | string | Comma-separated extras. x12 returns the text as renderedX12. At most 40 characters.e.g. x12 |
Request body
| Name | Type | Description |
|---|---|---|
| x12* | string | The 837D text, version 005010X224A2. At most 6 MB.e.g. ISA*00*...~ |
| facilityId | string | The 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 |
| tenantClaimId | string | Your 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
| Name | Type | Description |
|---|---|---|
| status* | string | ACCEPTED (queued for transport), HOLD (held for operator review) or REJECTED (stopped by Claim House edits).one of: ACCEPTED · HOLD · REJECTED |
| controlNumber | string | ST02 of the first transaction in your file. Null when absent. |
| tradingPartnerServiceId | string | The first claim's Loop 2010BB payer id. |
| claimReference* | object | Where to find the claim. Only the first claim in the file is described. |
| correlationId | string | The sub_ submission id. Null on a PAYER_NOT_FOUND refusal. |
| patientControlNumber | string | CLM01 as transmitted, including an allocated one, with your facility prefix. |
| payerId | string | Registry primary payer id when the payer resolves, otherwise the id from the file. |
| formatVersion* | string | Always 5010. |
| timeOfResponse* | datetime | When Claim House answered. |
| serviceLines* | array | Line control numbers. |
| lineItemControlNumber* | string | REF*6R line control number. |
| claimId | string | The clm_ claim id. |
| facilityId* | string | The resolved office. |
| payer* | object | The resolved payer. |
| payerId | string | Same rule as claimReference.payerId. |
| payerName | string | Registry display name. Null when the payer did not resolve. |
| x12 | string | The Claim House 999 for this intake decision. Never transported. |
| renderedX12 | string | Your submitted text, only with include=x12. |
| errors* | array | Intake findings. Empty when there are none. |
| code* | string | Finding or hold code. |
| description* | string | Message. Never contains a value from your claim. |
| followupAction | string | What to do: correct and resubmit with a new key for errors, do not resubmit for a hold, no action for warnings. |
| location | string | Where the finding applies, with segment id and position when known. |
| value | string | Always null. |
| meta* | object | Request metadata. |
| traceId* | string | The request id, also in X-Request-Id. |
| applicationMode* | string | Mode of the key. one of: TEST · PRODUCTION |
| facilityId* | string | The resolved office. |
| idempotentReplay* | boolean | true when this is the stored answer to an earlier request. |
| httpStatusCode* | integer | 200 or 400, matching the HTTP status. |
Responses
{
"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
}{
"error": "X12_PARSE_ERROR",
"message": "the X12 did not parse as an 837D",
"errors": [
{
"reason": "missing ISA segment"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "FACILITY_NOT_GRANTED",
"message": "facility not granted",
"errors": [
{
"facility_id": "fac_qnwrs4rq9tjtqwv37phe"
}
],
"request_id": "evt_e7dzmn5q5adw2cgkddnf"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Header missing or blank |
| 400 | IDEMPOTENCY_KEY_INVALID | Header longer than 255 characters |
| 400 | X12_PARSE_ERROR | Text does not parse as 837D |
| 400 | NO_CLAIMS | No facilityId and the file carries no claim |
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route permission |
| 403 | FACILITY_NOT_GRANTED | No granted office matches the file, or named office not granted |
| 403 | MODE_MISMATCH | Office binding does not accept the key mode |
| 403 | BILLING_IDENTITY_MISMATCH | A billing NPI or TIN is not the office's |
| 409 | PAYER_NOT_ENROLLED | Payer not live for this office |
| 409 | IDEMPOTENCY_IN_PROGRESS | Same key still being written (Retry-After: 5) |
| 409 | DUPLICATE_CONTENT | Identical X12 under another key within 24 hours |
| 413 | PAYLOAD_TOO_LARGE | X12 over 6 MB |
| 422 | IDEMPOTENCY_KEY_REUSED | Same key, different X12 |
| 422 | INVALID_REQUEST | Body or parameters do not fit the schema |
| 429 | TOO_MANY_REQUESTS | Over 20 in flight or rate bucket empty |
| 429 | QUOTA_EXCEEDED | Seller daily claim quota used up |
claimReference. For a multi-claim file, call GET /v1/submissions/{submission_id} with correlationId to see every claim.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.Get a submission
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| submission_id* | string | The submission id, sub_.... 1 to 64 characters.e.g. sub_rw97ns2b72wkzfk6hwpy |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id, also in X-Request-Id. |
| submission_id* | string | The submission id. |
| facility_id* | string | The office the submission bills as. |
| mode* | string | Mode of the submission. one of: test · production |
| state* | string | Current submission state. one of: QUEUED · HOLD · REJECTED_PRE_TRANSPORT · TRANSPORTED · TRANSPORT_AMBIGUOUS · CLOSED · RECEIVED |
| claim_count* | integer | Number of claims in the submission. |
| tenant_reference | string | Your tenant_reference or JSON tenantClaimId. Null when not sent. |
| received_at* | datetime | When intake received the submission. |
| updated_at* | datetime | Last change to the submission. |
| dispatch* | object | Dispatch window. |
| window_id | string | win_... once a dispatch window picked the submission up. Null before then. |
| transport | object | The transport file record. Null until a file is recorded. |
| file_id* | string | File id, file_.... |
| remote_filename | string | The name the file was put under. |
| isa13 | integer | Interchange control number allocated by the window. |
| gs06 | integer | Group control number. |
| state | string | File state. |
| transported_at | datetime | Verified transport time. |
| session_id | string | Transport session id, ses_.... |
| sha256 | string | Hash of the exact bytes archived and put. |
| size_bytes | integer | File size in bytes. |
| claims* | array | Claims in the submission, ordered by claim id. |
| claim_id* | string | The claim id. |
| tenant_claim_id | string | Your original CLM01. |
| pcn* | string | CLM01 as transmitted. |
| payer_id* | string | Payer id on the claim. |
| state* | string | Current claim state. |
Responses
{
"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"
}
]
}{
"error": "NOT_FOUND",
"message": "no such submission",
"errors": [],
"request_id": "evt_r86fe18bm08e0mwh4amk"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route permission |
| 403 | FACILITY_NOT_GRANTED | Group or office key with no granted office |
| 404 | NOT_FOUND | No such submission in your seller and grants |
| 422 | INVALID_REQUEST | Path id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | Over 20 in flight or rate bucket empty |
404, never 403.List 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id | string | Only 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 |
| state | string | Exact 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_id | string | Exact 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. |
| since | datetime | Only 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 |
| cursor | string | The next_cursor from the previous page. At most 512 characters. |
| limit | integer | Page size, 1 to 500. Defaults to 100. e.g. 100 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id, also in X-Request-Id. |
| claims* | array | Claim rows. |
| claim_id* | string | Claim House claim id, clm_.... |
| claim_reference | string | Human 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_at | datetime | Verified transport time of the file that first carried the claim. Null before transport. |
| reference_date | date | Office-local date used in claim_reference. Null until a reference is assigned. |
| reference_timezone | string | IANA timezone used for reference_date. Null until a reference is assigned. |
| submission_id* | string | The submission that created the claim, sub_.... |
| facility_id* | string | Office id, fac_.... |
| pcn* | string | CLM01 as transmitted: your office prefix, a hyphen, and your value or a 12-character token. |
| tenant_claim_id | string | Your original claim id as you sent it. |
| payer_id* | string | Payer id on the claim. |
| state* | string | Current claim state. See How a claim moves. |
| payer_claim_control_number | string | The payer's claim number, once a 277 carried it. |
| service_date_from | date | First service date. |
| charge_amount* | number | Total charge as a JSON number (for example 150.0). |
| updated_at* | datetime | Last time any event touched the claim. |
| next_cursor | string | Opaque cursor for the next page. Null on the last page. |
| has_more* | boolean | true exactly when next_cursor is not null. |
Responses
{
"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
}{
"error": "INVALID_CURSOR",
"message": "cursor is not readable",
"errors": [],
"request_id": "evt_zz6nhyefazb7hmcnqgk2"
}{
"error": "FACILITY_NOT_GRANTED",
"message": "this key's grants do not cover that facility",
"errors": [
{
"facility_id": "fac_qnwrs4rq9tjtqwv37phe"
}
],
"request_id": "evt_n5g3707m7517ksztz77s"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_CURSOR | Cursor unreadable or not issued by the gateway |
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route permission |
| 403 | FACILITY_NOT_GRANTED | No office grant, or facility_id outside grants |
| 422 | INVALID_REQUEST | A query parameter failed validation |
| 429 | TOO_MANY_REQUESTS | Over 20 in flight or rate bucket empty |
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.updated_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.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".Get a claim
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| claim_id* | string | The 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id, also in X-Request-Id. |
| claim_id* | string | Claim House claim id, clm_.... |
| claim_reference | string | Human 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_at | datetime | Verified transport time of the file that first carried the claim. Null before transport. |
| reference_date | date | Office-local date used in claim_reference. Null until a reference is assigned. |
| reference_timezone | string | IANA timezone used for reference_date. Null until a reference is assigned. |
| submission_id* | string | The submission that created the claim, sub_.... |
| facility_id* | string | Office id, fac_.... |
| pcn* | string | CLM01 as transmitted: your office prefix, a hyphen, and your value or a 12-character token. |
| tenant_claim_id | string | Your original claim id as you sent it. |
| payer_id* | string | Payer id on the claim. |
| state* | string | Current claim state. See How a claim moves. |
| payer_claim_control_number | string | The payer's claim number, once a 277 carried it. |
| service_date_from | date | First service date. |
| charge_amount* | number | Total charge as a JSON number (for example 150.0). |
| updated_at* | datetime | Last time any event touched the claim. |
| d9* | string | The REF*D9 value Claim House stamped into the 837D for this claim. |
| service_date_to | date | Last service date. |
| last_event_at | datetime | When the most recent event occurred. |
| ladder* | array | Every event on the claim, oldest first. Capped at 500. |
| event_id* | string | Event id, evt_.... |
| sequence* | integer | Global event sequence. |
| kind* | string | Event type, for example claim.ack_997. |
| artifact_id | string | Downloadable file behind this event, if any. |
| occurred_at* | datetime | When the event happened. |
| summary* | object | The event payload: codes, ids and amounts, never patient names. Shape depends on the event type. |
| remittance | object | Payment summary. Null until at least one 835 payment record exists for the claim. |
| latest_event_kind | string | Event kind of the most recent payment record: claim.paid, claim.denied, claim.reversed, or null for an unmapped CLP02. |
| latest_claim_status_code* | string | CLP02 of the most recent payment record. |
| latest_remittance_id* | string | Remittance id of the most recent payment record. |
| paid_amount* | string | Sum of paid_amount over every payment record, reversals included, two decimals. |
| patient_responsibility | string | Patient responsibility from the most recent payment record only. |
| payments* | array | Every payment record, ordered by remittance receipt then CLP position. Capped at 500. Same shape as a claim on Get a remittance. |
| payment_id* | string | Payment record id, pay_.... |
| remittance_id* | string | Remittance id, rem_.... |
| claim_id* | string | Claim id. |
| facility_id* | string | Office id. |
| submission_id* | string | Submission id. |
| tenant_claim_id | string | Always null inside this block. |
| clp_position* | integer | Position of the CLP within the 835. |
| patient_control_number* | string | CLP01 as the payer returned it. |
| routed_by* | string | How the payment was matched to the claim, for example PCN. |
| claim_status_code* | string | CLP02. |
| payer_claim_number | string | The payer's claim number. |
| original_reference | string | Original reference when the payer sent one. |
| filing_indicator | string | Claim filing indicator from the 835. |
| charged_amount* | string | Charged amount, two decimals. |
| paid_amount* | string | Paid amount, two decimals. |
| patient_responsibility | string | Patient responsibility, two decimals. |
| event_kind | string | claim.paid, claim.denied, claim.reversed, or null. |
| artifact_id | string | The 835 artifact behind this record. |
| recorded_at* | datetime | When the record was stored. |
| service_lines* | array | Line payment detail from the 835. |
| adjustments* | array | CAS adjustments. |
| adjustment_id* | string | Adjustment id, adj_.... |
| level* | string | claim or line level. |
| line_number | integer | Line number for a line adjustment. Null at claim level. |
| group_code* | string | CAS group code, for example PR. |
| reason_code* | string | CARC reason code. |
| amount* | string | Adjustment amount, two decimals. |
| quantity | number | Adjusted quantity when present. |
| remark_codes* | array | Array of strings. RARC remark codes. |
Responses
{
"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"
}
}
]
}{
"error": "NOT_FOUND",
"message": "no such claim",
"errors": [],
"request_id": "evt_72sgpr7d56rt5v4kyyzy"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route permission |
| 403 | FACILITY_NOT_GRANTED | Key has no office grant |
| 404 | NOT_FOUND | No such claim in your seller and grants |
| 422 | INVALID_REQUEST | Path id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | Over 20 in flight or rate bucket empty |
charge_amount is a JSON number while every amount inside remittance is a two-decimal string. Parse both defensively.remittance.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.claim_reference is not an X12 control number. The values the clearinghouse and payer see are pcn (CLM01) and d9 (REF*D9).Track a claim
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| claim_id* | string | The Claim House claim id, clm_.... 1 to 64 characters.e.g. clm_c41063850yd2fh8x75cm |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| claimId* | string | The claim id. |
| submissionId | string | The submission id. |
| facilityId | string | The office id. |
| patientControlNumber | string | CLM01 as transmitted (pcn). |
| tenantClaimId | string | Your claim id. |
| payerId | string | Payer id. |
| payerClaimNumber | string | The payer's claim number from a 277 or 835. Null when absent. |
| state* | string | Current claim state. |
| status* | string | One plain sentence for the state, for example "Held by the gateway pending operator review. Do not resubmit." |
| ladder | array | Every event, oldest first. Capped at 500. |
| event* | string | Event type. |
| occurredAt | datetime | Event time. |
| artifactId | string | Artifact behind the event. |
| summary | object | Event payload. |
| nextExpected* | object | What should happen next. |
| event | string | The event type expected next (claim.queued, claim.transported, claim.ack_997, claim.status_277 or remittance.received). Null when nothing is expected. |
| dueAt | datetime | Deadline for that event when a watchdog window governs it. Null otherwise, and null when the ladder has no claim.transported event. |
| basis | string | Why that is the expectation, in words. |
| artifacts | array | One entry per ladder event that has an artifact. The same artifact can appear more than once. |
| artifactId* | string | Artifact id. |
| event* | string | Event type. |
| occurredAt | datetime | Event time. |
| remittance | object | Payment summary. Null until at least one 835 payment record exists for the claim. Same block as on Get a claim. |
| latest_event_kind | string | Event kind of the most recent payment record: claim.paid, claim.denied, claim.reversed, or null for an unmapped CLP02. |
| latest_claim_status_code* | string | CLP02 of the most recent payment record. |
| latest_remittance_id* | string | Remittance id of the most recent payment record. |
| paid_amount* | string | Sum of paid_amount over every payment record, reversals included, two decimals. |
| patient_responsibility | string | Patient responsibility from the most recent payment record only. |
| payments* | array | Every payment record, ordered by remittance receipt then CLP position. Capped at 500. Same shape as a claim on Get a remittance. |
| payment_id* | string | Payment record id, pay_.... |
| remittance_id* | string | Remittance id, rem_.... |
| claim_id* | string | Claim id. |
| facility_id* | string | Office id. |
| submission_id* | string | Submission id. |
| tenant_claim_id | string | Always null inside this block. |
| clp_position* | integer | Position of the CLP within the 835. |
| patient_control_number* | string | CLP01 as the payer returned it. |
| routed_by* | string | How the payment was matched to the claim, for example PCN. |
| claim_status_code* | string | CLP02. |
| payer_claim_number | string | The payer's claim number. |
| original_reference | string | Original reference when the payer sent one. |
| filing_indicator | string | Claim filing indicator from the 835. |
| charged_amount* | string | Charged amount, two decimals. |
| paid_amount* | string | Paid amount, two decimals. |
| patient_responsibility | string | Patient responsibility, two decimals. |
| event_kind | string | claim.paid, claim.denied, claim.reversed, or null. |
| artifact_id | string | The 835 artifact behind this record. |
| recorded_at* | datetime | When the record was stored. |
| service_lines* | array | Line payment detail from the 835. |
| adjustments* | array | CAS adjustments. |
| adjustment_id* | string | Adjustment id, adj_.... |
| level* | string | claim or line level. |
| line_number | integer | Line number for a line adjustment. Null at claim level. |
| group_code* | string | CAS group code, for example PR. |
| reason_code* | string | CARC reason code. |
| amount* | string | Adjustment amount, two decimals. |
| quantity | number | Adjusted quantity when present. |
| remark_codes* | array | Array of strings. RARC remark codes. |
| updatedAt | datetime | Last update. |
Responses
{
"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"
}{
"error": "NOT_FOUND",
"message": "no such claim",
"errors": [],
"request_id": "evt_gvr8qxq5eh31dk3yjqb2"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route permission |
| 403 | FACILITY_NOT_GRANTED | Key has no office grant |
| 404 | NOT_FOUND | No such claim in your seller and grants |
| 422 | INVALID_REQUEST | Path id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | Over 20 in flight or rate bucket empty |
CLOSED) reads "In an unrecognized state; an operator has been asked to look." with no next expected event.dueAt 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.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.
| State | Meaning | Edit | Build | Submit | Delete |
|---|---|---|---|---|---|
OPEN | Created, never built | Yes | Yes | No, 409 DRAFT_NOT_READY | Yes |
READY | Last build found no error and no hold | Yes (makes the build stale) | Yes | Yes, if the build is not stale | Yes |
HELD | Last build found an error or hold, or submit landed in intake HOLD | Yes | Yes | Only when the stored verdict is still ready | Yes |
SUBMITTED | Submit queued the claim | No, 409 DRAFT_ALREADY_SUBMITTED | No | No | No |
REJECTED | Submit landed in intake REJECTED_PRE_TRANSPORT | No, 409 DRAFT_NOT_OPEN | No | No | No, 404 DRAFT_NOT_FOUND |
| What build does, by door | Source kinds | Build runs |
|---|---|---|
raw_x12 | x12 | The 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. |
json | json | The 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_form | ada, blank | Not 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. |
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.- Submit only after a fresh passing build:
verdict.readyistrueandbuild_staleisfalse. - Call submit once, with the build's
versionand, optionally, your ownIdempotency-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 asPOST /v1/submissionsplusdraft_id. - 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.
- Never resend. A second submit of a
SUBMITTEDdraft is refused with409 DRAFT_ALREADY_SUBMITTEDbefore intake, and identical content under a different key within 24 hours is409 DUPLICATE_CONTENT. - After a lost response,
GETthe draft, readsubmission_id, thenGET /v1/submissions/{submission_id}.
Create a claim draft
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The 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* | string | What 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 |
| x12 | string | The 837D text, stored verbatim. Required for x12. At most 6 MB of UTF-8 and must parse as 837D. |
| json_claim | object | The JSON dental claim, stored verbatim. Required for json. Must validate as the JSON claim submission body, including facilityId. |
| fields | object | ADA item fields keyed by item number (string values), for ada and blank. Not validated. |
| lines | array | ADA service lines (array of objects), for ada and blank. Not validated. |
| payer | object | ADA payer block, for ada and blank. Not validated. |
| content_base64 | string | PDF 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_id | string | Your 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_id | string | A pkt_ attachment packet id for packet parity. At most 64 characters. Not checked at create.e.g. pkt_20fng953rtp1hwc3zdcn |
| from_claim_id | string | Accepted 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
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft id, drf_....e.g. drf_EXAMPLE0000000000001 |
| facility_id* | string | The office the draft belongs to. Controls who can see the draft. e.g. fac_EXAMPLE0000000000001 |
| source_kind* | string | What the draft was created from. Fixed at create. one of: x12 · json · ada · blank |
| door* | string | The 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* | string | Where the draft is in its lifecycle. one of: OPEN · READY · HELD · SUBMITTED · REJECTED |
| version* | integer | Optimistic lock. Every successful edit, build, and submit increments it. e.g. 1 |
| saved_at* | datetime | ISO 8601 time of the last change. |
| expires_at* | datetime | ISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today. |
| tenant_claim_id | string | Your id for the claim. Null when not set. |
| fields* | object | ADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts. |
| lines* | array | ADA service lines (array of objects) for ada and blank drafts. An empty array otherwise. |
| payer | object | ADA payer block for ada and blank drafts. Null otherwise. |
| findings* | array | Findings from the last build. Empty before the first build. |
| severity* | string | Finding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.one of: HOLD · error · warning · INFO |
| code* | string | Stable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING. |
| message* | string | Human-readable reason. Never contains a claim value. |
| item | string | ADA item number when known. Null for intake findings today. |
| path | string | Machine location of the finding, for example transaction[0]/claim[0]. Null when unknown. |
| verdict* | object | The send decision from the last build. An empty object before the first build. |
| ready | boolean | true only when the last build found no error and no hold. Use this for the send decision. |
| holds | array | Array of strings. Codes of findings with severity HOLD. Can be empty while ready is false. |
| warnings | array | Array of strings. Codes of findings with severity WARNING. Empty for real intake warnings today. |
| built_at | datetime | ISO 8601 time of the last build. Null before the first build. |
| build_stale* | boolean | true until the first build, and again after any edit. Submit requires false. |
| attachment | object | The linked attachment packet. Null when no packet is linked. |
| packet_id* | string | The pkt_ id used for packet parity.e.g. pkt_EXAMPLE0000000000001 |
| submission_id | string | The submission created by submit. Null until submit. |
| submitted_at | datetime | ISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit. |
Responses
{
"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
}{
"error": "DRAFT_SOURCE_UNSUPPORTED",
"message": "draft source unsupported",
"errors": [
{
"source_kind": "from_claim"
}
],
"request_id": "evt_2awbm0d9j3jt5aqceeqa"
}{
"error": "CLAIM_SOURCE_UNREADABLE",
"message": "claim source unreadable",
"errors": [
{
"reason": "The X12 source did not parse."
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key |
| 400 | DRAFT_DENIED | The draft could not be stored |
| 403 | DRAFT_ACCESS_DENIED | Office unknown, inactive, not granted, or bound to the other mode |
| 403 | PERMISSION_DENIED | Key lacks submit |
| 413 | PAYLOAD_TOO_LARGE | x12 or decoded pdf bytes exceed 6 MB |
| 422 | CLAIM_SOURCE_UNREADABLE | x12 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 |
| 422 | DRAFT_SOURCE_UNSUPPORTED | source_kind is from_claim; a future reviewed PDF template also remains unsupported until extraction and immutable source-artifact lineage land together |
| 422 | INVALID_REQUEST | Body or parameters do not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate bucket empty or too many requests in flight |
| 429 | QUOTA_EXCEEDED | Seller's daily claim quota used up |
| 503 | DRAFTS_UNAVAILABLE | Drafts are not configured on this gateway |
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.json_claim.usageIndicator that does not match is caught at build as 400 CLAIM_BUILD_FAILED.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.request_id in the body. Read the X-Request-Id header.Get a claim draft
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft to act on, a drf_ id. 1 to 64 characters.e.g. drf_t2q7c2q033cwa9rhj9ns |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft id, drf_....e.g. drf_EXAMPLE0000000000001 |
| facility_id* | string | The office the draft belongs to. Controls who can see the draft. e.g. fac_EXAMPLE0000000000001 |
| source_kind* | string | What the draft was created from. Fixed at create. one of: x12 · json · ada · blank |
| door* | string | The 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* | string | Where the draft is in its lifecycle. one of: OPEN · READY · HELD · SUBMITTED · REJECTED |
| version* | integer | Optimistic lock. Every successful edit, build, and submit increments it. e.g. 1 |
| saved_at* | datetime | ISO 8601 time of the last change. |
| expires_at* | datetime | ISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today. |
| tenant_claim_id | string | Your id for the claim. Null when not set. |
| fields* | object | ADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts. |
| lines* | array | ADA service lines (array of objects) for ada and blank drafts. An empty array otherwise. |
| payer | object | ADA payer block for ada and blank drafts. Null otherwise. |
| findings* | array | Findings from the last build. Empty before the first build. |
| severity* | string | Finding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.one of: HOLD · error · warning · INFO |
| code* | string | Stable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING. |
| message* | string | Human-readable reason. Never contains a claim value. |
| item | string | ADA item number when known. Null for intake findings today. |
| path | string | Machine location of the finding, for example transaction[0]/claim[0]. Null when unknown. |
| verdict* | object | The send decision from the last build. An empty object before the first build. |
| ready | boolean | true only when the last build found no error and no hold. Use this for the send decision. |
| holds | array | Array of strings. Codes of findings with severity HOLD. Can be empty while ready is false. |
| warnings | array | Array of strings. Codes of findings with severity WARNING. Empty for real intake warnings today. |
| built_at | datetime | ISO 8601 time of the last build. Null before the first build. |
| build_stale* | boolean | true until the first build, and again after any edit. Submit requires false. |
| attachment | object | The linked attachment packet. Null when no packet is linked. |
| packet_id* | string | The pkt_ id used for packet parity.e.g. pkt_EXAMPLE0000000000001 |
| submission_id | string | The submission created by submit. Null until submit. |
| submitted_at | datetime | ISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit. |
Responses
{
"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
}{
"error": "DRAFT_NOT_FOUND",
"message": "draft not found",
"errors": [
{
"draft_id": "drf_mxc3nyetdazwd7w4ffgs"
}
],
"request_id": "evt_hn8t54zz10xprh6wzx2q"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key |
| 403 | PERMISSION_DENIED | Key lacks read |
| 404 | DRAFT_NOT_FOUND | No such draft for your seller, its office is no longer active or granted, or it belongs to the other mode |
| 429 | TOO_MANY_REQUESTS | Rate bucket empty or too many requests in flight |
| 503 | DRAFTS_UNAVAILABLE | Drafts are not configured on this gateway |
expires_at stays readable and usable.request_id in the body. Read the X-Request-Id header.Update a claim draft
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft to act on, a drf_ id. 1 to 64 characters.e.g. drf_fcc0f9mx74mmw7qh22za |
Request body
| Name | Type | Description |
|---|---|---|
| version* | integer | The draft's current version. At least 1. A stale value is refused, so a repeated edit never applies twice.e.g. 1 |
| fields | object | Replaces the whole ADA fields object (string values). ada and blank drafts only. |
| lines | array | Replaces the whole ADA lines array (array of objects). ada and blank drafts only. |
| payer | object | Replaces the whole ADA payer object. ada and blank drafts only. |
| tenant_claim_id | string | New claim id. At most 200 characters. e.g. your-claim-EXAMPLE-2 |
| attachment_packet_id | string | New pkt_ packet link. At most 64 characters. Not checked.e.g. pkt_krz5sag8qfzp63jvw8vw |
Request example
{
"version": 1,
"attachment_packet_id": "pkt_2g7hcpgd99p0p0hnhnaa"
}Response fields
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft id, drf_....e.g. drf_EXAMPLE0000000000001 |
| facility_id* | string | The office the draft belongs to. Controls who can see the draft. e.g. fac_EXAMPLE0000000000001 |
| source_kind* | string | What the draft was created from. Fixed at create. one of: x12 · json · ada · blank |
| door* | string | The 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* | string | Where the draft is in its lifecycle. one of: OPEN · READY · HELD · SUBMITTED · REJECTED |
| version* | integer | Optimistic lock. Every successful edit, build, and submit increments it. e.g. 1 |
| saved_at* | datetime | ISO 8601 time of the last change. |
| expires_at* | datetime | ISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today. |
| tenant_claim_id | string | Your id for the claim. Null when not set. |
| fields* | object | ADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts. |
| lines* | array | ADA service lines (array of objects) for ada and blank drafts. An empty array otherwise. |
| payer | object | ADA payer block for ada and blank drafts. Null otherwise. |
| findings* | array | Findings from the last build. Empty before the first build. |
| severity* | string | Finding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.one of: HOLD · error · warning · INFO |
| code* | string | Stable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING. |
| message* | string | Human-readable reason. Never contains a claim value. |
| item | string | ADA item number when known. Null for intake findings today. |
| path | string | Machine location of the finding, for example transaction[0]/claim[0]. Null when unknown. |
| verdict* | object | The send decision from the last build. An empty object before the first build. |
| ready | boolean | true only when the last build found no error and no hold. Use this for the send decision. |
| holds | array | Array of strings. Codes of findings with severity HOLD. Can be empty while ready is false. |
| warnings | array | Array of strings. Codes of findings with severity WARNING. Empty for real intake warnings today. |
| built_at | datetime | ISO 8601 time of the last build. Null before the first build. |
| build_stale* | boolean | true until the first build, and again after any edit. Submit requires false. |
| attachment | object | The linked attachment packet. Null when no packet is linked. |
| packet_id* | string | The pkt_ id used for packet parity.e.g. pkt_EXAMPLE0000000000001 |
| submission_id | string | The submission created by submit. Null until submit. |
| submitted_at | datetime | ISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit. |
Responses
{
"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
}{
"error": "DRAFT_VERSION_CONFLICT",
"message": "draft version conflict",
"errors": [
{
"draft_id": "drf_fcc0f9mx74mmw7qh22za",
"version": 2
}
],
"request_id": "evt_hf0fy0q80708t5nbpd7j"
}{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key |
| 403 | PERMISSION_DENIED | Key lacks submit |
| 404 | DRAFT_NOT_FOUND | No such draft for your seller, its office is no longer active or granted, or it belongs to the other mode |
| 409 | DRAFT_ALREADY_SUBMITTED | Draft is SUBMITTED |
| 409 | DRAFT_NOT_OPEN | Draft is REJECTED |
| 409 | DRAFT_VERSION_CONFLICT | version not current (errors[0].version is the current one), or the row changed during the update |
| 422 | DRAFT_NOT_EDITABLE | fields, lines, or payer sent for an x12 or json draft |
| 422 | INVALID_REQUEST | Body or parameters do not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate bucket empty or too many requests in flight |
| 503 | DRAFTS_UNAVAILABLE | Drafts are not configured on this gateway |
tenant_claim_id or attachment_packet_id once set. Sending null leaves them unchanged.Build a claim draft
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft to act on, a drf_ id. 1 to 64 characters.e.g. drf_wrf6c3tzz29wbw83zn4m |
Request body
| Name | Type | Description |
|---|---|---|
| version | integer | When 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
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft id, drf_....e.g. drf_EXAMPLE0000000000001 |
| facility_id* | string | The office the draft belongs to. Controls who can see the draft. e.g. fac_EXAMPLE0000000000001 |
| source_kind* | string | What the draft was created from. Fixed at create. one of: x12 · json · ada · blank |
| door* | string | The 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* | string | Where the draft is in its lifecycle. one of: OPEN · READY · HELD · SUBMITTED · REJECTED |
| version* | integer | Optimistic lock. Every successful edit, build, and submit increments it. e.g. 1 |
| saved_at* | datetime | ISO 8601 time of the last change. |
| expires_at* | datetime | ISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today. |
| tenant_claim_id | string | Your id for the claim. Null when not set. |
| fields* | object | ADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts. |
| lines* | array | ADA service lines (array of objects) for ada and blank drafts. An empty array otherwise. |
| payer | object | ADA payer block for ada and blank drafts. Null otherwise. |
| findings* | array | Findings from the last build. Empty before the first build. |
| severity* | string | Finding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.one of: HOLD · error · warning · INFO |
| code* | string | Stable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING. |
| message* | string | Human-readable reason. Never contains a claim value. |
| item | string | ADA item number when known. Null for intake findings today. |
| path | string | Machine location of the finding, for example transaction[0]/claim[0]. Null when unknown. |
| verdict* | object | The send decision from the last build. An empty object before the first build. |
| ready | boolean | true only when the last build found no error and no hold. Use this for the send decision. |
| holds | array | Array of strings. Codes of findings with severity HOLD. Can be empty while ready is false. |
| warnings | array | Array of strings. Codes of findings with severity WARNING. Empty for real intake warnings today. |
| built_at | datetime | ISO 8601 time of the last build. Null before the first build. |
| build_stale* | boolean | true until the first build, and again after any edit. Submit requires false. |
| attachment | object | The linked attachment packet. Null when no packet is linked. |
| packet_id* | string | The pkt_ id used for packet parity.e.g. pkt_EXAMPLE0000000000001 |
| submission_id | string | The submission created by submit. Null until submit. |
| submitted_at | datetime | ISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit. |
| rendered_x12 | string | The 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
{
"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~"
}{
"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
}{
"error": "DRAFT_VERSION_CONFLICT",
"message": "draft version conflict",
"errors": [
{
"draft_id": "drf_wrf6c3tzz29wbw83zn4m",
"version": 2
}
],
"request_id": "evt_7em48rrq8kp1mgddp4sk"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key |
| 403 | PERMISSION_DENIED | Key lacks submit |
| 404 | DRAFT_NOT_FOUND | No such draft for your seller, its office is no longer active or granted, or it belongs to the other mode |
| 400 | PAYER_NOT_FOUND | JSON draft: payer not in the registry |
| 400 | CLAIM_BUILD_FAILED | JSON draft: the claim model or builder refused the claim |
| 403 | FACILITY_NOT_GRANTED | The office (for JSON, json_claim.facilityId) is not granted |
| 403 | MODE_MISMATCH | Office binding does not accept the draft mode |
| 403 | BILLING_IDENTITY_MISMATCH | 2010AA NPI or TIN is not the office's |
| 409 | DRAFT_ALREADY_SUBMITTED | Draft is SUBMITTED |
| 409 | DRAFT_NOT_OPEN | Draft is REJECTED |
| 409 | DRAFT_VERSION_CONFLICT | version not current, or the row changed during the call |
| 409 | PAYER_NOT_ENROLLED | Payer not live for the office |
| 413 | PAYLOAD_TOO_LARGE | Stored X12 over 6 MB |
| 422 | CLAIM_SOURCE_UNREADABLE | Stored X12 missing or unparseable, or stored JSON no longer validates |
| 422 | ATTACHMENT_REFERENCE_AMBIGUOUS | JSON draft names more than one packet |
| 502 | CLAIM_BUILD_INVALID | JSON draft: builder output unparseable |
| 503 | CLAIM_BUILDER_UNAVAILABLE | JSON draft: builder not configured |
| 422 | INVALID_REQUEST | Body or parameters do not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate bucket empty or too many requests in flight |
| 429 | QUOTA_EXCEEDED | Seller's daily claim quota used up |
| 503 | DRAFTS_UNAVAILABLE | Drafts are not configured on this gateway |
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.READY and then fail at submit with 409 DUPLICATE_CONTENT.HELD with PACKET_PARITY.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.version. Submit with the version from this response.Submit a claim draft
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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 | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft to act on, a drf_ id. 1 to 64 characters.e.g. drf_2abp0mnygtbkc0z73y7j |
Request body
| Name | Type | Description |
|---|---|---|
| version | integer | When sent, must equal the draft's current version, which is the version returned by build.e.g. 3 |
Request example
{
"version": 2
}Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id, also in X-Request-Id.e.g. evt_EXAMPLE0000000000005 |
| submission_id* | string | The submission id, sub_....e.g. sub_EXAMPLE0000000000001 |
| state* | string | The 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* | boolean | true when this is a stored answer for the same key and content. |
| claims* | array | One entry per claim in the file. |
| claim_id* | string | The claim id, clm_....e.g. clm_EXAMPLE0000000000001 |
| tenant_claim_id* | string | Your original CLM01 as it appeared in the file. |
| pcn* | string | CLM01 as transmitted: <office prefix>-<CLM01>, tokenized when longer than 17 characters. |
| payer_id* | string | Loop 2010BB payer id from the file. |
| state* | string | Same as the submission state at intake. one of: QUEUED · HOLD · REJECTED_PRE_TRANSPORT |
| was_tokenized* | boolean | true when the PCN is a 12-character token instead of your CLM01. |
| lines* | array | Service lines. |
| line_control_number* | string | REF*6R, filled by the gateway when absent. |
| procedure_code* | string | CDT code. |
| validation* | object | Validation result for the file. |
| status* | string | REJECTED 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* | array | Every finding, errors and warnings. |
| code* | string | Finding code, for example PWK_PARITY or LICENSE_RECOMMENDED. |
| severity* | string | Finding severity. one of: error · warning |
| location* | string | Where in the file, for example transaction[0]/claim[1]. |
| message* | string | Never contains a value from your file. |
| value_redacted* | boolean | Always true. |
| acknowledgment_999* | string | The gateway's own 999 for this intake decision. Never transported. |
| hold_reason | string | Why the claim is held, for example PACKET_PARITY or IDENTITY_HIERARCHY. Null when not held. |
| dispatch | object | Transport estimate. Null for held and rejected submissions. |
| window_id | string | Always null at intake. Read the submission later to see the window. |
| expected_transport_by* | datetime | ISO 8601 estimate of when the next transport window picks the submission up. |
| draft_id* | string | The draft that was consumed. e.g. drf_EXAMPLE0000000000001 |
Responses
{
"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"
}{
"error": "DRAFT_NOT_READY",
"message": "draft not ready",
"errors": [
{
"draft_id": "drf_z7hk8ns6ghcv1nxxhrdr",
"state": "OPEN"
}
],
"request_id": "evt_8hkhd6rkgd0raa4q3tnp"
}{
"error": "DRAFT_ALREADY_SUBMITTED",
"message": "draft already submitted",
"errors": [
{
"draft_id": "drf_k7n9zma3cjmbfv81h7mq"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key |
| 403 | PERMISSION_DENIED | Key lacks submit |
| 404 | DRAFT_NOT_FOUND | No such draft visible to your key, or the intake write succeeded but the draft could not be updated |
| 409 | DRAFT_NOT_READY | Never built, build stale, verdict not ready, or an ada or blank draft |
| 409 | DUPLICATE_CONTENT | Identical content already submitted for this office under another key within 24 hours |
| 409 | IDEMPOTENCY_IN_PROGRESS | Same key still being written; Retry-After: 5 |
| 422 | IDEMPOTENCY_KEY_REUSED | Same key already used with different content within 24 hours |
| 400 | PAYER_NOT_FOUND | JSON draft: payer not in the registry |
| 400 | CLAIM_BUILD_FAILED | JSON draft: the claim model or builder refused the claim |
| 403 | FACILITY_NOT_GRANTED | The office (for JSON, json_claim.facilityId) is not granted |
| 403 | MODE_MISMATCH | Office binding does not accept the draft mode |
| 403 | BILLING_IDENTITY_MISMATCH | 2010AA NPI or TIN is not the office's |
| 409 | DRAFT_ALREADY_SUBMITTED | Draft is SUBMITTED |
| 409 | DRAFT_NOT_OPEN | Draft is REJECTED |
| 409 | DRAFT_VERSION_CONFLICT | version not current, or the row changed during the call |
| 409 | PAYER_NOT_ENROLLED | Payer not live for the office |
| 413 | PAYLOAD_TOO_LARGE | Stored X12 over 6 MB |
| 422 | CLAIM_SOURCE_UNREADABLE | Stored X12 missing or unparseable, or stored JSON no longer validates |
| 422 | ATTACHMENT_REFERENCE_AMBIGUOUS | JSON draft names more than one packet |
| 502 | CLAIM_BUILD_INVALID | JSON draft: builder output unparseable |
| 503 | CLAIM_BUILDER_UNAVAILABLE | JSON draft: builder not configured |
| 422 | INVALID_REQUEST | Body or parameters do not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate bucket empty or too many requests in flight |
| 429 | QUOTA_EXCEEDED | Seller's daily claim quota used up |
| 503 | DRAFTS_UNAVAILABLE | Drafts are not configured on this gateway |
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}.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.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.List claim 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id | string | Only drafts for this office. Must be granted to your key. At most 64 characters. e.g. fac_tycrfy0cs0qc7sq95eme |
| state | string | Only drafts in this state. REJECTED is refused. At most 16 characters.one of: OPEN · READY · HELD · SUBMITTED e.g. HELD |
| cursor | string | The next_cursor from the previous page. At most 200 characters. A cursor that does not parse returns the first page. |
| limit | integer | Page size, 1 to 200. Defaults to 50. e.g. 25 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| drafts* | array | The drafts on this page, each a full draft object. |
| draft_id* | string | The draft id, drf_....e.g. drf_EXAMPLE0000000000001 |
| facility_id* | string | The office the draft belongs to. Controls who can see the draft. e.g. fac_EXAMPLE0000000000001 |
| source_kind* | string | What the draft was created from. Fixed at create. one of: x12 · json · ada · blank |
| door* | string | The 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* | string | Where the draft is in its lifecycle. one of: OPEN · READY · HELD · SUBMITTED · REJECTED |
| version* | integer | Optimistic lock. Every successful edit, build, and submit increments it. e.g. 1 |
| saved_at* | datetime | ISO 8601 time of the last change. |
| expires_at* | datetime | ISO 8601 time 30 days after create. Edits do not extend it. Expiry is not enforced today. |
| tenant_claim_id | string | Your id for the claim. Null when not set. |
| fields* | object | ADA item fields keyed by item number, for ada and blank drafts. An empty object for x12 and json drafts. |
| lines* | array | ADA service lines (array of objects) for ada and blank drafts. An empty array otherwise. |
| payer | object | ADA payer block for ada and blank drafts. Null otherwise. |
| findings* | array | Findings from the last build. Empty before the first build. |
| severity* | string | Finding severity. Intake findings arrive lowercase (error, warning); compare case-insensitively.one of: HOLD · error · warning · INFO |
| code* | string | Stable finding code, for example PACKET_PARITY or ADA_NORMALIZER_PENDING. |
| message* | string | Human-readable reason. Never contains a claim value. |
| item | string | ADA item number when known. Null for intake findings today. |
| path | string | Machine location of the finding, for example transaction[0]/claim[0]. Null when unknown. |
| verdict* | object | The send decision from the last build. An empty object before the first build. |
| ready | boolean | true only when the last build found no error and no hold. Use this for the send decision. |
| holds | array | Array of strings. Codes of findings with severity HOLD. Can be empty while ready is false. |
| warnings | array | Array of strings. Codes of findings with severity WARNING. Empty for real intake warnings today. |
| built_at | datetime | ISO 8601 time of the last build. Null before the first build. |
| build_stale* | boolean | true until the first build, and again after any edit. Submit requires false. |
| attachment | object | The linked attachment packet. Null when no packet is linked. |
| packet_id* | string | The pkt_ id used for packet parity.e.g. pkt_EXAMPLE0000000000001 |
| submission_id | string | The submission created by submit. Null until submit. |
| submitted_at | datetime | ISO 8601 time of submit, set even when intake held or rejected the claim. Null until submit. |
| next_cursor | string | Opaque cursor for the next page. Do not parse it. Null on the last page. |
| has_more* | boolean | true when another page may exist. Keep following next_cursor until it is false. |
Responses
{
"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
}{
"error": "DRAFT_DENIED",
"message": "draft denied",
"errors": [
{
"reason": "unknown draft state 'NOPE'"
}
],
"request_id": "evt_7a3tc6rvn16mv8cg7jaj"
}{
"error": "DRAFT_ACCESS_DENIED",
"message": "draft access denied",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key |
| 400 | DRAFT_DENIED | Unknown state (including REJECTED), or a continuation cursor could not be produced |
| 403 | DRAFT_ACCESS_DENIED | facility_id is not active, not granted, or bound to the other mode |
| 403 | PERMISSION_DENIED | Key lacks read |
| 422 | INVALID_REQUEST | limit out of range or a parameter too long |
| 429 | TOO_MANY_REQUESTS | Rate bucket empty or too many requests in flight |
| 503 | DRAFTS_UNAVAILABLE | Drafts are not configured on this gateway |
limit drafts, or none, while has_more is true.request_id in the body. Read the X-Request-Id header.Delete a claim draft
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
HELDdraft stays on the ledger. Never contacts the clearinghouse. - In the dashboard
- API only
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft to act on, a drf_ id. 1 to 64 characters.e.g. drf_eh932r27rd5ybjdx85pc |
Request example
null
Responses
null
{
"error": "DRAFT_ALREADY_SUBMITTED",
"message": "draft already submitted",
"errors": [
{
"draft_id": "drf_k7n9zma3cjmbfv81h7mq"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "DRAFT_NOT_FOUND",
"message": "draft not found",
"errors": [
{
"draft_id": "drf_mxc3nyetdazwd7w4ffgs"
}
],
"request_id": "evt_xq3wg4817k1s1qxzhc0n"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown, or revoked key |
| 403 | PERMISSION_DENIED | Key lacks submit |
| 404 | DRAFT_NOT_FOUND | No such draft visible to your key, already deleted, or the draft is REJECTED |
| 409 | DRAFT_ALREADY_SUBMITTED | Draft is SUBMITTED |
| 429 | TOO_MANY_REQUESTS | Rate bucket empty or too many requests in flight |
| 503 | DRAFTS_UNAVAILABLE | Drafts are not configured on this gateway |
404 DRAFT_NOT_FOUND. REJECTED drafts cannot be deleted and also answer 404.Read draft 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The draft id, drf_....e.g. drf_EXAMPLE0000000000000001 |
Query parameters
| Name | Type | Description |
|---|---|---|
| version | integer | Pin one version. Returns its full snapshot body instead of the metadata list. e.g. 3 |
| before_version | integer | Cursor: only versions below this number. Mutually exclusive with version.e.g. 5 |
| limit | integer | 1–100, default 50. Ignored when version is set.e.g. 50 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| draft_id* | string | The draft id. e.g. drf_EXAMPLE0000000000000001 |
| current_version* | integer | The draft's latest version. e.g. 4 |
| coverage* | string | Always captured_versions_only — history begins at the first captured version, not necessarily creation.e.g. captured_versions_only |
| representation* | string | metadata for a list page, snapshot when version is pinned.one of: metadata · snapshot e.g. metadata |
| versions* | array | Version rows, newest first. |
| version* | integer | Version number. e.g. 4 |
| state* | string | Draft state at that version. e.g. EDITABLE |
| capture_kind* | string | What triggered the capture. e.g. edit |
| recorded_at* | datetime | When this version was captured. e.g. 2026-09-05T14:30:00+00:00 |
| snapshot* | object | Version 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* | boolean | True when more versions exist below this page. e.g. false |
| next_before_version | integer | Pass as before_version for the next page. Null when has_more is false.e.g. 3 |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks read or the draft's office |
| 404 | NOT_FOUND | no such draft or pinned version in your seller and grants |
| 422 | INVALID_REQUEST | limit out of range, version and before_version combined, or an invalid cursor |
| 503 | HISTORY_UNAVAILABLE | retained history is not configured on this deployment |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
has_more plus next_before_version is the forward pagination contract; keep passing before_version until has_more is false.Attachments
Upload claim documents, send them once to the payer's attachment door, and get back the attachment number your claim must carry.
| Packet API | Attachment workspace (drafts) | |
|---|---|---|
| Routes | POST /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 context | You 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. |
| Providers | Free 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. |
| Door | auto (default) or a door you name. | Always auto. |
| Checks before the send | Door 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 mode | Not 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 for | Systems 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. |
| Door | What it is | Built today | Reference returned |
|---|---|---|---|
network_A | The attachment network (NEA) | Yes, the only working door | nea_number |
network_B | A second attachment network | No, send answers 501 DOOR_NOT_BUILT | none |
x12_275 | X12 275 attachment transaction | No | none |
portal | Payer portal upload | No | none |
paper | No | none |
| Rule | At upload (POST /v1/attachments) | At send through the NEA door |
|---|---|---|
| Format | JSON body, one files[] item per file, bytes as strict base64 in content_base64. No multipart. | Same packet, no re-upload. |
| Media types | image/jpeg, image/png, image/tiff, image/gif, application/pdf, text/plain | image/jpeg only (DOOR_MEDIA_TYPE_NOT_ACCEPTED) |
| Files | 1 to 200 per packet | At most 127 (DOOR_FILE_COUNT_EXCEEDED) |
| Bytes per file | 1 byte to 50 MB decoded (FILE_EMPTY, FILE_TOO_LARGE) | 15 MB (DOOR_FILE_SIZE_EXCEEDED) |
| Bytes total | No packet total | 15 MB for the whole packet (DOOR_TOTAL_SIZE_EXCEEDED) |
| Kinds | radiograph, periodontal_chart, narrative, photo, eob, other | Must satisfy the payer's documentation rule (DOCUMENTATION_RULE_UNMET) |
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
SENTpacket carriesreference_kind: "nea_number"andreference, the attachment number. - JSON claims (
POST /v1/dental-claims/submissionand claim drafts): put the packet id inclaimInformation.claimSupplementalInformation.reportInformation.attachmentId, or the number inattachmentControlNumberif you already hold it, never both. - The 837D gets
PWK*<report type>*<transmission code>***AC*NEA<number>in Loop 2300 andNTE*ADD*NEA#<number>for any number no claim note already carries. - A packet that is not
SENTdoes not resolve and the claim is held withATTACHMENT_UNRESOLVED. A claim may name at most one packet (422ATTACHMENT_REFERENCE_AMBIGUOUS). - Raw X12 (
POST /v1/submissions): write PWK and NTE yourself.attachmentsmust equal the PWK NEA set (PWK_PARITY), andattachment_packet_idmust be aSENTpacket of the same office whose number is the only one in the PWK segments (PACKET_PARITY).
List 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office whose roster to return. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| providers* | array | Latest version per provider and role. |
| provider_id* | string | Provider id, prv_.... Shared by both roles of one NPI in one office. |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the profile. one of: test · production |
| role* | string | Role this version describes. one of: billing · treating |
| version* | integer | Version 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* | string | Only confirmed profiles can be selected on a draft.one of: observed · confirmed · inactive |
| identity* | object | Provider identity. |
| npi* | string | Provider NPI, 10 digits. |
| first_name* | string | First name. Empty string when not given. |
| last_name* | string | Last name. Empty string when not given. |
| organization_name* | string | Organization name. Empty string when not given. |
| tax_id | string | 9 digit tax id. Null when not given. |
| license_number | string | License number. Null when not given. |
| license_state | string | Two letter license state. Null when not given. |
| provenance* | object | Who supplied and who confirmed this version. |
| kind* | string | Where the profile came from. Always customer_api today.one of: customer_api |
| reference* | string | The source_reference you sent when saving. |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| observed_at* | datetime | When this version was saved. |
| confirmed_by | string | Key id or session that confirmed the profile. Null unless status is confirmed. |
| confirmed_at | datetime | When the profile was confirmed. Null unless status is confirmed. |
| active_from | date | First effective date. Null when open ended. |
| active_until | date | Last effective date. Null when open ended. |
| actor_id* | string | Key id or session principal that saved this version. |
| created_at* | datetime | When this version was written. |
Responses
{
"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"
}
]
}{
"error": "MODE_MISMATCH",
"message": "Office is unavailable in this environment.",
"errors": [],
"request_id": "evt_56mpph3tthwh06rf8vjw"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key has no access to the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office, or not granted |
| 422 | WORKSPACE_INVALID | Scope values could not be verified |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
sandbox office needs a test key and a production office needs a production key. The office must be ACTIVE.Save an attachment provider
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office the provider works for. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
Request body
| Name | Type | Description |
|---|---|---|
| provider_id | string | Existing prv_... to change. Must belong to the same NPI. Setting it needs admin. |
| expected_version | integer | Current latest version of this NPI and role, or 0 for a new role (the default). Above 0 needs admin.e.g. 0 |
| role* | string | The role this version describes. one of: billing · treating e.g. treating |
| status | string | Defaults to observed. confirmed and inactive need admin.one of: observed · confirmed · inactive |
| identity* | object | Provider identity. Unknown fields are refused. |
| npi* | string | Exactly 10 digits. The check digit is verified later, at draft validate. e.g. 1234567893 |
| first_name | string | At most 60 characters. Required for a treating provider at validate. |
| last_name | string | At most 60 characters. Required for a treating provider at validate. |
| organization_name | string | At most 120 characters. A billing provider needs this or a first and last name. |
| tax_id | string | Exactly 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_number | string | At most 30 characters, or null. |
| license_state | string | At most 2 characters, or null. |
| source_reference* | string | Where the identity came from, 1 to 200 characters. Stored as provenance.reference.e.g. roster-import-example |
| active_from | date | First effective date, YYYY-MM-DD, or null. |
| active_until | date | Last 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| provider* | object | The new version. When status is confirmed, provenance.confirmed_by is the saving key and confirmed_at is now. |
| provider_id* | string | Provider id, prv_.... Shared by both roles of one NPI in one office. |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the profile. one of: test · production |
| role* | string | Role this version describes. one of: billing · treating |
| version* | integer | Version 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* | string | Only confirmed profiles can be selected on a draft.one of: observed · confirmed · inactive |
| identity* | object | Provider identity. |
| npi* | string | Provider NPI, 10 digits. |
| first_name* | string | First name. Empty string when not given. |
| last_name* | string | Last name. Empty string when not given. |
| organization_name* | string | Organization name. Empty string when not given. |
| tax_id | string | 9 digit tax id. Null when not given. |
| license_number | string | License number. Null when not given. |
| license_state | string | Two letter license state. Null when not given. |
| provenance* | object | Who supplied and who confirmed this version. |
| kind* | string | Where the profile came from. Always customer_api today.one of: customer_api |
| reference* | string | The source_reference you sent when saving. |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| observed_at* | datetime | When this version was saved. |
| confirmed_by | string | Key id or session that confirmed the profile. Null unless status is confirmed. |
| confirmed_at | datetime | When the profile was confirmed. Null unless status is confirmed. |
| active_from | date | First effective date. Null when open ended. |
| active_until | date | Last effective date. Null when open ended. |
| actor_id* | string | Key id or session principal that saved this version. |
| created_at* | datetime | When this version was written. |
Responses
{
"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"
}
}{
"error": "PROVIDER_ADMIN_REQUIRED",
"message": "An organization administrator must confirm or retire provider profiles.",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "BILLING_IDENTITY_MISMATCH",
"message": "Billing identity must match the registered office.",
"errors": [],
"request_id": "evt_6q83h5553apd50cac4e4"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | PROVIDER_ADMIN_REQUIRED | Confirming, retiring or changing a profile without admin |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office in scope |
| 409 | WORKSPACE_VERSION_CONFLICT | expected_version is not the latest, or provider_id has a different NPI |
| 422 | BILLING_IDENTITY_MISMATCH | Billing NPI or tax id differs from the office |
| 422 | WORKSPACE_INVALID | Bad NPI shape or credential named fields in identity |
| 422 | INVALID_REQUEST | Schema failure, including a tax id that is not 9 digits |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
000000000 is refused as a placeholder, and draft validate needs a real 9 digit treating tax id.active_from or active_until date currently returns an unhandled 500 instead of a 422.Create an attachment draft
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_idis given, reads the packet and checks its file manifest. No vendor call. - In the dashboard
- Attachments > New attachment (Save context)
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office, 1 to 64 characters. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
| packet_id | string | A pkt_... from POST /v1/attachments in the same office, or null. |
| context | object | Claim context, at most 131,072 bytes encoded. No key named password, secret, token, authorization, api_key or signed_url at any depth. |
| payer_id | string | Payer primary id, exact. Required at validate. The payer must have attachment routing and an attachment network (NEA) payer id on file. |
| patient | object | The patient. Required at validate. |
| first_name* | string | Patient first name. |
| last_name* | string | Patient last name. |
| date_of_birth* | date | Patient date of birth, YYYY-MM-DD. |
| subscriber | object | The subscriber. Required at validate. |
| person* | object | Subscriber identity. |
| first_name* | string | Subscriber first name. |
| last_name* | string | Subscriber last name. |
| date_of_birth | date | Subscriber date of birth. Null when unknown. |
| member_id* | string | Subscriber member id. |
| group_number | string | Group number. Null when none. |
| relationship | string | Patient's relationship to the subscriber. Required at validate. one of: self · spouse · child · other |
| patient_is_subscriber | boolean | Must be true exactly when relationship is self. With self, patient name and date of birth must match the subscriber. |
| service_date_start | date | First date of service. Required at send, even for predeterminations. |
| service_date_end | date | Last date of service. Must not be before service_date_start. |
| predetermination | boolean | True for a predetermination. Defaults to false. |
| external_claim_reference | string | Your claim id. Sent as tenant_claim_id so events land on the matching claim. Null when none. |
| procedures | array | 1 to 50 procedures. Required at validate. |
| cdt_code* | string | CDT code, 2 to 8 characters. |
| procedure_date* | date | Procedure date inside the service date range. |
| teeth | array | Teeth treated. |
| tooth_number* | string | Tooth number, 1 to 4 characters. |
| surfaces | string | Surface letters from BDFILMO. |
| quadrant | string | Quadrant. Null when none. one of: UL · UR · LL · LR · UA · LA · FM |
| billing_provider_id | string | A prv_... with a billing role, or null. |
| billing_provider_version | integer | Billing provider version, at least 1. Must equal the latest version at validate. |
| treating_provider_id | string | A prv_... with a treating role, or null. |
| treating_provider_version | integer | Treating 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| draft* | object | The full draft. Contains PHI. |
| draft_id* | string | The draft, awd_.... |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the draft. one of: test · production |
| version* | integer | Current version. Every change adds 1. |
| state* | string | DRAFT 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* | datetime | When the draft was created. |
| updated_at* | datetime | When the draft last changed. |
| context* | object | Claim context as last saved. Contains PHI. |
| billing_provider_id | string | Selected billing provider. Null when none. |
| billing_provider_version | integer | Selected billing provider version. Null when none. |
| treating_provider_id | string | Selected treating provider. Null when none. |
| treating_provider_version | integer | Selected treating provider version. Null when none. |
| packet_id | string | Linked packet. Null when none. |
| provider_snapshots* | object | Empty until validate. Then billing and treating, each the provider version row that was reviewed. |
| context_snapshot* | object | Empty 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
{
"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": {}
}
}{
"error": "WORKSPACE_INVALID",
"message": "Workspace values could not be verified.",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office, or packet_id not in this office |
| 422 | PACKET_MANIFEST_INVALID | Linked packet has no files or files outside this office |
| 422 | WORKSPACE_INVALID | Context not an object, too large, or has a credential named key |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENTS_UNAVAILABLE | packet_id given and attachment service not wired |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
Cache-Control: private, no-store.sandbox office needs a test key and a production office needs a production key. The office must be ACTIVE.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.self, spouse or child (including other) is sent as self. Do not send dependent claims with relationship other until this is fixed.Retrieve an attachment draft
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The attachment draft id, awd_....e.g. awd_8f2nd1jewe9sstsgt4ez |
Query parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office the draft belongs to. 1 to 64 characters. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| draft* | object | The draft. Contains PHI. |
| draft_id* | string | The draft, awd_.... |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the draft. one of: test · production |
| version* | integer | Current version. Every change adds 1. |
| state* | string | DRAFT 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* | datetime | When the draft was created. |
| updated_at* | datetime | When the draft last changed. |
| context* | object | Claim context as last saved. Contains PHI. |
| billing_provider_id | string | Selected billing provider. Null when none. |
| billing_provider_version | integer | Selected billing provider version. Null when none. |
| treating_provider_id | string | Selected treating provider. Null when none. |
| treating_provider_version | integer | Selected treating provider version. Null when none. |
| packet_id | string | Linked packet. Null when none. |
| provider_snapshots* | object | Empty until validate. Then billing and treating, each the provider version row that was reviewed. |
| context_snapshot* | object | Empty 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
{
"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"
}
}
}
}{
"error": "NOT_FOUND",
"message": "No such workspace resource.",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office, or no such draft in this office and mode |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
Cache-Control: private, no-store.Update an attachment draft
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_idis given, reads the packet and checks its manifest. No vendor call. - In the dashboard
- Attachments > New attachment (Save context, Store documents)
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The attachment draft id, awd_....e.g. awd_8f2nd1jewe9sstsgt4ez |
Query parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office the draft belongs to. 1 to 64 characters. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The draft's current version, at least 1. Acts as an optimistic lock. e.g. 1 |
| packet_id | string | Link a packet from the same office, or null. |
| context | object | Replaces the whole context object. Same limits and shape as on create. Null leaves it unchanged. |
| billing_provider_id | string | A prv_... with a billing role, or null. |
| billing_provider_version | integer | Billing provider version, at least 1. |
| treating_provider_id | string | A prv_... with a treating role, or null. |
| treating_provider_version | integer | Treating provider version, at least 1. |
Request example
{
"expected_version": 1,
"packet_id": "pkt_20fng953rtp1hwc3zdcn"
}Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| draft* | object | The updated draft: version increased by 1, state DRAFT, empty snapshots. |
| draft_id* | string | The draft, awd_.... |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the draft. one of: test · production |
| version* | integer | Current version. Every change adds 1. |
| state* | string | DRAFT 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* | datetime | When the draft was created. |
| updated_at* | datetime | When the draft last changed. |
| context* | object | Claim context as last saved. Contains PHI. |
| billing_provider_id | string | Selected billing provider. Null when none. |
| billing_provider_version | integer | Selected billing provider version. Null when none. |
| treating_provider_id | string | Selected treating provider. Null when none. |
| treating_provider_version | integer | Selected treating provider version. Null when none. |
| packet_id | string | Linked packet. Null when none. |
| provider_snapshots* | object | Empty until validate. Then billing and treating, each the provider version row that was reviewed. |
| context_snapshot* | object | Empty 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
{
"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": {}
}
}{
"error": "WORKSPACE_VERSION_CONFLICT",
"message": "The workspace changed. Reload before saving.",
"errors": [],
"request_id": "evt_6q83h5553apd50cac4e4"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office, draft, or packet in this office |
| 409 | WORKSPACE_VERSION_CONFLICT | expected_version is stale, or the draft is FROZEN |
| 422 | PACKET_MANIFEST_INVALID | Linked packet manifest incomplete |
| 422 | WORKSPACE_INVALID | Context not an object, too large, or has a credential named key |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENTS_UNAVAILABLE | packet_id given and attachment service not wired |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
Cache-Control: private, no-store.Create an attachment packet
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 emitsattachment.packet_created. No vendor call. - In the dashboard
- Attachments > New attachment (Store documents)
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. File bytes are base64 inside the JSON body, not multipart.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office the files belong to, 1 to 64 characters. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
| files* | array | 1 to 200 files. |
| content_base64* | string | File bytes as strict base64, at least 4 characters. Decoded size 1 byte to 50 MB. e.g. /9j/4AAQSkZJRgABAQAAAQABAAD/2wBD |
| media_type* | string | The 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* | string | Document 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 |
| teeth | array | Array of strings. Teeth the file shows, at most 32, each 1 to 4 characters. e.g. ["30"] |
| orientation | string | Image orientation, or null. one of: left · right · none |
| taken_at | string | Image date, at most 32 characters. Passed to the attachment network as the document date. |
| filename | string | At 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| packet_id* | string | The new packet, pkt_.... |
| facility_id* | string | The office. |
| state* | string | Always OPEN on create.one of: OPEN |
| attachments* | array | One entry per stored file, in request order. |
| attachment_id* | string | The stored file, att_.... |
| sha256* | string | Hex SHA-256 digest of the decoded bytes. |
| size_bytes* | integer | Decoded size in bytes. |
| media_type* | string | Stored media type. one of: image/jpeg · image/png · image/tiff · image/gif · application/pdf · text/plain |
| kind* | string | Stored 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* | array | Array of strings. Teeth the file shows, as stored. |
| orientation | string | Stored orientation. Null when none was given. one of: left · right · none |
| deduplicated* | boolean | True when these exact bytes already existed in this office and the existing file was reused. |
Responses
{
"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
}
]
}{
"error": "FILE_TOO_LARGE",
"message": "file exceeds the archive ceiling",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "FACILITY_NOT_GRANTED",
"message": "this key's grants do not cover that facility",
"errors": [
{
"facility_id": "fac_w9qxypnbcw7b308jbbhh"
}
],
"request_id": "evt_8t458bs69g1h0bb2eajs"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover facility_id |
| 404 | NOT_FOUND | No such office for this seller |
| 422 | INVALID_REQUEST | Schema failure, bad base64, or media type, kind or orientation not accepted |
| 422 | FILE_EMPTY | A file decodes to zero bytes |
| 422 | FILE_TOO_LARGE | A file decodes to more than 50 MB |
| 422 | TOOTH_INVALID | A tooth token is blank or longer than 4 characters |
| 422 | ATTACHMENT_CONFLICT | The digest collides with a file outside your scope |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENTS_UNAVAILABLE | Attachment service not wired on this gateway |
ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.Retrieve an attachment packet
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| packet_id* | string | The packet id, pkt_.... 1 to 64 characters.e.g. pkt_qxwt9srf1300w6s4e6zt |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| packet_id* | string | The packet. |
| facility_id* | string | The office. |
| state* | string | Packet state. one of: OPEN · SENDING · SENT · AMBIGUOUS · FAILED |
| door | string | Door used. Null until attempted. |
| reference_kind | string | nea_number when sent through the attachment network (NEA). Null otherwise. |
| reference | string | The attachment number. Null unless SENT. |
| vendor_transaction_id | string | The id sent to the vendor (the packet id, up to 45 characters). Null until attempted. |
| payer_id | string | Payer named on the send. Null until sent. |
| submission_id | string | Submission named on the send. Null when none. |
| tenant_claim_id | string | Your claim id named on the send. Null when none. |
| created_at* | datetime | When the packet was created. |
| updated_at* | datetime | When the packet last changed. |
| attachments* | array | The packet's files. deduplicated is always false here. |
| attachment_id* | string | The stored file, att_.... |
| sha256* | string | Hex SHA-256 digest of the decoded bytes. |
| size_bytes* | integer | Decoded size in bytes. |
| media_type* | string | Stored media type. one of: image/jpeg · image/png · image/tiff · image/gif · application/pdf · text/plain |
| kind* | string | Stored 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* | array | Array of strings. Teeth the file shows, as stored. |
| orientation | string | Stored orientation. Null when none was given. one of: left · right · none |
| deduplicated* | boolean | True when these exact bytes already existed in this office and the existing file was reused. |
| sends* | array | Send attempts. At most one in practice. |
| send_id* | string | The send attempt, snd_.... |
| door* | string | Door attempted. one of: network_A · network_B · x12_275 · portal · paper |
| outcome* | string | Result of the one attempt. one of: OK · AMBIGUOUS · FAILED |
| reference_kind | string | nea_number when sent through the attachment network (NEA). Null otherwise. |
| reference | string | The attachment number. Null unless the outcome is OK. |
| vendor_transaction_id | string | Id the vendor knows the attempt by. Null when no vendor call was made. |
| error_class | string | Why the attempt did not end OK. Null on OK. |
| attempted_at* | datetime | When the attempt was made. |
Responses
{
"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": []
}{
"error": "NOT_FOUND",
"message": "no such packet",
"errors": [],
"request_id": "evt_jwamjpg2w054px4bmctc"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 404 | NOT_FOUND | No such packet, or outside the key's office grants |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENTS_UNAVAILABLE | Attachment service not wired on this gateway |
ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.Validate an attachment draft
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
READYversion with provider and context snapshots. No vendor call:vendor_verifiedis always false. - In the dashboard
- Attachments > New attachment (Check readiness)
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The attachment draft id, awd_....e.g. awd_8f2nd1jewe9sstsgt4ez |
Query parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office the draft belongs to. 1 to 64 characters. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The draft's current version, at least 1. e.g. 2 |
Request example
{
"expected_version": 2
}Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| draft* | object | The draft at its new version, state READY, snapshots filled. |
| draft_id* | string | The draft, awd_.... |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the draft. one of: test · production |
| version* | integer | Current version. Every change adds 1. |
| state* | string | DRAFT 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* | datetime | When the draft was created. |
| updated_at* | datetime | When the draft last changed. |
| context* | object | Claim context as last saved. Contains PHI. |
| billing_provider_id | string | Selected billing provider. Null when none. |
| billing_provider_version | integer | Selected billing provider version. Null when none. |
| treating_provider_id | string | Selected treating provider. Null when none. |
| treating_provider_version | integer | Selected treating provider version. Null when none. |
| packet_id | string | Linked packet. Null when none. |
| provider_snapshots* | object | Empty until validate. Then billing and treating, each the provider version row that was reviewed. |
| context_snapshot* | object | Empty 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* | object | Validation result. |
| valid* | boolean | Always true on 200. Failures come back as errors. |
| vendor_verified* | boolean | Always false. Nothing was checked with the vendor. |
Responses
{
"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
}
}{
"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"
}{
"error": "PACKET_REQUIRED",
"message": "Attach and review the actual packet before validation.",
"errors": [],
"request_id": "evt_61ae1hdfawbmybrxa762"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office, draft or packet |
| 409 | WORKSPACE_VERSION_CONFLICT | expected_version not current, or draft FROZEN |
| 409 | PACKET_ALREADY_ATTEMPTED | Linked packet is not OPEN |
| 409 | PROVIDER_VERSION_CONFLICT | Selected provider missing or not at the recorded version |
| 422 | PACKET_REQUIRED | No packet linked |
| 422 | PACKET_MANIFEST_INVALID | Packet manifest incomplete |
| 422 | PAYER_MAPPING_UNRESOLVED | Payer not found exactly, no NEA payer id, or no routing |
| 422 | BILLING_IDENTITY_MISMATCH | Billing NPI or tax id differs from the office |
| 422 | ATTACHMENT_CONTEXT_INCOMPLETE | Context rules failed; errors[] lists {code, path} |
| 422 | ATTACHMENT_CONTEXT_INVALID | Context has the wrong types or shape |
| 422 | NEA_CONTEXT_REQUIRED | No procedures, or treating provider has no valid tax id |
| 422 | NEA_CONTEXT_INVALID | Context does not fit the attachment network fields |
| 422 | PAYER_ROUTING_UNKNOWN | No attachment routing for the payer |
| 422 | FACILITY_NOT_ENROLLED_FOR_DOOR | Office not enrolled for any payer door |
| 422 | DOOR_FILE_COUNT_EXCEEDED | Too many files for the door |
| 422 | DOOR_FILE_SIZE_EXCEEDED | A file exceeds the door's per file limit |
| 422 | DOOR_MEDIA_TYPE_NOT_ACCEPTED | A file's media type is refused by the door |
| 422 | DOOR_TOTAL_SIZE_EXCEEDED | Packet exceeds the door's total size |
| 422 | DOCUMENTATION_RULE_UNMET | Payer rule for a CDT code needs kinds the packet lacks |
| 422 | WORKSPACE_INVALID | Snapshot refused |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENTS_UNAVAILABLE | Attachment service not wired on this gateway |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.ATTACHMENT_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.paper for an office without NEA registration). The send then consumes the packet and answers 501 DOOR_NOT_BUILT.Send an attachment draft
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.sentorattachment.failed. - In the dashboard
- Attachments > New attachment (Send attachment, once)
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| draft_id* | string | The attachment draft id, awd_....e.g. awd_8f2nd1jewe9sstsgt4ez |
Query parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office the draft belongs to. 1 to 64 characters. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The READY version returned by validate, at least 1.e.g. 3 |
Request example
{
"expected_version": 3
}Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| draft* | object | The frozen draft (state FROZEN). |
| draft_id* | string | The draft, awd_.... |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the draft. one of: test · production |
| version* | integer | Current version. Every change adds 1. |
| state* | string | DRAFT 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* | datetime | When the draft was created. |
| updated_at* | datetime | When the draft last changed. |
| context* | object | Claim context as last saved. Contains PHI. |
| billing_provider_id | string | Selected billing provider. Null when none. |
| billing_provider_version | integer | Selected billing provider version. Null when none. |
| treating_provider_id | string | Selected treating provider. Null when none. |
| treating_provider_version | integer | Selected treating provider version. Null when none. |
| packet_id | string | Linked packet. Null when none. |
| provider_snapshots* | object | Empty until validate. Then billing and treating, each the provider version row that was reviewed. |
| context_snapshot* | object | Empty 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* | object | Result of the one attempt. |
| packet_id* | string | The linked packet. |
| state* | string | Packet state after the attempt. one of: SENT · AMBIGUOUS · FAILED |
| reference | string | The attachment number on OK. Null otherwise. |
| reference_kind | string | nea_number on OK. Null otherwise. |
| outcome* | string | Result of the attempt. one of: OK · AMBIGUOUS · FAILED |
| send_id* | string | The send attempt, snd_.... |
Responses
{
"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"
}
}{
"error": "DRAFT_NOT_READY",
"message": "Review the current draft and linked packet before sending.",
"errors": [],
"request_id": "evt_61ae1hdfawbmybrxa762"
}{
"error": "ATTACHMENT_SEND_UNCERTAIN",
"message": "The draft is frozen and the send outcome requires reconciliation. Do not resend.",
"errors": [],
"request_id": "evt_9f5vkfv8a8rxpmm7k77z"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office, draft or packet |
| 409 | DRAFT_NOT_READY | Draft not READY, version not current, or no packet linked |
| 409 | BILLING_IDENTITY_CHANGED | Office billing NPI or TIN changed since validate |
| 409 | PACKET_CHANGED_OR_ATTEMPTED | Packet not OPEN, or its manifest changed |
| 409 | ATTACHMENT_AUTHORITY_CHANGED | Office, payer or routing changed since validate |
| 409 | ATTACHMENT_CONTEXT_CHANGED | Provider roster or context no longer passes |
| 409 | WORKSPACE_VERSION_CONFLICT | Draft changed during the freeze |
| 409 | ATTACHMENT_MANIFEST_CHANGED | Manifest changed between freeze and attempt; no vendor call |
| 409 | ATTACHMENT_ROUTING_CHANGED | Routing changed between freeze and attempt; no vendor call |
| 409 | PACKET_ALREADY_SENT | Packet claimed by another attempt at the last moment |
| 422 | NEA_CONTEXT_REQUIRED | As on validate |
| 422 | NEA_CONTEXT_INVALID | As on validate |
| 422 | PAYER_MAPPING_UNRESOLVED | As on validate |
| 422 | PAYER_ROUTING_UNKNOWN | As on validate |
| 422 | FACILITY_NOT_ENROLLED_FOR_DOOR | As on validate |
| 422 | DOOR_FILE_COUNT_EXCEEDED | Door limit |
| 422 | DOOR_FILE_SIZE_EXCEEDED | Door limit |
| 422 | DOOR_MEDIA_TYPE_NOT_ACCEPTED | Door limit |
| 422 | DOOR_TOTAL_SIZE_EXCEEDED | Door limit |
| 422 | DOCUMENTATION_RULE_UNMET | Payer documentation rule not met |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 501 | DOOR_NOT_BUILT | Router chose an unbuilt door; draft frozen, packet FAILED |
| 503 | ATTACHMENT_SEND_UNCERTAIN | Unexpected fault after the freeze; reconcile, do not resend |
| 503 | ATTACHMENT_DRAFT_SEND_UNAVAILABLE | Attachment service not wired; no vendor attempt |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
AMBIGUOUS outcome or a 503 ATTACHMENT_SEND_UNCERTAIN means the network may hold a record. Do not resend; reconcile first.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.error_class and vendor_transaction_id are not in this response. Read them with GET /v1/attachments/packets/{packet_id}.ATTACHMENT_DRAFT_SEND_UNAVAILABLE with no vendor attempt. Deployed wiring was not confirmed on 2026-09-14.Send an attachment packet
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.sentorattachment.failed. - In the dashboard
- Claims > Submit a claim > Draft flow > Attach (not working today)
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| packet_id* | string | The packet id, pkt_.... 1 to 64 characters.e.g. pkt_txz0tef8k7pvn9tvggbg |
Request body
| Name | Type | Description |
|---|---|---|
| payer_id* | string | Payer primary id, exact, 1 to 40 characters. Routing and the NEA payer id are looked up by primary id only. e.g. EXMPL |
| door | string | Door to use. Defaults to auto.one of: auto · network_A · network_B · x12_275 · portal · paper e.g. auto |
| submission_id | string | Links the send to a submission, at most 64 characters. Also used as the attachment network reference. |
| tenant_claim_id | string | Your claim id, at most 80 characters. Links events to the matching claim. |
| procedure_codes | array | Array of strings. CDT codes, at most 50, checked against payer documentation rules. Omitted means no documentation check. |
| claim_context | object | Claim 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* | string | 1 to 60 characters. |
| patient_last_name* | string | 1 to 60 characters. |
| patient_date_of_birth* | string | 8 to 10 characters, passed as sent. YYYYMMDD recommended. |
| insured_id* | string | Subscriber member id, 1 to 80 characters. |
| insured_first_name* | string | Subscriber first name, 1 to 60 characters. |
| insured_last_name* | string | Subscriber last name, 1 to 60 characters. |
| relationship | string | Patient'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_number | string | At most 50 characters. |
| procedures* | array | 1 to 50 procedures. |
| cdt_code* | string | 2 to 8 characters. |
| procedure_date* | string | 8 to 10 characters. YYYYMMDD recommended. |
| teeth | array | At most 32 teeth. |
| tooth_number* | string | 1 to 4 characters. |
| surfaces | string | At most 8 letters from BDFILMO. |
| quadrant | string | UL, UR, LL, LR, UA, LA or FM. Anything else is sent as none. |
| date_of_service_from* | string | 8 to 10 characters. |
| date_of_service_thru* | string | 8 to 10 characters. |
| is_predetermination | boolean | Defaults to false. |
| doctor_first_name | string | At most 60 characters. |
| doctor_last_name | string | At most 60 characters. |
| doctor_npi | string | At most 10 characters. Not check digit validated on this route. |
| doctor_license | string | At most 30 characters. |
| doctor_tax_id | string | At 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| packet_id* | string | The packet. |
| state* | string | Packet state after the attempt. one of: SENT · AMBIGUOUS · FAILED |
| door* | string | Door used. |
| door_requested* | string | auto or the door you named. |
| outcome* | string | Result of the one attempt. one of: OK · AMBIGUOUS · FAILED |
| reference_kind | string | nea_number on OK. Null otherwise. |
| reference | string | The attachment number on OK. Null otherwise. |
| vendor_transaction_id | string | Id the vendor knows the attempt by. Null when no vendor call was made. |
| error_class | string | Why the outcome is not OK, for example FacilityNotRegistered, ClaimContextRequired, PayerMasterIdUnknown, MediaTypeNotJpeg, NeaAuthError, CloseStatusNotSent or DoorNotBuilt. Null on OK. |
| send_id* | string | The send attempt, snd_.... |
Responses
{
"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"
}{
"error": "PACKET_ALREADY_SENT",
"message": "this packet was already sent or is being sent",
"errors": [
{
"packet_id": "pkt_20fng953rtp1hwc3zdcn"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 404 | NOT_FOUND | No such packet in scope, or its office is missing |
| 409 | PACKET_ALREADY_SENT | Packet is not OPEN, including FAILED and AMBIGUOUS |
| 422 | PAYER_ROUTING_UNKNOWN | No routing for payer_id; packet untouched |
| 422 | PAYER_DOOR_NOT_ACCEPTED | Named door not accepted by the payer; packet untouched |
| 422 | FACILITY_NOT_ENROLLED_FOR_DOOR | Office not enrolled for the door; packet untouched |
| 422 | ATTACHMENT_MISSING | A file of the packet is gone; packet untouched |
| 422 | DOOR_FILE_COUNT_EXCEEDED | Too many files for the door; packet untouched |
| 422 | DOOR_FILE_SIZE_EXCEEDED | A file exceeds the door limit; packet untouched |
| 422 | DOOR_MEDIA_TYPE_NOT_ACCEPTED | Media type refused by the door; packet untouched |
| 422 | DOOR_TOTAL_SIZE_EXCEEDED | Packet exceeds the door total; packet untouched |
| 422 | DOCUMENTATION_RULE_UNMET | Payer rule needs kinds the packet lacks; packet untouched |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 501 | DOOR_NOT_BUILT | Chosen door not built; packet now FAILED |
| 503 | ATTACHMENTS_UNAVAILABLE | Attachment service not wired on this gateway |
AMBIGUOUS packet. For FAILED, create a new packet (same bytes deduplicate) and send that.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.DOCUMENTATION_RULE_UNMET puts cdt_code, missing_kinds and guideline_url in errors[0]. Door limit errors name the limit in errors[0].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.other or any unlisted value is sent as self. Do not send dependent claims with relationship other until this is fixed.OPEN can surface as an unhandled 500 with no error code. Treat it as ambiguous and do not resend.ATTACHMENTS_UNAVAILABLE. Deployed wiring was not confirmed on 2026-09-14.List 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office to list, 1 to 64 characters. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
| limit | integer | Page size, 1 to 200. Defaults to 100. Use 100 or less (see note). e.g. 50 |
| cursor | string | The next_cursor from the previous page, at most 64 characters. |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header. |
| drafts* | array | Draft heads. |
| draft_id* | string | The draft, awd_.... |
| seller_id* | string | Your seller. |
| facility_id* | string | The office. |
| mode* | string | Environment of the draft. one of: test · production |
| version* | integer | Current version. |
| state* | string | Draft state. one of: DRAFT · READY · FROZEN |
| created_at* | datetime | When the draft was created. |
| updated_at* | datetime | When the draft last changed. |
| next_cursor | string | Pass as cursor for the next page. Null when there are no more pages. |
Responses
{
"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
}{
"error": "FACILITY_NOT_GRANTED",
"message": "this key's grants do not cover that facility",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, unknown or revoked key |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key grants do not cover the office |
| 403 | MODE_MISMATCH | Key mode does not match the office binding, or office not ACTIVE |
| 404 | NOT_FOUND | No such office in scope |
| 422 | WORKSPACE_INVALID | Scope values could not be verified |
| 422 | INVALID_REQUEST | Body, query or path fails the schema |
| 429 | TOO_MANY_REQUESTS | Too many requests in flight or rate bucket empty |
| 503 | ATTACHMENT_WORKSPACE_UNAVAILABLE | Workspace not wired on this gateway |
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.sandbox office needs a test key and a production office needs a production key. The office must be ACTIVE.Get 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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-Match | string | Send the ETag from a previous answer to get 304 Not Modified while the rules have not changed.e.g. W/"0f1a2b3c4d5e6f708192a3b4c5d6e7f8" |
Query parameters
| Name | Type | Description |
|---|---|---|
| payer_id* | string | The payer id you put on the claim, the same value your 837D carries. 1 to 20 characters. e.g. 60054 |
| procedure_codes* | string | Comma separated CDT codes, 1 to 50, each matching D plus four digits. Case insensitive.e.g. D4341,D1110 |
| plan_id | string | A specific plan, when the payer's requirements differ by plan. Opaque apl_ value from the plans route.e.g. apl_9e98e74a6a3c1f02 |
| facility_id | string | The 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
| Name | Type | Description |
|---|---|---|
| payer_id* | string | The payer id you asked about. |
| payer_name | string | Payer name when Claim House knows it. |
| electronic_attachments* | string | available or not_available. When not_available the claim still goes out; the attachment cannot. |
| plan_selection* | string | not_needed, required, chosen or remembered. required means a requested code differs by plan. |
| plan | object | The plan in scope, when one is. plan_id is opaque and stable. |
| narrative* | object | allowed and max_characters (2000). |
| payer_reference_number* | string | required, allowed or not_accepted. |
| file_limits* | object | Upload constraints: formats, max_images, max_total_bytes, orientation_values. |
| payer_notes | string | The payer's own note, unedited. Null when the payer provided none. |
| notes_vary_by_plan* | boolean | True when plans under this payer carry different notes or return policies. |
| return_policy | string | The payer's own return policy, unedited. |
| procedures* | array | One entry per requested code, in the order you sent them. |
| procedure_code* | string | The CDT code. |
| status* | string | required, 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. |
| description | string | The procedure description as the payer publishes it. |
| payer_says | string | The payer's own wording, unedited. Show this rather than paraphrasing. |
| documents | array | What the payer lists for this code. |
| kind* | string | Stable Claim House document kind, for example periodontal_chart, xray, narrative. Match on this, not on the label. |
| label* | string | Display text, for example X-ray. |
| format* | string | film or paper. |
| needs_date* | boolean | The image must carry a date. |
| needs_orientation* | boolean | The image must carry an orientation of left or right. Orientation is the mount side of the film, not the shape of the image. |
| options | array | Present only for depends_on_plan: one group per distinct answer, with documents, plan_count and up to five example plans. |
| message | string | Plain explanation for the non-required statuses. |
| source* | object | rules_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
{
"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"
}
}{
"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"
}
}{
"error": {
"code": "INVALID_PROCEDURE_CODE",
"message": "procedure_codes must be CDT codes such as D4341",
"errors": [
{
"field": "procedure_codes",
"value": "4341"
}
]
}
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 400 | INVALID_PROCEDURE_CODE | A code is malformed, the list is empty, or more than 50 were sent |
| 404 | PAYER_UNKNOWN | No payer source Claim House holds knows this identifier |
| 422 | PLAN_NOT_FOR_PAYER | plan_id does not belong to that payer |
| 403 | FACILITY_NOT_GRANTED | Your key has no access to facility_id |
| 503 | ATTACHMENT_RULES_UNAVAILABLE | No payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it |
ETag and Cache-Control: private, max-age=300. Payer rules change weekly at most, so conditional requests are cheap.Get attachment requirements in bulk
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| groups* | array | 1 to 500 groups. Each group takes payer_id, procedure_codes (1 to 50), and optionally plan_id and facility_id. |
| payer_id* | string | The claim payer id. |
| procedure_codes* | array | Array of CDT codes, at most 50. |
| plan_id | string | Opaque plan id. |
| facility_id | string | Office asking, for the remembered plan choice. |
Request example
{
"groups": [
{
"payer_id": "60054",
"procedure_codes": [
"D4341"
]
},
{
"payer_id": "CX014",
"procedure_codes": [
"D2950"
]
}
]
}Response fields
| Name | Type | Description |
|---|---|---|
| results* | array | One entry per group, in request order. |
| index* | integer | The index of the group this answers. |
| payer_id* | string | Echoed payer id. |
| result | object | The same shape as the single lookup. Null when the group failed. |
| error | object | code and message when that one group could not be answered. |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 422 | INVALID_REQUEST | More than 500 groups, or a group with no procedure codes |
| 503 | ATTACHMENT_RULES_UNAVAILABLE | No payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it |
Eligibility
Check a patient's dental coverage with the eligibility vendor, store the answer, and render it as a PDF.
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_idand returned as 502VENDOR_ERRORwith anerror_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 sameIdempotency-Keyreplays 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_returnednames each field the payer did not return, so absent is never read as zero.
| Mode | What happens |
|---|---|
standard (default) | The vendor's standard eligibility answer. |
enhanced | The 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. |
auto | enhanced 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_idwithcache.state: "hit"andcache.age_seconds, makes no vendor call, writes no new history row and does not record yourIdempotency-Key. - Procedure codes are not part of the cache key, so a cached answer may show a different
procedure_codeslist. SendCache-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 send | Result |
|---|---|
New Idempotency-Key | Cache hit if one matches, else one live vendor call. |
| Same key, same content | The recorded answer replayed with idempotent_replay: true, or the recorded failure as 502 VENDOR_ERROR. No vendor call. |
| Same key, different content | 422 IDEMPOTENCY_KEY_REUSED. Content is office, payer, subscriber and dependent identity, service date, procedure codes and resolved mode. |
| Same new key, two requests at once | Not locked. Both can reach the vendor and one answers 500. Check the history before sending again. |
| No key | 400 IDEMPOTENCY_KEY_REQUIRED. |
Check 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. Withinclude=pdfalso stores a PDF artifact. - In the dashboard
- Eligibility > New check
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Any 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-Control | string | Any value containing no-cache skips the cache and makes a live vendor call.e.g. no-cache |
| Content-Type* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Query parameters
| Name | Type | Description |
|---|---|---|
| mode | string | Which 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 |
| include | string | Comma list. pdf renders a PDF of the answer and adds artifact_id and pdf_filename to the response.e.g. pdf |
Request body
| Name | Type | Description |
|---|---|---|
| tradingPartnerServiceId* | string | The 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* | string | The office the check is for. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
| provider | object | Accepted for compatibility. The office's registered billing NPI and tax id are always what the payer receives. |
| npi | string | 10 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 |
| organizationName | string | At most 60 characters. Ignored. |
| firstName | string | At most 35 characters. Ignored. |
| lastName | string | At most 60 characters. Ignored. |
| taxId | string | 9 digits. Ignored. |
| subscriber* | object | The policy holder. |
| memberId* | string | Member 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* | string | First name, 1 to 35 characters. e.g. Sample |
| lastName* | string | Last name, 1 to 60 characters. e.g. Subscriber |
| dateOfBirth* | string | Date of birth as YYYYMMDD (not YYYY-MM-DD). Must be a real date not after the service date.e.g. 19800101 |
| groupNumber | string | Group number, at most 50 characters. |
| dependents | array | At most one item: the patient when the patient is not the subscriber. |
| memberId | string | Member id, at most 80 characters. Optional for a dependent. e.g. SYN000123456 |
| firstName* | string | First name, 1 to 35 characters. e.g. Sample |
| lastName* | string | Last name, 1 to 60 characters. e.g. Patient |
| dateOfBirth* | string | Date of birth as YYYYMMDD (not YYYY-MM-DD). Must be a real date not after the service date.e.g. 20150101 |
| groupNumber | string | Group number, at most 50 characters. |
| relationshipToSubscriberCode* | string | Two 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 |
| encounter | object | Service detail. |
| dateOfService | string | Service date as YYYYMMDD. Defaults to today (UTC).e.g. 20260914 |
| procedureCode | string | One CDT code, D plus 4 digits. Merged with procedureCodes.e.g. D1120 |
| procedureCodes | array | Array of strings. CDT codes for benefit detail. The merged list may hold at most 10 codes. e.g. ["D1120"] |
| serviceTypeCodes | array | Array of strings, at most 10. Accepted and ignored on the dental rail. |
| tenantReference | string | Your 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| idempotent_replay* | boolean | True when the recorded answer for this Idempotency-Key was replayed without a vendor call. |
| eligibility_id* | string | The check id, elg_.... A cache hit returns the original check's id.e.g. elg_EXAMPLE0000000000001 |
| facility_id* | string | The office the check was made for. e.g. fac_EXAMPLE0000000000001 |
| payer_id* | string | The payer id you sent. e.g. EXMPL |
| mode* | string | The resolved mode. auto resolves to one of these before the call.one of: standard · enhanced |
| relationship* | string | self for a subscriber check, else the dependent's relationship.one of: self · spouse · child · other |
| service_date* | date | ISO date the check was made for. |
| procedure_codes* | array | Array of strings. CDT codes sent on the original check. On a cache hit these are the original check's codes, not yours. |
| tenant_reference | string | Your reference. Null when you did not send one. |
| status* | string | Coverage status. UNKNOWN when not returned or not recognized.one of: ACTIVE · INACTIVE · UNKNOWN |
| coverage* | object | Coverage dates. |
| effective_date | date | Coverage start. Null when not returned. |
| termination_date | date | Coverage end. Null when not returned. |
| plan* | object | Plan detail. Enhanced mode adds number, issuing_state and benefit_period. |
| name | string | Plan name. Null when not returned. |
| type | string | Plan type. Null when not returned. |
| group_number | string | Group number. Null when not returned. |
| number | string | Enhanced mode only. Plan number. Null when not returned. |
| issuing_state | string | Enhanced mode only. Issuing state. Null when not returned. |
| benefit_period | string | Enhanced mode only. Benefit period. Null when not returned. |
| network_status | string | Network status for the office. Null when not returned. one of: IN_NETWORK · OUT_OF_NETWORK · BOTH |
| deductibles* | array | Deductible benefit lines. Every item field may be null. |
| section | string | Benefit section, for example deductibles. Null when not returned. |
| network | string | Network, for example in_network. Null when not returned. |
| category | string | Service category. Null when not returned. |
| service_type | string | Service type. Null when not returned. |
| coverage_level | string | Coverage level, for example individual or family. Null when not returned. |
| plan_period | string | Plan period, for example calendar_year. Null when not returned. |
| procedure_code | string | CDT code the line applies to. Null when not returned. |
| insurance_type | string | Insurance type. Null when not returned. |
| amount | string | Money amount as a decimal string. Null when not returned. |
| remaining | string | Remaining amount as a decimal string. Null when not returned. |
| used | string | Used amount as a decimal string. Null when not returned. |
| percent | string | Percent as a decimal string. Null when not returned. |
| description | string | Payer description. Null when not returned. |
| start_date | date | Start date. Null when not returned. |
| end_date | date | End date. Null when not returned. |
| maximums* | array | Annual or lifetime maximum lines, same item shape as deductibles. |
| coinsurance* | array | Coinsurance lines, same item shape as deductibles. |
| copayments* | array | Copayment lines, same item shape as deductibles. |
| not_covered* | array | Not covered lines, same item shape as deductibles. |
| frequency_limitations* | array | Frequency or quantity limits. |
| network | string | Network. Null when not returned. |
| category | string | Service category. Null when not returned. |
| service_type | string | Service type. Null when not returned. |
| procedure_code | string | CDT code. Null when not returned. |
| plan_period | string | Plan period. Null when not returned. |
| quantity | string | Allowed quantity. Null when not returned. |
| quantity_remaining | string | Quantity remaining. Null when not returned. |
| quantity_used | string | Quantity used. Null when not returned. |
| description | string | Payer description. Null when not returned. |
| start_date | date | Start date. Null when not returned. |
| end_date | date | End date. Null when not returned. |
| waiting_periods* | array | Waiting periods. |
| network | string | Network. Null when not returned. |
| category | string | Service category. Null when not returned. |
| service_type | string | Service type. Null when not returned. |
| procedure_code | string | CDT code. Null when not returned. |
| description | string | Payer description. Null when not returned. |
| start_date | date | Start date. Null when not returned. |
| end_date | date | End date. Null when not returned. |
| missing_tooth_clause | object | Missing tooth clause. Null when not returned. |
| text | string | Payer text. |
| applies | boolean | False only when the payer's text negates the clause. |
| downgrade_rules* | array | Downgrade rules. |
| text | string | Payer text. |
| category | string | Service category. |
| service_type | string | Service type. |
| procedure_code | string | CDT code. |
| tooth_history* | array | Prior services by tooth. |
| tooth | string | Tooth. |
| procedure_code | string | CDT code. |
| service_date | date | Date of service. |
| description | string | Payer description. |
| procedure_benefits | array | Enhanced 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_tiers | array | Enhanced mode only. Items carry network, status, description. |
| coordination_of_benefits | object | Enhanced mode only. Carries other_coverage and description. |
| eligibility_flags | array | Enhanced mode only. Items carry name, value, description. |
| payer_notes | array | Enhanced mode only. Array of strings. |
| disclaimers* | array | Array of strings. Payer disclaimers. |
| not_returned* | array | Array 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* | array | Array of strings. Parse warnings such as UNREADABLE_DATE or UNRECOGNIZED_COVERAGE_STATUS. They name the field, never the value. |
| source* | object | Where the answer came from. |
| vendor* | string | Eligibility vendor name. |
| transaction_id | string | Vendor transaction id. Null when none was read. |
| retrieved_at* | datetime | When the vendor call was made. |
| vendor_latency_ms* | integer | Vendor latency in milliseconds. |
| cache* | object | Whether this answer came from the cache. |
| state* | string | fresh for a live call, hit when served from cache.one of: fresh · hit |
| age_seconds | integer | Age of the cached answer. 0 when fresh. |
| artifact_id | string | Only with include=pdf. Pass it to GET /v1/artifacts/{artifact_id} for a download link. |
| pdf_filename | string | Only with include=pdf. Always elig_<eligibility_id>.pdf, never a patient name. |
Responses
{
"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"
}{
"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"
}{
"error": "IDEMPOTENCY_KEY_REUSED",
"message": "this Idempotency-Key was used with different content",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "INVALID_ELIGIBILITY_REQUEST",
"message": "invalid eligibility request",
"errors": [
{
"findings": [
"SUBSCRIBER_MEMBER_ID_REQUIRED"
]
}
],
"request_id": "evt_47qjfbxj2q2rzbvq8zes"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | IDEMPOTENCY_KEY_REQUIRED | Idempotency-Key header missing or blank |
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Office unknown to your account or not granted (never 404) |
| 403 | BILLING_IDENTITY_MISMATCH | provider.npi differs from the office billing NPI |
| 422 | INVALID_REQUEST | Path, query or body fails the schema |
| 422 | INVALID_ELIGIBILITY_REQUEST | Dependent relationship missing, or request rule findings in errors[0].findings |
| 422 | PAYER_NOT_SUPPORTED_FOR_ELIGIBILITY | Payer not found by primary or internal id, or has no eligibility mapping |
| 422 | IDEMPOTENCY_KEY_REUSED | Key used before with different content |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
| 502 | VENDOR_ERROR | Vendor did not answer usefully, or its answer could not be read |
| 503 | ELIGIBILITY_UNAVAILABLE | Eligibility is not configured on this gateway |
| 503 | ENHANCED_ELIGIBILITY_UNAVAILABLE | Resolved mode is enhanced and enhanced is disabled |
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.VENDOR_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}.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.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.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.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.Get an eligibility answer
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| eligibility_id* | string | The check id, elg_..., 1 to 64 characters.e.g. elg_hp130jp45188vw4wk5tz |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| outcome* | string | OK for an answer, VENDOR_ERROR for a recorded vendor failure.one of: OK · VENDOR_ERROR |
| error_class | string | Only on a failure record. The vendor error class, for example TIMEOUT. |
| eligibility_id* | string | The check id, elg_.... A cache hit returns the original check's id.e.g. elg_EXAMPLE0000000000001 |
| facility_id* | string | The office the check was made for. e.g. fac_EXAMPLE0000000000001 |
| payer_id* | string | The payer id you sent. e.g. EXMPL |
| mode* | string | The resolved mode. auto resolves to one of these before the call.one of: standard · enhanced |
| relationship* | string | self for a subscriber check, else the dependent's relationship.one of: self · spouse · child · other |
| service_date* | date | ISO date the check was made for. |
| procedure_codes* | array | Array of strings. CDT codes sent on the original check. On a cache hit these are the original check's codes, not yours. |
| tenant_reference | string | Your reference. Null when you did not send one. |
| status* | string | Coverage status. UNKNOWN when not returned or not recognized.one of: ACTIVE · INACTIVE · UNKNOWN |
| coverage* | object | Coverage dates. |
| effective_date | date | Coverage start. Null when not returned. |
| termination_date | date | Coverage end. Null when not returned. |
| plan* | object | Plan detail. Enhanced mode adds number, issuing_state and benefit_period. |
| name | string | Plan name. Null when not returned. |
| type | string | Plan type. Null when not returned. |
| group_number | string | Group number. Null when not returned. |
| number | string | Enhanced mode only. Plan number. Null when not returned. |
| issuing_state | string | Enhanced mode only. Issuing state. Null when not returned. |
| benefit_period | string | Enhanced mode only. Benefit period. Null when not returned. |
| network_status | string | Network status for the office. Null when not returned. one of: IN_NETWORK · OUT_OF_NETWORK · BOTH |
| deductibles* | array | Deductible benefit lines. Every item field may be null. |
| section | string | Benefit section, for example deductibles. Null when not returned. |
| network | string | Network, for example in_network. Null when not returned. |
| category | string | Service category. Null when not returned. |
| service_type | string | Service type. Null when not returned. |
| coverage_level | string | Coverage level, for example individual or family. Null when not returned. |
| plan_period | string | Plan period, for example calendar_year. Null when not returned. |
| procedure_code | string | CDT code the line applies to. Null when not returned. |
| insurance_type | string | Insurance type. Null when not returned. |
| amount | string | Money amount as a decimal string. Null when not returned. |
| remaining | string | Remaining amount as a decimal string. Null when not returned. |
| used | string | Used amount as a decimal string. Null when not returned. |
| percent | string | Percent as a decimal string. Null when not returned. |
| description | string | Payer description. Null when not returned. |
| start_date | date | Start date. Null when not returned. |
| end_date | date | End date. Null when not returned. |
| maximums* | array | Annual or lifetime maximum lines, same item shape as deductibles. |
| coinsurance* | array | Coinsurance lines, same item shape as deductibles. |
| copayments* | array | Copayment lines, same item shape as deductibles. |
| not_covered* | array | Not covered lines, same item shape as deductibles. |
| frequency_limitations* | array | Frequency or quantity limits. |
| network | string | Network. Null when not returned. |
| category | string | Service category. Null when not returned. |
| service_type | string | Service type. Null when not returned. |
| procedure_code | string | CDT code. Null when not returned. |
| plan_period | string | Plan period. Null when not returned. |
| quantity | string | Allowed quantity. Null when not returned. |
| quantity_remaining | string | Quantity remaining. Null when not returned. |
| quantity_used | string | Quantity used. Null when not returned. |
| description | string | Payer description. Null when not returned. |
| start_date | date | Start date. Null when not returned. |
| end_date | date | End date. Null when not returned. |
| waiting_periods* | array | Waiting periods. |
| network | string | Network. Null when not returned. |
| category | string | Service category. Null when not returned. |
| service_type | string | Service type. Null when not returned. |
| procedure_code | string | CDT code. Null when not returned. |
| description | string | Payer description. Null when not returned. |
| start_date | date | Start date. Null when not returned. |
| end_date | date | End date. Null when not returned. |
| missing_tooth_clause | object | Missing tooth clause. Null when not returned. |
| text | string | Payer text. |
| applies | boolean | False only when the payer's text negates the clause. |
| downgrade_rules* | array | Downgrade rules. |
| text | string | Payer text. |
| category | string | Service category. |
| service_type | string | Service type. |
| procedure_code | string | CDT code. |
| tooth_history* | array | Prior services by tooth. |
| tooth | string | Tooth. |
| procedure_code | string | CDT code. |
| service_date | date | Date of service. |
| description | string | Payer description. |
| procedure_benefits | array | Enhanced 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_tiers | array | Enhanced mode only. Items carry network, status, description. |
| coordination_of_benefits | object | Enhanced mode only. Carries other_coverage and description. |
| eligibility_flags | array | Enhanced mode only. Items carry name, value, description. |
| payer_notes | array | Enhanced mode only. Array of strings. |
| disclaimers* | array | Array of strings. Payer disclaimers. |
| not_returned* | array | Array 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* | array | Array of strings. Parse warnings such as UNREADABLE_DATE or UNRECOGNIZED_COVERAGE_STATUS. They name the field, never the value. |
| source* | object | Where the answer came from. |
| vendor* | string | Eligibility vendor name. |
| transaction_id | string | Vendor transaction id. Null when none was read. |
| retrieved_at* | datetime | When the vendor call was made. |
| vendor_latency_ms* | integer | Vendor latency in milliseconds. |
| cache* | object | Whether this answer came from the cache. |
| state* | string | fresh for a live call, hit when served from cache.one of: fresh · hit |
| age_seconds | integer | Age of the cached answer. 0 when fresh. |
Responses
{
"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"
}{
"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
}{
"error": "NOT_FOUND",
"message": "no such eligibility result",
"errors": [],
"request_id": "evt_wmkpgd7r9t71sskpmz6q"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 404 | NOT_FOUND | No such check, or outside the key's office grants |
| 422 | INVALID_REQUEST | Path, query or body fails the schema |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
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.Create an eligibility 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| eligibility_id* | string | The check id, elg_..., 1 to 64 characters. The check must have succeeded.e.g. elg_hp130jp45188vw4wk5tz |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| eligibility_id* | string | The check. |
| artifact_id* | string | The stored PDF, art_.... Pass to GET /v1/artifacts/{artifact_id} for a download link. |
| filename* | string | Always elig_<eligibility_id>.pdf. No patient name. |
| bytes* | integer | PDF size in bytes. |
Responses
{
"request_id": "evt_9vw2py8b5f1xw79dykcg",
"eligibility_id": "elg_hp130jp45188vw4wk5tz",
"artifact_id": "art_7cx8qyshggtk72qstpjz",
"filename": "elig_elg_c04ysmvj0h58513dt65t.pdf",
"bytes": 7820
}{
"error": "INVALID_ELIGIBILITY_REQUEST",
"message": "PDF requires a successful eligibility result",
"errors": [
{
"eligibility_id": "elg_wddgq19nhagdyseyyhte"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "NOT_FOUND",
"message": "no such eligibility result",
"errors": [
{
"eligibility_id": "elg_5zgacj2z460gmw1ebbq7"
}
],
"request_id": "evt_y496y9sv80mh6vq5a73g"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | The check's office is not granted to your key |
| 404 | NOT_FOUND | No such check for your account |
| 422 | INVALID_REQUEST | Path, query or body fails the schema |
| 422 | INVALID_ELIGIBILITY_REQUEST | The check was a vendor failure or has no archived answer |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
| 503 | ELIGIBILITY_UNAVAILABLE | Eligibility is not configured on this gateway |
ELIGIBILITY_UNAVAILABLE when eligibility is not configured on the gateway, and the deployed configuration was not confirmed when these docs were written.List an office's eligibility checks
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office, 1 to 64 characters. Must be granted to your key. e.g. fac_q5s09nzww25ysd5a2f3g |
Query parameters
| Name | Type | Description |
|---|---|---|
| since | datetime | Only checks retrieved at or after this ISO 8601 time. e.g. 2026-09-01T00:00:00Z |
| limit | integer | Rows to return, 1 to 500. Default 100. e.g. 2 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| facility_id* | string | The office. |
| results* | array | Checks, newest first. |
| eligibility_id* | string | The check id, elg_.... |
| facility_id* | string | The office. |
| payer_id* | string | The payer id sent. |
| relationship* | string | Who was checked. one of: self · spouse · child · other |
| service_date* | date | Service date. |
| status* | string | Coverage status. one of: ACTIVE · INACTIVE · UNKNOWN |
| outcome* | string | OK or VENDOR_ERROR.one of: OK · VENDOR_ERROR |
| error_class | string | Vendor error class on failures. Null on success. |
| vendor* | string | Eligibility vendor name. |
| vendor_transaction_id | string | Vendor transaction id. Null when none was read. |
| vendor_latency_ms* | integer | Vendor latency in milliseconds. |
| tenant_reference | string | Your reference. Null when not sent. |
| retrieved_at* | datetime | When the vendor call was made. |
Responses
{
"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"
}
]
}{
"error": "FACILITY_NOT_GRANTED",
"message": "this key's grants do not cover that facility",
"errors": [
{
"facility_id": "fac_w9qxypnbcw7b308jbbhh"
}
],
"request_id": "evt_raxyv96xhdgz5s57d4bd"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Office not covered by the key's grants |
| 404 | NOT_FOUND | No such office for your account |
| 422 | INVALID_REQUEST | Path, query or body fails the schema |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
mode is not on list rows. Read a single check for it.since. Narrow since to walk older history.Poll an eligibility 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| eligibility_id* | string | The eligibility id returned by POST /v1/eligibility.e.g. elg_EXAMPLE0000000000000001 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| eligibility_id* | string | The eligibility id. e.g. elg_EXAMPLE0000000000000001 |
| facility_id* | string | The office the check ran under. e.g. fac_EXAMPLE0000000000000001 |
| environment* | string | The key mode the operation ran in, test or live.one of: test · live e.g. test |
| state* | string | Operation state. e.g. ANSWERED |
| response_mode* | string | How the answer was delivered. e.g. realtime |
| result_id | string | The retained result id once answered. Null while in flight. e.g. res_EXAMPLE0000000000000001 |
| request_archived* | boolean | The exact outbound request is durably archived. e.g. true |
| response_archived* | boolean | The raw payer response is durably archived. e.g. true |
| object | Latest PDF artifact metadata when one has been generated. e.g. {"artifact_id":"art_EXAMPLE0000000000000001","status":"ready"} |
Responses
{
"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"
}
}{
"error": "NOT_FOUND",
"message": "no such eligibility operation",
"errors": [],
"request_id": "evt_ang0n1gexynjw0dmfh8v"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks read or the operation's office |
| 404 | NOT_FOUND | no such eligibility operation in your seller, grants and key mode |
| 503 | ELIGIBILITY_UNAVAILABLE | the eligibility rail is not configured on this gateway |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
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_idwithGET /v1/artifacts/{artifact_id}.
routed_by | Matched when |
|---|---|
PCN | CLP01 equals the patient control number Claim House put on the claim. |
PAYER_CLAIM_NUMBER | CLP07 equals a payer claim number already known for the claim (from a 277). |
ORIGINAL_REFERENCE | REF*F8 equals a payer claim number or patient control number. |
| CLP02 | event_kind |
|---|---|
1, 2, 3, 19, 20, 21 | claim.paid |
4 | claim.denied |
22 | claim.reversed |
| Any other code | null, the claim state does not change |
List 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id | string | Only count and list payment lines for this office, at most 64 characters. Must be granted to your key. e.g. fac_d6h4k8qph9qqvx7jwkh8 |
| since | datetime | Only 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 |
| cursor | string | The next_cursor from the previous page, at most 512 characters. Keep other filters identical while paging. |
| limit | integer | Page size, 1 to 500. Default 100. e.g. 1 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| remittances* | array | Remittance rows, ordered by received_at then remittance_id, descending. |
| remittance_id* | string | Remittance id, rem_.... One 835 file. |
| payer_name* | string | Payer name from the 835. Empty string when absent. |
| payer_id* | string | Payer identifier from the 835. Empty string when absent. |
| trace_number* | string | Check or EFT trace number (TRN02). Use it to match a bank deposit. |
| payment_method* | string | BPR04 payment method code as sent, for example ACH, CHK or NON. |
| transaction_handling_code* | string | BPR01 code as sent, for example C (payment with remittance) or I (remittance information only). |
| payment_date | date | BPR16 effective payment date. Null when absent. |
| received_at* | datetime | When Claim House received the 835 file. |
| claim_count* | integer | Number 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* | string | Sum of paid_amount over those lines, as a two decimal string. |
| artifact_id | string | Artifact id for the raw 835. When your lines span several offices, one office's copy is returned. |
| next_cursor | string | Opaque cursor for the next page. Null on the last page. |
| has_more* | boolean | True exactly when next_cursor is not null. |
Responses
{
"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
}{
"error": "INVALID_CURSOR",
"message": "cursor is not readable",
"errors": [],
"request_id": "evt_6yzmma09tbqtvyw60mtk"
}{
"error": "FACILITY_NOT_GRANTED",
"message": "this key's grants do not cover that facility",
"errors": [
{
"facility_id": "fac_tycrfy0cs0qc7sq95eme"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_CURSOR | cursor was not issued by the gateway |
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 403 | FACILITY_NOT_GRANTED | Key has no office grant, or facility_id is outside its grants |
| 422 | INVALID_REQUEST | Bad since, or limit outside 1 to 500 |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
since.Get a remittance
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| remittance_id* | string | Remittance id, rem_..., 1 to 64 characters.e.g. rem_6e65br54q5wnygrtqcrq |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| remittance_id* | string | Remittance id, rem_.... One 835 file. |
| payer_name* | string | Payer name from the 835. Empty string when absent. |
| payer_id* | string | Payer identifier from the 835. Empty string when absent. |
| trace_number* | string | Check or EFT trace number (TRN02). Use it to match a bank deposit. |
| payment_method* | string | BPR04 payment method code as sent, for example ACH, CHK or NON. |
| transaction_handling_code* | string | BPR01 code as sent, for example C (payment with remittance) or I (remittance information only). |
| payment_date | date | BPR16 effective payment date. Null when absent. |
| received_at* | datetime | When Claim House received the 835 file. |
| claim_count* | integer | Number 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* | string | Sum of paid_amount over those lines, as a two decimal string. |
| artifact_id | string | Artifact id for the raw 835. When your lines span several offices, one office's copy is returned. |
| production_date | date | 835 production date. Null when absent. |
| claims* | array | Your payment lines, in file order, at most 500. |
| payment_id* | string | Payment id, pay_.... |
| remittance_id* | string | Parent remittance. |
| claim_id* | string | The claim this line was matched to. |
| facility_id* | string | Office of that claim. |
| submission_id* | string | Submission of that claim. |
| tenant_claim_id | string | Your claim id. Null when not set. |
| clp_position* | integer | 1 based position of this CLP in the file. |
| patient_control_number* | string | CLP01 as the payer returned it. |
| routed_by* | string | How the line was matched to your claim. one of: PCN · PAYER_CLAIM_NUMBER · ORIGINAL_REFERENCE |
| claim_status_code* | string | CLP02 claim status code. |
| payer_claim_number | string | CLP07 payer claim control number. Null when empty. |
| original_reference | string | REF*F8 original reference. Null when empty. |
| filing_indicator* | string | CLP06 claim filing indicator. |
| charged_amount* | string | CLP03 total charge. |
| paid_amount* | string | CLP04 payment amount. Can be negative on a reversal. |
| patient_responsibility* | string | CLP05 patient responsibility. |
| event_kind | string | The claim event this line raised. Null for a CLP02 that raises no event. one of: claim.paid · claim.denied · claim.reversed |
| artifact_id | string | Your office's copy of the raw 835. |
| recorded_at* | datetime | When Claim House recorded the line. |
| service_lines* | array | Service line (SVC) detail. Currently always empty, see notes. |
| line_number* | integer | Line position. |
| procedure_code* | string | CDT code. |
| procedure_modifier* | string | Modifier. May be empty. |
| charged_amount* | string | Line charge. |
| paid_amount* | string | Line payment. |
| service_date_from* | string | Line service date as parsed. |
| service_date_to* | string | Line end date as parsed. |
| line_control_number* | string | REF*6R line item control number. |
| adjustments* | array | Line level adjustments, same shape as adjustments without adjustment_id. |
| adjustments* | array | Every adjustment for this payment: claim level first, then line level, in file order. |
| adjustment_id* | string | Adjustment id, adj_.... Not present on items inside service_lines[].adjustments. |
| level* | string | claim for a CAS before the first SVC, line for a CAS under an SVC.one of: claim · line |
| line_number | integer | Service line for a line adjustment. Null for claim. |
| group_code* | string | CAS01 group code, for example CO (contractual), PR (patient responsibility), OA or PI. |
| reason_code* | string | CAS02 claim adjustment reason code. |
| amount* | string | Adjustment amount as a two decimal string. |
| quantity | string | Adjustment quantity. Null when absent. |
| remark_codes* | array | Array of strings. Remittance advice remark codes attached to the adjustment. |
Responses
{
"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"
]
}
]
}
]
}{
"error": "NOT_FOUND",
"message": "no such remittance",
"errors": [],
"request_id": "evt_ykazcjzw21epeybbysyw"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks the route's permission |
| 404 | NOT_FOUND | No remittance with that id containing your lines within your grants |
| 422 | INVALID_REQUEST | Path id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
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.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.
| Route | Accepts |
|---|---|
GET /v1/payers/{payer_id} | Primary id, internal id or alias |
GET /v1/payers/{payer_id}/doors | Primary id only |
POST /v1/eligibility | Primary id or internal id, not aliases |
| Attachment packet send and drafts | Primary id only |
POST /v1/dental-claims/submission | Primary id, internal id or alias |
primaryPayerId everywhere unless a route says otherwise. Store payerId as your database key: it never changes.transactionSupportlevels areSUPPORTED,ENROLLMENT_REQUIREDandNOT_SUPPORTED.claimPaymentisENROLLMENT_REQUIREDwhenever offered, since an 835 always needs ERA enrollment.eligibilityCheck: SUPPORTEDdoes not guarantee a check works: the payer also needs an eligibility mapping, or the check answers 422PAYER_NOT_SUPPORTED_FOR_ELIGIBILITY.unsolicitedClaimAttachment: SUPPORTEDdoes not guarantee anetwork_Asend works: it also needsnetwork_Ain the door order and a network registration that no payer route exposes.
DOCUMENTATION_RULE_UNMET.Search payers
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| q* | string | Search text, 2 to 120 characters. e.g. dental |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| payers* | array | Matching payer records. Empty when nothing matches (never 404). |
| payerId* | string | Immutable internal id. Use it as your database key. |
| displayName* | string | Name to show. |
| primaryPayerId* | string | The id to show and to send on claims, eligibility and attachments. |
| aliases* | array | Array of strings. Other ids that resolve to this payer. |
| names* | array | Array of strings. Known names. Falls back to [displayName]. |
| transactionSupport* | object | Support level per transaction. |
| eligibilityCheck* | string | Eligibility checks. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| claimStatus* | string | Claim status. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| dentalClaimSubmission* | string | Dental claim submission. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| claimPayment* | string | 835 remittances. Never SUPPORTED unless set explicitly, since an 835 needs ERA enrollment.one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| unsolicitedClaimAttachment* | string | Attachments sent after the claim. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| coverageTypes* | array | Array of strings. Falls back to ["dental"]. |
| attachmentDoors* | array | Array of strings. The payer's attachment door order, for example ["network_A", "paper"]. Empty when the payer has no routing. |
| eraEnrollmentCode | string | ERA enrollment code. Null when none. |
| paperEobShutoff | boolean | Whether paper EOBs stop after ERA enrollment. Null when unknown. |
Responses
{
"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
}
]
}{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 422 | INVALID_REQUEST | q missing, shorter than 2 or longer than 120 |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
% and _ in q act as wildcards.List 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| format | string | Response format. Default json.one of: json · csv e.g. json |
| pageToken | string | The nextPageToken from the previous page, at most 200 characters. Ignored for CSV. |
| pageSize | integer | Payers per page, 10 to 50. Default 50. Ignored for CSV. e.g. 10 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| payers* | array | Payer records for this page. |
| payerId* | string | Immutable internal id. Use it as your database key. |
| displayName* | string | Name to show. |
| primaryPayerId* | string | The id to show and to send on claims, eligibility and attachments. |
| aliases* | array | Array of strings. Other ids that resolve to this payer. |
| names* | array | Array of strings. Known names. Falls back to [displayName]. |
| transactionSupport* | object | Support level per transaction. |
| eligibilityCheck* | string | Eligibility checks. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| claimStatus* | string | Claim status. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| dentalClaimSubmission* | string | Dental claim submission. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| claimPayment* | string | 835 remittances. Never SUPPORTED unless set explicitly, since an 835 needs ERA enrollment.one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| unsolicitedClaimAttachment* | string | Attachments sent after the claim. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| coverageTypes* | array | Array of strings. Falls back to ["dental"]. |
| attachmentDoors* | array | Array of strings. The payer's attachment door order, for example ["network_A", "paper"]. Empty when the payer has no routing. |
| eraEnrollmentCode | string | ERA enrollment code. Null when none. |
| paperEobShutoff | boolean | Whether paper EOBs stop after ERA enrollment. Null when unknown. |
| nextPageToken | string | Token for the next page. Null on the last page. |
Responses
{
"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
}{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 422 | INVALID_REQUEST | pageSize outside 10 to 50, unknown format, or pageToken too long |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
request_id field. The X-Request-Id header is still set.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.Get a payer
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| payer_id* | string | Primary id, internal id or alias, 1 to 80 characters. Exact match. e.g. TESTPAYER1 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| payerId* | string | Immutable internal id. Use it as your database key. |
| displayName* | string | Name to show. |
| primaryPayerId* | string | The id to show and to send on claims, eligibility and attachments. |
| aliases* | array | Array of strings. Other ids that resolve to this payer. |
| names* | array | Array of strings. Known names. Falls back to [displayName]. |
| transactionSupport* | object | Support level per transaction. |
| eligibilityCheck* | string | Eligibility checks. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| claimStatus* | string | Claim status. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| dentalClaimSubmission* | string | Dental claim submission. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| claimPayment* | string | 835 remittances. Never SUPPORTED unless set explicitly, since an 835 needs ERA enrollment.one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| unsolicitedClaimAttachment* | string | Attachments sent after the claim. one of: SUPPORTED · ENROLLMENT_REQUIRED · NOT_SUPPORTED |
| coverageTypes* | array | Array of strings. Falls back to ["dental"]. |
| attachmentDoors* | array | Array of strings. The payer's attachment door order, for example ["network_A", "paper"]. Empty when the payer has no routing. |
| eraEnrollmentCode | string | ERA enrollment code. Null when none. |
| paperEobShutoff | boolean | Whether paper EOBs stop after ERA enrollment. Null when unknown. |
Responses
{
"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
}{
"error": "NOT_FOUND",
"message": "no such payer",
"errors": [],
"request_id": "evt_g80nnx0fjhj0d0ng5adn"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 404 | NOT_FOUND | No payer has that primary id, internal id or alias |
| 422 | INVALID_REQUEST | Path, query or body fails the schema |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
request_id field. The X-Request-Id header is still set.Get a payer's attachment 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| payer_id* | string | Primary payer id only, 1 to 40 characters, exact. Aliases and internal ids answer 404. e.g. TESTPAYER1 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id. |
| payer_id* | string | Primary payer id. |
| display_name* | string | Registry name. |
| networks* | array | Array of strings. Attachment networks listed on the registry row. |
| routing_known* | boolean | False when the payer has no routing. Every attachment send for this payer then fails with 422 PAYER_ROUTING_UNKNOWN. |
| door_order* | array | Array of strings. Doors in the payer's order. Empty without routing. |
| doors* | array | Array 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* | array | One entry per door_order entry, in the same order. |
| door* | string | The door. |
| max_files | integer | Maximum file count. Null means no check. Always 127 for network_A. |
| max_file_bytes | integer | Maximum size of one file. Null means no check. Always 15728640 for network_A. |
| max_total_bytes | integer | Maximum total size. Null means no check. Always 15728640 for network_A. |
| media_types* | array | Array of strings. Allowed media types. Empty means any stored type. Always ["image/jpeg"] for network_A. |
| portal_platform | string | Portal platform name when the payer uses one. Null otherwise. |
| portal_after_claim | boolean | Whether the portal accepts attachments only after the claim exists. Null when unknown. |
| evidence_grade | string | Confidence grade of the routing research. Null when not graded. |
| source_url | string | Where the routing was sourced. Null when not recorded. |
Responses
{
"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
}{
"error": "NOT_FOUND",
"message": "no such payer",
"errors": [],
"request_id": "evt_4fcz5qg00vmpw8b3g8zg"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 404 | NOT_FOUND | No registry row with that primary id |
| 422 | INVALID_REQUEST | Path, query or body fails the schema |
| 429 | TOO_MANY_REQUESTS | More than 20 in flight or rate bucket empty |
Get a payer's 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| payer_id* | string | The claim payer id. e.g. CX014 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| payer_id* | string | Echoed payer id. |
| payer_name | string | Payer name when known. |
| electronic_attachments* | string | available or not_available. |
| route_state* | string | routed, choose_plan or not_on_network. |
| plan_count* | integer | How many plans accept attachments under this payer id. |
| plan_sensitive_codes* | array | Codes whose answer depends on which plan the patient is on. Empty for most payers. |
| narrative* | object | allowed and max_characters. |
| payer_reference_number* | string | required, allowed or not_accepted. |
| payer_notes | string | The payer's own note. |
| notes_vary_by_plan* | boolean | True when plans differ in notes or return policy. |
| return_policy | string | The payer's own return policy. |
| file_limits* | object | Upload constraints. |
| source* | object | Freshness stamp. |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 404 | PAYER_UNKNOWN | No payer source Claim House holds knows this identifier |
| 503 | ATTACHMENT_RULES_UNAVAILABLE | No payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it |
List a payer's 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| payer_id* | string | The claim payer id. e.g. CX014 |
Query parameters
| Name | Type | Description |
|---|---|---|
| q | string | Case insensitive substring of the plan name. e.g. emblem |
| pageToken | string | Token from the previous page. |
| pageSize | integer | 10 to 50, default 25. |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| payer_id* | string | Echoed payer id. |
| plans* | array | plan_id and name. |
| total* | integer | How many plans match. |
| next_page_token | string | Null on the last page. |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 404 | PAYER_UNKNOWN | No payer source Claim House holds knows this identifier |
| 503 | ATTACHMENT_RULES_UNAVAILABLE | No payer rule generation is loaded yet. Retry after the Retry-After interval; guidance is advisory, so do not block a claim on it |
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.
| Field | Set by | Rules |
|---|---|---|
facility_id | Gateway | fac_ plus 20 lowercase base32 characters. Opaque. |
billing_npi | You, on create | Ten digits with a valid NPI check digit. Unique together with the TIN inside your seller. Cannot be changed. |
tin / tin_type | You, on create | Nine digits, no dashes. The TIN is never returned by any route; responses carry only tin_type (EI default, or SY). Cannot be changed. |
control_prefix | Gateway (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_kind | Gateway | sandbox 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_timezone | You, once | Form human claim references such as NORTH-20260914-0001. Permanent once saved. |
nea_facility_id | Gateway, on NEA registration | The office's id on the NEA attachment network. Null until registered. |
status | Gateway | ACTIVE, 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 inclaim_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 state | Meaning | Claims to this payer |
|---|---|---|
NOT_REQUIRED | The payer needs no enrollment for this transaction. | Accepted |
REQUESTED | Claim House has requested enrollment. | Refused |
PENDING | The payer or clearinghouse is processing it. | Refused |
LIVE | Enrolled. | Accepted |
REJECTED | The payer refused the enrollment. | Refused |
List offices
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id, also sent in the X-Request-Id header.e.g. evt_EXAMPLE0000000000101 |
| facilities* | array | One entry per reachable office. |
| facility_id* | string | The office id. e.g. fac_EXAMPLE0000000000001 |
| group_id | string | The group the office belongs to. Null when the office is in no group. |
| billing_npi* | string | The office's billing NPI. e.g. 1234567893 |
| tin_type* | string | The TIN type. The TIN itself is never returned. one of: EI · SY |
| taxonomy* | string | Billing taxonomy code. Empty string when not set. e.g. 1223G0001X |
| control_prefix* | string | The three or four character code stamped into this office's X12 control numbers. e.g. AAB |
| claim_office_code | string | Office code used in claim references. Null until set. |
| claim_timezone | string | IANA timezone that decides the claim reference date. Null until set. |
| nea_facility_id | string | The office's id on the NEA attachment network. Null until registered. |
| binding_kind* | string | Whether the office accepts test or production keys. one of: sandbox · production |
| status* | string | The office's lifecycle status. one of: ACTIVE · SUSPENDED · RETIRED |
| address_line1 | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| address_line2 | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| city | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| contact_email | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| contact_name | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| contact_phone | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| country | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| display_name | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| identity_review_status | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| legal_name | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| partner_office_ref | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| postal_code | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| profile_source | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| profile_version | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| state | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
Responses
{
"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
}
]
}{
"error": "FACILITY_NOT_GRANTED",
"message": "No office access is granted.",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks read |
| 403 | FACILITY_NOT_GRANTED | A group or office scope resolves to no office |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
officeId, the transport armed switch and the created time.display_name, city, state, partner_office_ref, identity_review_status) that this reference does not yet document. Treat them as unstable.Create an office
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| billingNpi* | string | The office's billing NPI. Ten digits with a valid NPI check digit. e.g. 1234567893 |
| tin* | string | The billing TIN, nine digits with no dashes. Stored for billing identity checks and never returned. e.g. 000000000 |
| tinType | string | The TIN type. Defaults to EI.one of: EI · SY |
| taxonomy | string | Billing taxonomy code, up to 10 characters. Defaults to empty string. The API does not check the pattern. e.g. 1223G0001X |
| groupId | string | A group of your seller to place the office in. Up to 64 characters. There is no route to create or list groups. |
| neaFacilityId | string | An NEA facility id you already hold, up to 64 characters. Setting it makes NEA registration refuse this office with 409. |
| officeId | string | Your own office reference, up to 64 characters. Stored but never returned. e.g. your-office-17 |
| controlPrefix | string | Import 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. |
| claimOfficeCode | string | Claim office code, matching ^[A-Z][A-Z0-9]{1,7}$. Send together with claimTimezone.e.g. NORTH |
| claimTimezone | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000102 |
| facility_id* | string | The new office id. e.g. fac_EXAMPLE0000000000001 |
| seller_id* | string | Your seller id. e.g. sel_EXAMPLE0000000000001 |
| group_id | string | The group id. Null when none was sent. |
| billing_npi* | string | The billing NPI. e.g. 1234567893 |
| tin_type* | string | The TIN type. one of: EI · SY |
| control_prefix* | string | The allocated or imported control prefix. e.g. AAB |
| claim_office_code | string | The claim office code. Null when not sent. |
| claim_timezone | string | The claim timezone. Null when not sent. |
| binding_kind* | string | Always sandbox for offices created through the API.one of: sandbox |
| status* | string | Always ACTIVE on create.one of: ACTIVE |
| address_line1* | string | Returned by the current gateway build; not yet described in this reference. |
| address_line2* | string | Returned by the current gateway build; not yet described in this reference. |
| capabilities* | array | Returned by the current gateway build; not yet described in this reference. |
| capability* | string | Returned by the current gateway build; not yet described in this reference. |
| state* | string | Returned by the current gateway build; not yet described in this reference. |
| city* | string | Returned by the current gateway build; not yet described in this reference. |
| contact_email* | string | Returned by the current gateway build; not yet described in this reference. |
| contact_name* | string | Returned by the current gateway build; not yet described in this reference. |
| contact_phone* | string | Returned by the current gateway build; not yet described in this reference. |
| country* | string | Returned by the current gateway build; not yet described in this reference. |
| display_name* | string | Returned by the current gateway build; not yet described in this reference. |
| legal_name | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| partner_office_ref | string | Returned by the current gateway build; not yet described in this reference. Can be null. |
| postal_code* | string | Returned by the current gateway build; not yet described in this reference. |
| profile_source* | string | Returned by the current gateway build; not yet described in this reference. |
| profile_version* | integer | Returned by the current gateway build; not yet described in this reference. |
| state* | string | Returned by the current gateway build; not yet described in this reference. |
Responses
{
"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"
}
]
}{
"error": "FACILITY_EXISTS",
"message": "A facility identity or claim office code is already reserved",
"errors": [],
"request_id": "evt_944a31r81kthrt83daef"
}{
"error": "VALIDATION",
"message": "billingNpi fails the 80840 Luhn check digit",
"errors": [
{
"billingNpi": "check digit invalid"
}
],
"request_id": "evt_d6sx71qdq9t9ctx4t3j3"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks admin |
| 409 | FACILITY_EXISTS | Same NPI and TIN, or the claim office code, already used in your seller |
| 422 | VALIDATION | NPI check digit fails, or groupId is not a group of your seller |
| 422 | INVALID_REQUEST | Missing field, wrong pattern, unknown field, office code without timezone, unknown timezone |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
FACILITY_EXISTS.controlPrefix that is malformed (for example AB) or already registered currently returns an unhandled 500 with a plain text body. Nothing is created.taxonomy and neaFacilityId are accepted but not echoed; read them back with List offices.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.Import offices in bulk
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| facilities* | array | Office rows, 1 to 500, processed in order. Each row takes the same fields and constraints as the Create an office body. |
| billingNpi* | string | The office's billing NPI. Ten digits with a valid NPI check digit. e.g. 1234567893 |
| tin* | string | The billing TIN, nine digits with no dashes. Stored for billing identity checks and never returned. e.g. 000000000 |
| tinType | string | The TIN type. Defaults to EI.one of: EI · SY |
| taxonomy | string | Billing taxonomy code, up to 10 characters. Defaults to empty string. The API does not check the pattern. e.g. 1223G0001X |
| groupId | string | A group of your seller to place the office in. Up to 64 characters. There is no route to create or list groups. |
| neaFacilityId | string | An NEA facility id you already hold, up to 64 characters. Setting it makes NEA registration refuse this office with 409. |
| officeId | string | Your own office reference, up to 64 characters. Stored but never returned. e.g. your-office-17 |
| controlPrefix | string | Import 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. |
| claimOfficeCode | string | Claim office code, matching ^[A-Z][A-Z0-9]{1,7}$. Send together with claimTimezone.e.g. NORTH |
| claimTimezone | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000103 |
| received* | integer | Number of rows in the request. e.g. 3 |
| created* | integer | Number of rows with status created.e.g. 1 |
| results* | array | One entry per row, in request order. |
| index* | integer | Zero-based row index. e.g. 0 |
| status* | string | The verdict for this row. one of: created · duplicate · invalid |
| facility_id | string | The new office id. Present only when created. |
| control_prefix | string | The allocated or imported control prefix. Present only when created. |
| error | string | Why 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
{
"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"
}
]
}{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks admin |
| 422 | INVALID_REQUEST | Empty or over 500 rows, or any row fails the schema |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
duplicate.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.controlPrefix is reported per row as invalid with error rejected.Get office 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
readandoffices.read(API keys receive 404) - Idempotency
- none
- Side effects
- Read only. No audit entry and no vendor call.
- In the dashboard
- API only
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A signed-in dashboard session token as Bearer <session token>. API keys are refused (404).e.g. Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyX0VYQU1QTEUifQ.EXAMPLEsignature |
Path parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office id (fac_...). Must be covered by the session's office access.e.g. fac_tycrfy0cs0qc7sq95eme |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000104 |
| capabilities* | array | One row per capability for this office. Empty when the office has no capability rows. |
| seller_id* | string | Your seller id. e.g. sel_EXAMPLE0000000000001 |
| facility_id* | string | The office id. e.g. fac_EXAMPLE0000000000001 |
| capability* | string | The service the row describes. one of: attachments · claims · eligibility · remittances |
| state* | string | The office's readiness for this service. New offices start at UNCONFIGURED.one of: UNCONFIGURED · PENDING_VERIFICATION · READY · BLOCKED · SUSPENDED |
| reason_code | string | Why the capability is in its state. Null when not set. |
| evidence_reference | string | Reference to the evidence reviewed for this state. Null when not set. |
| reviewed_by | string | Who last reviewed the capability. Null when never reviewed. |
| reviewed_at | datetime | When the capability was last reviewed. Null when never reviewed. |
| version* | integer | Row version, starting at 1. e.g. 1 |
Responses
{
"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
}
]
}{
"error": "NOT_FOUND",
"message": "no such office",
"errors": [],
"request_id": "evt_hvmk24krwqtwq9dbndsy"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks read |
| 404 | NOT_FOUND | Any API key, a session without offices.read, or an office outside the session's access |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
NOT_FOUND, even for their own offices.UNCONFIGURED until Claim House verifies the service for the office.capabilities array for an unknown id. Offices created before capability tracking also return an empty array.Set 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The 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
| Name | Type | Description |
|---|---|---|
| claimOfficeCode* | string | Office code used in references. Two to eight characters matching ^[A-Z][A-Z0-9]{1,7}$, unique inside your seller.e.g. NORTH |
| claimTimezone* | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000105 |
| facility_id* | string | The office id. e.g. fac_EXAMPLE0000000000001 |
| claim_office_code* | string | The code you sent. e.g. NORTH |
| claim_timezone* | string | The timezone you sent. e.g. America/New_York |
Responses
{
"request_id": "evt_vcdcf0wr9c9n71mzpn74",
"facility_id": "fac_bkzth74mqx09wergap5c",
"claim_office_code": "NORTH",
"claim_timezone": "America/New_York"
}{
"error": "CLAIM_NAMING_IMMUTABLE",
"message": "The office naming configuration is already fixed",
"errors": [],
"request_id": "evt_ddyvwhyaeqvx4j4ayhja"
}{
"error": "OFFICE_CODE_TAKEN",
"message": "Office code is already reserved",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks admin |
| 403 | FACILITY_NOT_GRANTED | The office is outside the credential's grants |
| 404 | NOT_FOUND | No such office in your seller |
| 409 | OFFICE_CODE_TAKEN | Another office of your seller uses this code |
| 409 | CLAIM_NAMING_IMMUTABLE | The office already has a different code or timezone |
| 422 | INVALID_REQUEST | Missing field, bad code pattern, unknown timezone or unknown field |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
claim_reference on claim reads, GET /v1/claims?tenant_claim_id= also matches it, and single-claim artifact downloads use it in their filenames.List office 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000106 |
| facility_id* | string | The office id. e.g. fac_EXAMPLE0000000000001 |
| enrollments* | array | One row per payer and transaction type. |
| payer_id* | string | The payer id the enrollment is keyed on. e.g. EXAMPLEPAYER1 |
| transaction_type* | string | The transaction the enrollment covers. Only dental_claim gates claim intake today.one of: dental_claim · claim_status · eligibility · remittance |
| state* | string | The enrollment state. one of: NOT_REQUIRED · REQUESTED · PENDING · LIVE · REJECTED |
| requested_at | datetime | When enrollment was requested. Null when never requested. |
| live_at | datetime | When the enrollment went live. Null until live. |
Responses
{
"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"
}
]
}{
"error": "FACILITY_NOT_GRANTED",
"message": "this key's grants do not cover that facility",
"errors": [
{
"facility_id": "fac_qnwrs4rq9tjtqwv37phe"
}
],
"request_id": "evt_w7r75h0tw0pcyt2p6yc6"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks read |
| 403 | FACILITY_NOT_GRANTED | The office is outside the credential's grants |
| 404 | NOT_FOUND | No such office in your seller |
| 422 | INVALID_REQUEST | facility_id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
Register an office with NEA
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The 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
| Name | Type | Description |
|---|---|---|
| practiceName* | string | Practice name, 1 to 100 characters. e.g. Sample Dental Group |
| address1* | string | Street address, 1 to 100 characters. e.g. 100 Sample Street |
| city* | string | City, 1 to 50 characters. e.g. Sampleville |
| state* | string | Two-letter state. Sent uppercased. e.g. OH |
| zip* | string | ZIP code, five or nine digits with no dash. e.g. 44000 |
| phone* | string | Practice phone, 10 to 20 characters. e.g. 5555550100 |
| fax | string | Practice fax, up to 20 characters. Sent only when present. |
| contactName | string | Contact person, up to 100 characters. Defaults to empty string. e.g. Office Manager |
| contactEmail | string | Contact email, up to 100 characters. Defaults to empty string. e.g. office@example.com |
| doctorFirstName | string | Doctor first name, up to 50 characters. Defaults to empty string. e.g. Sample |
| doctorLastName | string | Doctor last name, up to 50 characters. Defaults to empty string. e.g. Provider |
| taxId | string | Optional confirmation of the office's TIN, nine digits. If sent it must equal the registered TIN. |
| specialtyCodeId | integer | Attachment network specialty code, 0 or more. Defaults to 0. e.g. 0 |
| partnerCustomerId | string | Your customer id at the attachment network, up to 64 characters. Defaults to the facility_id. |
| promoCode | string | Attachment network promo code, up to 32 characters. Defaults to empty string. |
| username | string | Attachment 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
| Name | Type | Description |
|---|---|---|
| facilityId* | string | The office id. e.g. fac_EXAMPLE0000000000001 |
| neaFacilityId* | string | The attachment network's facility id for this office. e.g. EXAMPLE-NEA-0001 |
| credentialId* | string | Id of the sealed registration record (art_...).e.g. art_EXAMPLE0000000000001 |
| registeredAt* | datetime | When the gateway recorded the registration. |
| responseKeys | array | Array of strings. The sorted top-level key names the network returned, with no values. |
| recordedOnFacility | boolean | False when the id was sealed but the office already carried an id from a concurrent registration. Claim House reconciles it. e.g. true |
Responses
{
"facilityId": "fac_j0h554r1qy2nzrkvspb4",
"neaFacilityId": "NEAFAC-0001",
"credentialId": "art_e019bmb636ej658wy065",
"registeredAt": "2026-09-05T14:30:00+00:00",
"responseKeys": [
"facilityId"
],
"recordedOnFacility": true
}{
"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"
}{
"error": "NEA_FACILITY_ALREADY_REGISTERED",
"message": "this facility already carries an NEA facility id",
"errors": [],
"request_id": "evt_4sxr7065y35003arwn90"
}Errors
| Status | Code | When |
|---|---|---|
| 422 | INVALID_REQUEST | Body or path fails the schema (checked first) |
| 503 | NEA_REGISTRATION_UNAVAILABLE | NEA registration is not configured on this gateway |
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks admin |
| 403 | SELLER_KEY_REQUIRED | The credential is group or office scoped |
| 404 | NOT_FOUND | No such office in your seller |
| 409 | NEA_FACILITY_ALREADY_REGISTERED | The office already has an nea_facility_id |
| 403 | BILLING_IDENTITY_MISMATCH | taxId was sent and differs from the office's TIN |
| 502 | NEA_REGISTRATION_FAILED | The network refused, answered without an id, or did not answer |
| 500 | NEA_CREDENTIAL_NOT_SEALED | Registered at the network but the record did not save |
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.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.errors[0].error_class on a 502 is one of NeaAuthError, NeaRequestError, NeaTransportError, NeaResponseError.neaFacilityId already set is refused with 409.Read an office's plan choice
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
| payer_id* | string | The claim payer id. e.g. CX014 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| facility_id* | string | Echoed office id. |
| payer_id* | string | Echoed payer id. |
| plan | object | plan_id and name, or null. |
| chosen_by | string | Who chose it. |
| chosen_at | string | When it was chosen. |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 403 | FACILITY_NOT_GRANTED | Your key has no access to that office |
Remember an office's plan choice
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
| payer_id* | string | The claim payer id. e.g. CX014 |
Request body
| Name | Type | Description |
|---|---|---|
| plan_id* | string | An opaque plan id belonging to that payer. e.g. apl_2a156835fbfcc398 |
Request example
{
"plan_id": "apl_2a156835fbfcc398"
}Response fields
| Name | Type | Description |
|---|---|---|
| facility_id* | string | Echoed office id. |
| payer_id* | string | Echoed payer id. |
| plan* | object | The saved plan. |
| chosen_by* | string | Who chose it. |
| chosen_at* | string | When it was chosen. |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 422 | PLAN_NOT_FOR_PAYER | That plan does not belong to this payer |
| 403 | FACILITY_NOT_GRANTED | Your key has no access to that office |
Forget an office's plan choice
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| facility_id* | string | The office. Must be granted to your key. e.g. fac_tycrfy0cs0qc7sq95eme |
| payer_id* | string | The claim payer id. e.g. CX014 |
Request example
null
Responses
null
Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Key missing, malformed, unknown or revoked |
| 403 | PERMISSION_DENIED | Key lacks read |
| 403 | FACILITY_NOT_GRANTED | Your key has no access to that office |
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.
| Type | Meaning | Claim state effect | artifact_id | Key summary fields |
|---|---|---|---|---|
claim.received | Submission stored but held at intake for review. | Created in HOLD. | none | submission_id, state, finding_codes |
claim.queued | Passed intake, waiting for a transport window. | Created in QUEUED. | none | submission_id, state, finding_codes |
submission.rejected_pre_transport | Intake edits rejected the submission, one event per claim. Nothing sent. | Created in REJECTED_PRE_TRANSPORT (terminal). | none | submission_id, state, finding_codes |
claim.transported | The outbound 837D file was uploaded once and verified by read-back. | Moves to TRANSPORTED. | The 837D file as sent | file_id, file_stem, window_id, session_id |
claim.transport_ambiguous | The single upload attempt failed or could not be verified. Never resent automatically. | Moves to TRANSPORT_AMBIGUOUS. | The 837D file attempted | file_id, reason (put_failed:<ErrorClass> or readback_mismatch) |
claim.operator_resolved | An operator settled an ambiguous file with the clearinghouse. | TRANSPORTED (delivered), QUEUED (not delivered) or CLOSED (dead). | none | resolution, resolved_by, file_id, note |
claim.ack_997 | A 997 acknowledgment arrived. | Accepted to ACK_997_ACCEPTED; rejected to NEEDS_CORRECTION. | The 997 file | group_code, transaction_code, ack_kind, errors, error_count |
claim.ack_999 | A 999 acknowledgment arrived. | Same as claim.ack_997. | The 999 file | Same as claim.ack_997, ack_kind 999 |
claim.status_277 | A 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 file | category_code, status_code, entity_code, effective_date, payer_claim_number |
claim.stalled_997 | No 997 within the window after transport (default 4 hours). Raised once. | STALLED_997, only from TRANSPORTED. | none | expected_event_kind, due_at, sla_seconds |
claim.stalled_277 | No 277 within the window after transport (default 2 days). Raised once. | STALLED_277, only from TRANSPORTED. | none | expected_event_kind, due_at, sla_seconds |
claim.paid | An 835 line paid the claim fully or partly. | Moves to PAID (not from REJECTED_PRE_TRANSPORT). | The 835 file | claim_status_code, remittance_id, payment_id, charged_amount, paid_amount, patient_responsibility, trace_number, payment_date |
claim.denied | An 835 line with CLP02 4 denied the claim. | Moves to DENIED. | The 835 file | Same as claim.paid |
claim.reversed | An 835 line with CLP02 22 reversed a payment. | Moves to REVERSED. | The 835 file | Same as claim.paid |
remittance.received | An 835 file included at least one of your claims. One per seller per file. | None (applied: false). | The 835 file | remittance_id, claim_count, paid_amount, claim_ids, trace_number, payment_method, payer_id |
eligibility.checked | An eligibility check finished. | No claim (claim_id is null). | none | eligibility_id, payer_id, status, outcome, cache, mode |
eligibility.pdf_generated | A PDF of an eligibility answer was generated. | No claim. | The PDF | eligibility_id, artifact_id, filename, bytes |
attachment.sent | An attachment packet was delivered and named a resolvable claim. | None (applied: false). | none | packet_id, payer_id, door, outcome (OK), reference, send_id |
attachment.failed | An attachment send failed or was ambiguous and named a resolvable claim. | None. | none | Same as attachment.sent, outcome FAILED or AMBIGUOUS |
attachment.packet_created | A packet was created. | Written only to the packet's own timeline, so it does not appear in this feed today. | none | packet_id, attachment_count, deduplicated_count, kinds |
- Load your stored cursor (start at
0). The cursor is a plain integer and never expires. - Call
GET /v1/events?since_cursor=<cursor>&limit=500. Events come back oldest first in ascendingsequence. - Process each event, skipping any
event_idyou already processed.sequenceis shared by every seller, so gaps are normal. - Store
next_cursoronly after processing succeeds. On an empty page it echoes yoursince_cursor. - If
has_moreis 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 byevent_id.
- Out-of-order evidence is normal: a 277 can arrive before its 997. Use
appliedandstate, not the event type, to know the current claim state. occurred_atis not monotonic withsequence. 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.
| Record | Id | Where you find the id | What you get |
|---|---|---|---|
| Artifact | art_... | artifact_id on events, remittance records and the eligibility PDF response | A short-lived download URL for one stored document (837D, 997, 999, 277, 835 or eligibility PDF). |
| File | file_... | transport.file_id on a submission, or summary.file_id on transport events | The transport record of one outbound 837D file (control numbers, hash, state) plus a download URL when one exists. |
List 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| since_cursor | integer | Return events with sequence strictly greater than this. 0 or more. Defaults to 0, the start of your history.e.g. 1040 |
| limit | integer | Maximum events per page, 1 to 500. Defaults to 100. e.g. 500 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. Starts with evt_ but is not an event id.e.g. evt_EXAMPLE0000000000201 |
| events* | array | Events in ascending sequence order. |
| event_id* | string | Unique event id (evt_...). Use it to de-duplicate.e.g. evt_EXAMPLE0000000000001 |
| sequence* | integer | Position in the global event order. Shared by every seller, so gaps are normal. e.g. 1102 |
| type* | string | The event type. See the event types table in the group guide. |
| seller_id* | string | Your seller id. e.g. sel_EXAMPLE0000000000001 |
| facility_id* | string | The office the event belongs to. e.g. fac_EXAMPLE0000000000001 |
| claim_id | string | The claim id. Null for eligibility events. |
| submission_id | string | The submission that created the claim. Null when there is no claim. |
| tenant_claim_id | string | Your own claim id from the claim. Null when there is no claim or you did not send one. |
| artifact_id | string | The downloadable file behind the event (art_...). Null when the event has none. |
| occurred_at* | datetime | When the event happened, not when it was recorded. Not monotonic with sequence. |
| summary* | object | The event payload. Keys depend on type; never contains patient names, dates of birth or member ids. |
| next_cursor* | integer | The 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* | boolean | True when the page is full. A full page can be followed by an empty one. e.g. true |
Responses
{
"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
}{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks read |
| 403 | FACILITY_NOT_GRANTED | The key has no office grant at all |
| 422 | INVALID_REQUEST | since_cursor negative or not an integer, or limit outside 1 to 500 |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
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.remittance.received is attached to the first of your claims in the 835, so a key for a different office may not see it.INVALID_CURSOR error on this route.claim.transported and claim.transport_ambiguous events may not be recorded, so do not treat their absence as proof a claim was not sent.Get an artifact
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| artifact_id* | string | The 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000202 |
| artifact_id* | string | The id you asked for. e.g. art_EXAMPLE0000000000277 |
| download_filename | string | Suggested 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_id | string | Claim of the first event that references this artifact. Null for an eligibility PDF. |
| facility_id* | string | Office of that first event. e.g. fac_EXAMPLE0000000000001 |
| event_kind* | string | Type of that first event. e.g. claim.status_277 |
| url* | string | The download URL. Anyone holding it can download the file until it expires, so never log or store it. |
| expires_in_seconds* | integer | Lifetime of url in seconds.e.g. 600 |
Responses
{
"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
}{
"error": "NOT_FOUND",
"message": "no such artifact",
"errors": [],
"request_id": "evt_21qntm5psys23mxtpbqd"
}{
"error": "ARTIFACT_URL_UNAVAILABLE",
"message": "this archive store cannot produce a retrieval URL",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks read |
| 403 | FACILITY_NOT_GRANTED | The key has no office grant at all |
| 404 | NOT_FOUND | No event in your seller and grants references this id |
| 422 | INVALID_REQUEST | artifact_id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
| 503 | ARTIFACT_URL_UNAVAILABLE | The archive store cannot produce a URL |
<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.expires_in_seconds (600 today). Do not cache it, log it or put it in page HTML.file:// path on the gateway machine instead of a signed URL.Get an outbound file
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| file_id* | string | The file id (file_...), 1 to 64 characters. Must belong to your seller and grants.e.g. file_mgrexn8c9nxscydn6z3y |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000203 |
| file_id* | string | The file id. e.g. file_EXAMPLE0000000000001 |
| facility_id* | string | The office the file was built for. e.g. fac_EXAMPLE0000000000001 |
| state* | string | STAGED (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* | string | The name the file was given at the clearinghouse. e.g. AAB_20260912140500_001.837 |
| isa13* | integer | Interchange control number (ISA13). e.g. 100000123 |
| gs06* | integer | Group control number (GS06). e.g. 123 |
| size_bytes* | integer | Size of the file bytes. e.g. 4821 |
| sha256* | string | SHA-256 of the file bytes, lowercase hex. |
| transaction_count* | integer | Number of 837D transaction sets from your submissions in this file. e.g. 3 |
| ambiguity_reason_code | string | Why the file is ambiguous, for example put_failed:TimeoutError or readback_mismatch. Null otherwise. |
| transported_at | datetime | When the verified upload completed. Null until transported. |
| created_at* | datetime | When the file record was created. |
| updated_at* | datetime | Last change to the record. |
| download | object | Link to the 837D bytes. Null for STAGED, AMBIGUOUS and DEAD files and whenever no transport event recorded an artifact. |
| artifact_id* | string | Artifact id of the file bytes. e.g. art_EXAMPLE0000000000837 |
| url* | string | Signed download URL. No download_filename is applied here. |
| expires_in_seconds* | integer | Lifetime of url in seconds.e.g. 600 |
Responses
{
"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
}
}{
"error": "NOT_FOUND",
"message": "no such file",
"errors": [],
"request_id": "evt_d5xc02v810w935gr8k8p"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks read |
| 403 | FACILITY_NOT_GRANTED | The key has no office grant at all |
| 404 | NOT_FOUND | No file with that id in your seller and grants |
| 422 | INVALID_REQUEST | file_id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
| 503 | ARTIFACT_URL_UNAVAILABLE | A download exists but the archive store cannot produce a URL |
AMBIGUOUS file is never resent automatically. Do not resubmit its claims; wait for Claim House to resolve it with the clearinghouse.download can be null even for TRANSPORTED files, because the transport event that carries the artifact may not be recorded.Webhooks
Register an HTTPS endpoint to receive signed claim, acknowledgment, payment and eligibility events, then inspect, replay and re-sign deliveries.
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 type | Emitted when |
|---|---|
claim.received | A claim was accepted at intake but placed on hold. |
claim.queued | A claim passed intake and is queued for the next dispatch window. |
claim.transported | Delivery of the file carrying the claim to the clearinghouse was confirmed. |
claim.transport_ambiguous | The transport outcome for the claim's file is uncertain. It is not resent automatically; a Claim House operator resolves it. |
claim.operator_resolved | An operator recorded a verdict on an ambiguous file. |
claim.ack_997 | A 997 functional acknowledgment covering the claim arrived. |
claim.ack_999 | A 999 implementation acknowledgment covering the claim arrived. |
claim.status_277 | A 277 claim status response for the claim arrived. |
claim.stalled_997 | No acknowledgment arrived within the expected window (default 4 hours after transport). |
claim.stalled_277 | No claim status arrived within the expected window (default 2 days). |
submission.rejected_pre_transport | A submission was refused at intake and will never be transported. |
eligibility.checked | An eligibility check completed (including vendor errors and cache hits). Not tied to a claim. |
eligibility.pdf_generated | A PDF was generated for an eligibility result. |
attachment.packet_created | An attachment packet was created. Delivered only when the event names a claim, which at creation it normally does not. |
attachment.sent | An attachment send succeeded. Delivered only when the send resolved to a claim. |
attachment.failed | An attachment send failed or its outcome is uncertain. Delivered only when the send resolved to a claim. |
claim.paid | An 835 paid the claim. |
claim.denied | An 835 denied the claim. |
claim.reversed | An 835 reversed an earlier payment. |
remittance.received | An 835 file was processed. Sent once per seller per file, attached to the first of your claims in that file. |
| Payload field | Type | Description |
|---|---|---|
event_id | string | Unique event id, evt_.... Deduplicate on it. |
type | string | One of the event types above. |
occurred_at | string | When the event happened, UTC, formatted YYYY-MM-DD HH:MM:SS.ffffff+00:00 with a space rather than T. |
seller_id | string | Your seller id. |
facility_id | string | Office id. |
submission_id | string or null | Submission id when known. |
claim_id | string | Claim id. An empty string, not null, for events with no claim such as eligibility.checked. |
tenant_claim_id | string or null | Your own claim id, when you supplied one. |
artifact_id | string or null | Related raw artifact (for example an acknowledgment file), downloadable through GET /v1/artifacts/{artifact_id}. |
summary | object | Present 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 rule | Behavior |
|---|---|
| Request | One POST per delivery, Content-Type: application/json, compact JSON with keys sorted alphabetically. Identifiers only, never patient data or amounts. |
| Guarantee | At least once. The same event can arrive more than once. |
| Success | Only a 2xx within the 5 second timeout. A 3xx is a failure and redirects are not followed. |
| Retry schedule | After a failed attempt the next is due 1, 5, 30, 120, then 600 seconds later: six attempts over roughly 13 minutes at the fastest. |
| Parking | If the sixth attempt fails the delivery is PARKED and never attempted again unless you replay it. |
| Backlog | A new endpoint receives every past event of its subscribed types, not only new ones. |
| Timing and order | A 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 rotation | Rotating 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)
Create a webhook endpoint
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| url* | string | Your 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* | array | Array 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| endpoint* | object | The endpoint that was created. |
| endpoint_id* | string | Endpoint id, whk_.... Store it; no live route lists endpoints.e.g. whk_EXAMPLE0000000000000001 |
| url* | string | The endpoint URL, trimmed. e.g. https://hooks.example.com/claimhouse |
| event_types* | array | Array of strings. The subscribed event types. e.g. ["claim.transported","claim.ack_999","claim.status_277","claim.paid","claim.denied"] |
| secret_version* | integer | Version of the current signing secret. 1 at creation, incremented by each rotation.e.g. 1 |
| status* | string | Endpoint status. Always ACTIVE at creation.one of: ACTIVE · DISABLED e.g. ACTIVE |
| created_at* | datetime | ISO 8601 creation time. e.g. 2026-09-14T15:10:00+00:00 |
| signing_secret* | string | The signing secret, whsec_ followed by 43 URL-safe characters. Shown only in this response.e.g. whsec_EXAMPLE |
| signing_secret_note* | string | Reminder 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
{
"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"
}{
"error": "UNKNOWN_EVENT_TYPE",
"message": "one or more event types are not published by this gateway",
"errors": [
{
"event_types": [
"claim.*"
]
}
],
"request_id": "evt_qp8qgwm3ztwd2k7x3d0r"
}{
"error": "WEBHOOK_ENDPOINT_LIMIT",
"message": "a seller may register at most 4 endpoints",
"errors": [],
"request_id": "evt_j6fspw8jxs8kgb3weyxc"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | INVALID_URL | url does not start with https:// |
| 400 | UNKNOWN_EVENT_TYPE | one or more event types are not published; errors[0].event_types lists them |
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks webhooks |
| 409 | WEBHOOK_ENDPOINT_LIMIT | seller already has 4 endpoints, active or disabled |
| 422 | INVALID_REQUEST | url length out of range, or event_types missing or empty |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
| 500 | ENDPOINT_NOT_CREATED | the endpoint could not be stored |
List 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| endpoints* | array | Registered webhook endpoints, ordered by endpoint_id. |
| endpoint_id* | string | Endpoint id, whk_....e.g. whk_EXAMPLE0000000000000001 |
| url* | string | The https URL events are posted to. e.g. https://example.com/hooks/claimhouse |
| event_types* | array | Event types this endpoint is subscribed to. e.g. ["claim.status_changed"] |
| secret_version* | integer | Signing secret version; increments on each rotation. e.g. 2 |
| status* | string | Endpoint state. one of: ACTIVE · DISABLED e.g. ACTIVE |
| created_at* | datetime | ISO 8601 creation time. e.g. 2026-09-05T14:30:00+00:00 |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks webhooks |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
List webhook 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| endpoint_id* | string | The whk_... endpoint id returned when the endpoint was created. 1 to 64 characters.e.g. whk_35ka7045j25e5w5h2a32 |
Query parameters
| Name | Type | Description |
|---|---|---|
| limit | integer | Maximum attempts to return, 1 to 500. Defaults to 100. e.g. 100 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| endpoint_id* | string | The endpoint id. e.g. whk_EXAMPLE0000000000000001 |
| deliveries* | array | Array of attempt objects, newest first. |
| delivery_id* | string | Attempt row id, whd_....e.g. whd_EXAMPLE0000000000000006 |
| event_id* | string | The event this attempt delivered. e.g. evt_EXAMPLE0000000000000040 |
| attempt* | integer | Attempt number for this event on this endpoint, starting at 1. e.g. 6 |
| outcome* | string | DELIVERED, FAILED (another attempt is scheduled) or PARKED (no more attempts unless replayed).one of: DELIVERED · FAILED · PARKED e.g. PARKED |
| status_code | integer | HTTP status your endpoint returned. Null on timeout, connection error, or a replay marker row. e.g. 503 |
| error_type | string | Failure class, for example TimeoutError or ConnectError. OperatorReplay marks a row written by a replay request. Null when delivered.e.g. TimeoutError |
| attempted_at* | datetime | ISO 8601 time of the attempt. e.g. 2026-09-14T15:24:40+00:00 |
| next_attempt_at | datetime | When the next attempt is due. Null for DELIVERED and PARKED.e.g. 2026-09-14T15:24:35+00:00 |
Responses
{
"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
}
]
}{
"error": "NOT_FOUND",
"message": "no such webhook endpoint",
"errors": [],
"request_id": "evt_1v843gx5jrd72t8hz96z"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks webhooks |
| 404 | NOT_FOUND | no such endpoint in your seller |
| 422 | INVALID_REQUEST | limit out of range or endpoint_id too long |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
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.Replay a webhook delivery
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
FAILEDreplay 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| endpoint_id* | string | The whk_... endpoint id returned when the endpoint was created. 1 to 64 characters.e.g. whk_wfrqqqwhwwpzmrxvnvna |
| delivery_id* | string | Any whd_... attempt id of the event on this endpoint. 1 to 64 characters.e.g. whd_kbzmrtyjjrd2p000m3g0 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| endpoint_id* | string | The endpoint id. e.g. whk_EXAMPLE0000000000000001 |
| delivery_id* | string | The delivery id you sent. e.g. whd_EXAMPLE0000000000000006 |
| event_id* | string | The event that will be attempted. e.g. evt_EXAMPLE0000000000000040 |
| attempt* | integer | Attempt number of the replay marker row. The real attempt is recorded as attempt + 1.e.g. 7 |
| queued_for* | datetime | ISO 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
{
"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"
}{
"error": "NOT_FOUND",
"message": "no such delivery on that endpoint",
"errors": [],
"request_id": "evt_k2f7ax1bbrnxc0rycc4c"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks webhooks |
| 404 | NOT_FOUND | no such endpoint in your seller, or the delivery is not on that endpoint |
| 422 | INVALID_REQUEST | a path value is longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
Rotate a webhook signing 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| endpoint_id* | string | The whk_... endpoint id returned when the endpoint was created. 1 to 64 characters.e.g. whk_qzmjdkyqkp8abf16wk0w |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| endpoint* | object | The endpoint after rotation. |
| endpoint_id* | string | Endpoint id, whk_.... Store it; no live route lists endpoints.e.g. whk_EXAMPLE0000000000000001 |
| url* | string | The endpoint URL, trimmed. e.g. https://hooks.example.com/claimhouse |
| event_types* | array | Array of strings. The subscribed event types. e.g. ["claim.transported","claim.ack_999","claim.status_277","claim.paid","claim.denied"] |
| secret_version* | integer | The new secret version. e.g. 2 |
| status* | string | Endpoint status. one of: ACTIVE · DISABLED e.g. ACTIVE |
| created_at* | datetime | ISO 8601 creation time. e.g. 2026-09-14T15:10:00+00:00 |
| signing_secret* | string | The new whsec_... signing secret. Shown only in this response.e.g. whsec_EXAMPLE2 |
| previous_secret_expires_at* | datetime | ISO 8601 time the previous secret stops signing (now plus 24 hours). e.g. 2026-09-15T15:45:00+00:00 |
Responses
{
"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"
}{
"error": "NOT_FOUND",
"message": "no such webhook endpoint",
"errors": [],
"request_id": "evt_g2qys47wvp393mz3179x"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks webhooks |
| 404 | NOT_FOUND | no such endpoint in your seller |
| 422 | INVALID_REQUEST | endpoint_id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
Account and team
Confirm who a credential is, onboard a new organization, and manage the people who can use it.
| Route family | Credential | Needs an organization membership |
|---|---|---|
GET /v1/me | API key or dashboard session with read | Sessions: yes, active |
/v1/onboarding/* | Signed-in dashboard session only | No. Works before any organization exists |
/v1/members, /v1/invitations, /v1/access-requests | Dashboard session only, admin plus a member action | Yes, active. API keys are refused with 403 |
- Sign in to the dashboard, then call
GET /v1/onboarding/state. - New company: create an application, then submit it. Joining an existing company: request access with its partner reference.
- Claim House reviews the application. When the organization is
READY, the state route returnsallowed_actions: ["enter_partner"]. - Organization administrators prepare invitations for teammates and adjust each member's actions and office access.
| Record | States |
|---|---|
| Application | DRAFT > PENDING_REVIEW > NEEDS_INFORMATION | APPROVED > PROVISIONING > READY, or DECLINED / BLOCKED |
| Access request | PENDING > APPROVED_FOR_INVITATION | DECLINED |
| Invitation | PREPARED > 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.
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.Get the current identity
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id, same as the X-Request-Id header. |
| seller_id | string | API key only. Your seller id, sel_.... |
| seller_name | string | API key only. The seller's legal name. |
| seller_status | string | API key only. Seller status, for example ACTIVE. |
| principal* | object | Who is calling. |
| kind* | string | api_key or clerk (a dashboard session).one of: api_key · clerk |
| key_id | string | API key only. The key_... id. |
| subject | string | Session only. A stable pseudonymous id clerk:<24 hex> derived from the signed-in user. |
| mode* | string | test or production. For a key, the key's mode. For a session, the gateway's configured session mode.one of: test · production |
| scope | object | API key only. The key's grants. |
| kind* | string | Scope kind. one of: seller · group · facility |
| ids* | array | Array of strings. The raw grant ids. For a seller scope, [seller_id]. |
| permissions* | array | Array of strings. Sorted key permissions. one of: admin · read · submit · webhooks |
| reachable_facility_ids | array | API key only. Array of strings. Office ids the key reaches. For seller scope, every office of the seller (first 500). |
| user | object | Session only. The signed-in person. |
| display_name* | string | Name from the session token. Empty string when the token carries none. |
| email* | string | Email from the session token. Empty string when the token carries none. |
| organization | object | Session only. The organization the session is signed into. |
| clerk_org_id* | string | The sign-in organization id. |
| seller_id* | string | The Claim House seller the organization is bound to. |
| name* | string | Seller display name, or legal name when no display name is set. |
| onboarding_state* | string | Seller onboarding status. Always READY for a session that can call this route.one of: APPLICATION · PROVISIONING · READY · BLOCKED |
| role | string | Session only. The membership role label. one of: owner · admin · submitter · viewer |
| permissions | array | Session 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_scope | object | Session only. The member's office access. |
| kind* | string | ALL, SUBSET or NONE.one of: ALL · SUBSET · NONE |
| facility_ids* | array | Array of strings. For ALL, every office of the seller. For SUBSET, the granted offices. For NONE, empty. |
| authorization_version | integer | Session only. The membership's authorization version. |
| capabilities | object | Session only. Reserved. Always an empty object today. |
Responses
{
"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"
]
}{
"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": {}
}{
"error": "PERMISSION_DENIED",
"message": "permission denied",
"errors": [
{
"permission": "read"
}
],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 401 | UNAUTHORIZED | Session with no active membership in a bound organization |
| 403 | PERMISSION_DENIED | Credential lacks read, or the session's membership, seller or onboarding is not active |
| 403 | FACILITY_NOT_GRANTED | API key whose group or office grants resolve to no office |
| 404 | NOT_FOUND | API key whose seller record does not exist |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
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.seller_name, scope.office_scope and principal.role). The shapes above follow the current gateway; API key responses no longer carry scope.office_scope.Get 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id, same as the X-Request-Id header. |
| state* | string | READY 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* | array | The person's own applications, newest first. Each item has the same fields as Get an onboarding application. |
| application_id* | string | Application id, app_.... |
| status* | string | Application status. one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED |
| version* | integer | Current version. |
| display_name* | string | Display name. |
| review_reason | string | Review guidance, or null. |
| invitation | object | The 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* | string | Invitation id, inv_.... |
| organization_name* | string | The inviting organization's name. |
| intended_email* | string | The email the invitation was addressed to. |
| state* | string | Invitation state. one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN |
| version* | integer | Invitation version. |
| access_requests* | array | The person's own access requests, newest first. |
| request_id* | string | Access request id, arq_.... Not the trace id. |
| organization_reference* | string | The organization reference the request named. |
| relationship_reason* | string | Why the person asked for access. |
| status* | string | Review status. one of: PENDING · APPROVED_FOR_INVITATION · DECLINED |
| version* | integer | Current version. |
| created_at* | datetime | When it was created. |
| reviewed_at | datetime | When it was reviewed, or null. |
| allowed_actions* | array | Array of strings. ["enter_partner"] when state is READY, else ["create_application", "request_access"]. |
Responses
{
"request_id": "evt_nj662q095m3fjmnsbj9r",
"state": "READY",
"applications": [],
"invitation": null,
"access_requests": [],
"allowed_actions": [
"enter_partner"
]
}{
"error": "UNAUTHORIZED",
"message": "unauthorized",
"errors": [],
"request_id": "evt_devdp7p3r4z468arq3en"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | No session token, an API key was presented, or session sign-in is not configured |
SUSPENDED is declared but not produced by the current organization statuses. An organization still in APPLICATION or PROVISIONING reports PENDING or NO_ORGANIZATION.Create an onboarding application
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | 1 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| legal_name* | string | The company or practice legal name. 1 to 240 characters. e.g. Sample Dental Group LLC |
| display_name* | string | The name shown in the dashboard. 1 to 240 characters. e.g. Sample Dental Group |
| company_type* | string | Free text describing the kind of company, for example a dental group or software vendor. 1 to 80 characters. e.g. Dental group |
| applicant_name* | string | The person applying. 1 to 160 characters. e.g. Sample Applicant |
| applicant_title* | string | The applicant's job title. 1 to 120 characters. e.g. Operations Director |
| country | string | Two-letter country code. Defaults to US.e.g. US |
| expected_office_count | integer | How many offices you expect to connect. 1 to 10000. Defaults to 1. e.g. 3 |
| requested_capabilities | array | Array of strings. The rails you want enabled. Defaults to an empty array. one of: claims · attachments · eligibility · remittances e.g. ["claims","eligibility"] |
| existing_account_reference | string | Optional 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
| Name | Type | Description |
|---|---|---|
| application_id* | string | The application id, app_.... |
| applicant_issuer* | string | The sign-in issuer of the person who owns the application. |
| applicant_user_id* | string | The signed-in user id that owns the application. Only that person can read or change it. |
| contact_email_digest* | string | SHA-256 hex digest of the lowercased contact email. |
| contact_email_display* | string | The contact email, taken from the session token, lowercased. |
| legal_name* | string | Legal company or practice name. |
| display_name* | string | Display name. |
| company_type* | string | Kind of company. |
| applicant_name* | string | Applicant name. |
| applicant_title* | string | Applicant title. |
| country* | string | Two-letter country code. |
| expected_office_count* | integer | Expected number of offices. |
| requested_capabilities* | array | Array of strings. Requested rails. one of: claims · attachments · eligibility · remittances |
| existing_account_reference | string | Existing relationship reference, or null. |
| idempotency_key* | string | The Idempotency-Key the application was created with. |
| status* | string | Where the application is in review. one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED |
| version* | integer | Increments on every change. Send it back as expected_version. |
| submitted_at | datetime | When it was submitted for review, or null. |
| reviewed_at | datetime | When Claim House reviewed it, or null. |
| reviewed_by | string | Reviewer reference, or null. |
| review_reason | string | Review guidance, for example what information is missing, or null. |
| created_at* | datetime | When the application was created. |
| updated_at* | datetime | When it last changed. |
Responses
{
"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"
}{
"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"
}{
"error": "UNAUTHORIZED",
"message": "unauthorized",
"errors": [],
"request_id": "evt_y2hdpr67z1an7rymed9s"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | No session token, an API key was presented, or session sign-in is not configured |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 422 | INVALID_REQUEST | Idempotency-Key header missing or longer than 200 characters |
| 503 | IDENTITY_REGISTRY_UNAVAILABLE | Onboarding registry is not wired on this gateway |
Idempotency-Key returns the original application with 201, even if the body differs. No 409 is raised.request_id field; read X-Request-Id from the headers.Get an onboarding application
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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
| Name | Type | Description |
|---|---|---|
| application_id* | string | The application id, app_....e.g. app_pwefwt0r0bhaq97gsycf |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| application_id* | string | The application id, app_.... |
| applicant_issuer* | string | The sign-in issuer of the person who owns the application. |
| applicant_user_id* | string | The signed-in user id that owns the application. Only that person can read or change it. |
| contact_email_digest* | string | SHA-256 hex digest of the lowercased contact email. |
| contact_email_display* | string | The contact email, taken from the session token, lowercased. |
| legal_name* | string | Legal company or practice name. |
| display_name* | string | Display name. |
| company_type* | string | Kind of company. |
| applicant_name* | string | Applicant name. |
| applicant_title* | string | Applicant title. |
| country* | string | Two-letter country code. |
| expected_office_count* | integer | Expected number of offices. |
| requested_capabilities* | array | Array of strings. Requested rails. one of: claims · attachments · eligibility · remittances |
| existing_account_reference | string | Existing relationship reference, or null. |
| idempotency_key* | string | The Idempotency-Key the application was created with. |
| status* | string | Where the application is in review. one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED |
| version* | integer | Increments on every change. Send it back as expected_version. |
| submitted_at | datetime | When it was submitted for review, or null. |
| reviewed_at | datetime | When Claim House reviewed it, or null. |
| reviewed_by | string | Reviewer reference, or null. |
| review_reason | string | Review guidance, for example what information is missing, or null. |
| created_at* | datetime | When the application was created. |
| updated_at* | datetime | When it last changed. |
Responses
{
"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"
}{
"error": "NOT_FOUND",
"message": "no such application",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | No session token, an API key was presented, or session sign-in is not configured |
| 404 | NOT_FOUND | Unknown id, or an application owned by someone else |
| 503 | IDENTITY_REGISTRY_UNAVAILABLE | Onboarding registry is not wired on this gateway |
request_id field in the body; read X-Request-Id from the headers.Update an onboarding application
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| application_id* | string | The application id, app_....e.g. app_pwefwt0r0bhaq97gsycf |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The application version you last read. The update is refused if the record has changed since.e.g. 1 |
| legal_name* | string | The company or practice legal name. 1 to 240 characters. e.g. Sample Dental Group LLC |
| display_name* | string | The name shown in the dashboard. 1 to 240 characters. e.g. Sample Dental Group |
| company_type* | string | Free text describing the kind of company, for example a dental group or software vendor. 1 to 80 characters. e.g. Dental group |
| applicant_name* | string | The person applying. 1 to 160 characters. e.g. Sample Applicant |
| applicant_title* | string | The applicant's job title. 1 to 120 characters. e.g. Operations Director |
| country | string | Two-letter country code. Defaults to US. Omitting it resets it to the default.e.g. US |
| expected_office_count | integer | How many offices you expect to connect. 1 to 10000. Defaults to 1. Omitting it resets it to the default. e.g. 3 |
| requested_capabilities | array | Array 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_reference | string | Up 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
| Name | Type | Description |
|---|---|---|
| application_id* | string | The application id, app_.... |
| applicant_issuer* | string | The sign-in issuer of the person who owns the application. |
| applicant_user_id* | string | The signed-in user id that owns the application. Only that person can read or change it. |
| contact_email_digest* | string | SHA-256 hex digest of the lowercased contact email. |
| contact_email_display* | string | The contact email, taken from the session token, lowercased. |
| legal_name* | string | Legal company or practice name. |
| display_name* | string | Display name. |
| company_type* | string | Kind of company. |
| applicant_name* | string | Applicant name. |
| applicant_title* | string | Applicant title. |
| country* | string | Two-letter country code. |
| expected_office_count* | integer | Expected number of offices. |
| requested_capabilities* | array | Array of strings. Requested rails. one of: claims · attachments · eligibility · remittances |
| existing_account_reference | string | Existing relationship reference, or null. |
| idempotency_key* | string | The Idempotency-Key the application was created with. |
| status* | string | Where the application is in review. one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED |
| version* | integer | Increments on every change. Send it back as expected_version. |
| submitted_at | datetime | When it was submitted for review, or null. |
| reviewed_at | datetime | When Claim House reviewed it, or null. |
| reviewed_by | string | Reviewer reference, or null. |
| review_reason | string | Review guidance, for example what information is missing, or null. |
| created_at* | datetime | When the application was created. |
| updated_at* | datetime | When it last changed. |
Responses
{
"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"
}{
"error": "STALE_VERSION_OR_STATE",
"message": "application changed; reload before retrying",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | No session token, an API key was presented, or session sign-in is not configured |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 409 | STALE_VERSION_OR_STATE | Version mismatch, status not editable, or unknown or foreign id |
| 503 | IDENTITY_REGISTRY_UNAVAILABLE | Onboarding registry is not wired on this gateway |
STALE_VERSION_OR_STATE, not 404. Reload with Get an onboarding application.Submit an onboarding application
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_REVIEWand records a transition event. Grants no access. - In the dashboard
- Onboarding > Submit application for review
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| application_id* | string | The application id, app_....e.g. app_pwefwt0r0bhaq97gsycf |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The application version you last read. Minimum 1.e.g. 1 |
Request example
{
"expected_version": 1
}Response fields
| Name | Type | Description |
|---|---|---|
| application_id* | string | The application id, app_.... |
| applicant_issuer* | string | The sign-in issuer of the person who owns the application. |
| applicant_user_id* | string | The signed-in user id that owns the application. Only that person can read or change it. |
| contact_email_digest* | string | SHA-256 hex digest of the lowercased contact email. |
| contact_email_display* | string | The contact email, taken from the session token, lowercased. |
| legal_name* | string | Legal company or practice name. |
| display_name* | string | Display name. |
| company_type* | string | Kind of company. |
| applicant_name* | string | Applicant name. |
| applicant_title* | string | Applicant title. |
| country* | string | Two-letter country code. |
| expected_office_count* | integer | Expected number of offices. |
| requested_capabilities* | array | Array of strings. Requested rails. one of: claims · attachments · eligibility · remittances |
| existing_account_reference | string | Existing relationship reference, or null. |
| idempotency_key* | string | The Idempotency-Key the application was created with. |
| status* | string | Where the application is in review. one of: DRAFT · PENDING_REVIEW · NEEDS_INFORMATION · DECLINED · APPROVED · PROVISIONING · READY · BLOCKED |
| version* | integer | Increments on every change. Send it back as expected_version. |
| submitted_at | datetime | When it was submitted for review, or null. |
| reviewed_at | datetime | When Claim House reviewed it, or null. |
| reviewed_by | string | Reviewer reference, or null. |
| review_reason | string | Review guidance, for example what information is missing, or null. |
| created_at* | datetime | When the application was created. |
| updated_at* | datetime | When it last changed. |
Responses
{
"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"
}{
"error": "STALE_VERSION_OR_STATE",
"message": "application changed; reload before retrying",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | No session token, an API key was presented, or session sign-in is not configured |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 409 | STALE_VERSION_OR_STATE | Version mismatch, already submitted, or unknown or foreign id |
| 503 | IDENTITY_REGISTRY_UNAVAILABLE | Onboarding registry is not wired on this gateway |
Accept an organization invitation
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
Request access to an organization
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
PENDINGaccess request when the reference matches an organization. Grants no access and sends no notification. - In the dashboard
- Onboarding > Request access
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | 1 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| organization_reference* | string | The organization's partner reference, as the organization gave it to you. 1 to 160 characters. e.g. SAMPLE-DENTAL-GROUP |
| relationship_reason* | string | Why 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | The access request id, arq_.... This is not the trace id; read X-Request-Id for that. |
| status* | string | Review status. PENDING on creation.one of: PENDING · APPROVED_FOR_INVITATION · DECLINED |
| version* | integer | Current version. |
Responses
{
"request_id": "arq_EXAMPLE0000000000001",
"status": "PENDING",
"version": 1
}{
"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"
}{
"error": "UNAUTHORIZED",
"message": "unauthorized",
"errors": [],
"request_id": "evt_ah241wdyh220530kxd2z"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | No session token, an API key was presented, or session sign-in is not configured |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 503 | IDENTITY_REGISTRY_UNAVAILABLE | Onboarding registry is not wired on this gateway |
Idempotency-Key returns the stored request. When the reference matches no organization nothing is stored and each call returns a fresh id.List 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.manageaction (API keys are refused) - Idempotency
- none
- Side effects
- None. Reads only.
- In the dashboard
- Settings > Organization > Team > Invite and review
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id, same as the X-Request-Id header. |
| access_requests* | array | Access requests, newest first. |
| seller_id* | string | Your seller id. |
| request_id* | string | The access request id, arq_.... |
| requester_issuer* | string | Sign-in issuer of the requester. |
| requester_user_id* | string | Sign-in user id of the requester. |
| organization_reference* | string | The reference the requester typed. |
| relationship_reason* | string | The requester's stated reason. |
| status* | string | Review status. one of: PENDING · APPROVED_FOR_INVITATION · DECLINED |
| reviewer_id | string | Reviewer, or null. |
| review_reason | string | Review reason, or null. |
| version* | integer | Current version. |
| idempotency_key* | string | The key the requester sent. |
| created_at* | datetime | When it was created. |
| reviewed_at | datetime | When it was reviewed, or null. |
Responses
{
"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
}
]
}{
"error": "PERMISSION_DENIED",
"message": "members.manage is required",
"errors": [],
"request_id": "evt_vaqnezmamm7k8ty2e30h"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 403 | PERMISSION_DENIED | Lacks admin, is an API key, or the session lacks members.manage |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
request_id is the access request id, while the top-level request_id is the trace id.List 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.readaction (API keys are refused) - Idempotency
- none
- Side effects
- None. Reads only.
- In the dashboard
- Settings > Organization > Team
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id, same as the X-Request-Id header. |
| members* | array | Members ordered by membership id. |
| membership_id* | string | The membership id, mem_.... |
| name* | string | Currently the member's sign-in user id, not a person name. |
| email* | string | Always an empty string today. |
| role* | string | Role label. one of: owner · admin · submitter · viewer |
| status* | string | Membership status. one of: PENDING · ACTIVE · SUSPENDED · REVOKED |
| actions* | array | Array 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* | string | Office access kind. one of: ALL · SUBSET · NONE |
| scope_ids* | array | Array of strings. Granted office ids. Empty for ALL and NONE. |
| scope_label* | string | Human label: All offices, <n> offices or No office access. |
| authorization_version* | integer | Send it back as expected_version when updating the member. |
| last_sign_in_at | datetime | Last time the membership was reconciled with sign-in, or null. Not a true last sign-in time. |
Responses
{
"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
}
]
}{
"error": "PERMISSION_DENIED",
"message": "members.read is required",
"errors": [],
"request_id": "evt_c0g0xxwhg11bv0p0yc26"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 403 | PERMISSION_DENIED | Lacks read, is an API key, or the session lacks members.read |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
name and email are placeholders until profile data is joined.Update a member
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.manageaction (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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| membership_id* | string | The membership id, mem_....e.g. mem_1w2pqxr89mss07rbry6s |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The member's authorization_version from List members. Minimum 1.e.g. 3 |
| role* | string | The role label to record. one of: owner · admin · submitter · viewer e.g. viewer |
| actions | array | Array 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* | string | ALL 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_ids | array | Array 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* | string | Why 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
| Name | Type | Description |
|---|---|---|
| seller_id* | string | Your seller id. |
| membership_id* | string | The membership id, mem_.... |
| clerk_issuer* | string | The sign-in issuer of the member. |
| clerk_user_id* | string | The member's sign-in user id. |
| clerk_org_id* | string | The sign-in organization the membership belongs to. |
| role_template* | string | The member's role label after the change. one of: owner · admin · submitter · viewer |
| status* | string | Membership status. one of: PENDING · ACTIVE · SUSPENDED · REVOKED |
| authorization_version* | integer | Increments on every access change. Send it back as expected_version. |
| all_current_and_future_offices* | boolean | True when the new scope is ALL. |
| invitation_id | string | The invitation the membership came from, or null. |
| source* | string | How the membership was created, for example invitation. |
| activated_at | datetime | When it became active, or null. |
| suspended_at | datetime | When it was suspended, or null. |
| revoked_at | datetime | When it was revoked, or null. |
| last_reconciled_clerk_event | string | Last sign-in provider event applied, or null. |
| last_reconciled_at | datetime | When it was last reconciled, or null. |
Responses
{
"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
}{
"error": "DELEGATION_EXCEEDED",
"message": "membership proposal exceeds caller offices",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "MEMBERSHIP_CHANGE_REFUSED",
"message": "the final active owner cannot be demoted",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 403 | PERMISSION_DENIED | Lacks admin, is an API key, or the session lacks members.manage |
| 403 | DELEGATION_EXCEEDED | Actions or offices exceed yours, or office_ids do not fit scope_kind |
| 409 | STALE_VERSION_OR_STATE | expected_version is stale or the membership id is unknown |
| 409 | MEMBERSHIP_CHANGE_REFUSED | Demoting the last active owner, or an office id that is not yours |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
request_id, actions or office list. Call List members to see the new access.role is a label. What the member can do comes from actions and office scope.List 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.manageaction (API keys are refused) - Idempotency
- none
- Side effects
- None. Reads only.
- In the dashboard
- Settings > Organization > Team > Invite and review
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Request id, same as the X-Request-Id header. |
| invitations* | array | Invitations, newest first. |
| invitation_id* | string | The invitation id, inv_.... |
| organization_name* | string | Your organization's display name, or legal name. |
| intended_email* | string | The invitee email. |
| email* | string | Same value as intended_email. |
| role* | string | Proposed role label. one of: owner · admin · submitter · viewer |
| office_scope* | object | Proposed office access. |
| kind* | string | Scope kind. one of: ALL · SUBSET · NONE |
| facility_ids | array | Array of strings. Present only when kind is SUBSET. |
| state* | string | Invitation state. one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN |
| version* | integer | Current version. |
| created_at* | datetime | When it was prepared. |
Responses
{
"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"
}
]
}{
"error": "PERMISSION_DENIED",
"message": "members.manage is required",
"errors": [],
"request_id": "evt_52t06r6aw1f55ava9sp3"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 403 | PERMISSION_DENIED | Lacks admin, is an API key, or the session lacks members.manage |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
intended_email_display, role_template, proposed_scope_kind).Prepare an invitation
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.manageaction (API keys are refused) - Idempotency
- none
- Side effects
- Stores one
PREPAREDinvitation 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| email* | string | The invitee email. 3 to 254 characters, must contain @. Stored lowercased.e.g. sample.teammate@example.com |
| role* | string | The role label to propose. one of: owner · admin · submitter · viewer e.g. submitter |
| scope_kind* | string | Proposed office access. one of: ALL · SUBSET · NONE e.g. SUBSET |
| office_ids | array | Array 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"] |
| actions | array | Array 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* | datetime | When 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
| Name | Type | Description |
|---|---|---|
| seller_id* | string | Your seller id, sel_.... |
| invitation_id* | string | The invitation id, inv_.... |
| intended_email_digest* | string | SHA-256 hex digest of the lowercased invitee email. |
| intended_email_display* | string | The invitee email, lowercased. |
| role_template* | string | The role label proposed for the invitee. one of: owner · admin · submitter · viewer |
| proposed_scope_kind* | string | The office scope proposed for the invitee. one of: ALL · SUBSET · NONE |
| expires_at* | datetime | When the invitation stops being valid. |
| clerk_invitation_id | string | The sign-in provider's invitation id once delivered, else null. |
| delivery_operation_id | string | Id of the single delivery attempt once one starts, else null. |
| state* | string | Invitation state. one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN |
| version* | integer | Increments on every change. Send it back as expected_version. |
| created_by* | string | The user id of the administrator who prepared it. |
| created_at* | datetime | When it was prepared. |
| revoked_at | datetime | When it was revoked, else null. |
| accepted_at | datetime | When acceptance was observed, else null. |
| reconciled_at | datetime | When membership was reconciled, else null. |
Responses
{
"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
}{
"error": "DELEGATION_EXCEEDED",
"message": "membership proposal exceeds caller actions",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}{
"error": "PERMISSION_DENIED",
"message": "human member administration is required",
"errors": [],
"request_id": "evt_a83nh7hhkqk6kgqkghrh"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 403 | PERMISSION_DENIED | Lacks admin, or is an API key |
| 403 | DELEGATION_EXCEEDED | Session lacks members.manage, actions or offices exceed yours, or office_ids do not fit scope_kind |
| 404 | NOT_FOUND | An office id outside your own office access |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
| 503 | REGISTRY_WRITE_FAILED | The invitation was not stored |
Send an invitation
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.manageaction (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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| invitation_id* | string | The invitation id, inv_....e.g. inv_c78sg2v0veqke88d7h7s |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The invitation version you last read. Minimum 1.e.g. 1 |
| confirmed* | boolean | Must be true. Confirms you intend a real delivery.e.g. true |
Request example
{
"expected_version": 1,
"confirmed": true
}Response fields
| Name | Type | Description |
|---|---|---|
| seller_id* | string | Your seller id, sel_.... |
| invitation_id* | string | The invitation id, inv_.... |
| intended_email_digest* | string | SHA-256 hex digest of the lowercased invitee email. |
| intended_email_display* | string | The invitee email, lowercased. |
| role_template* | string | The role label proposed for the invitee. one of: owner · admin · submitter · viewer |
| proposed_scope_kind* | string | The office scope proposed for the invitee. one of: ALL · SUBSET · NONE |
| expires_at* | datetime | When the invitation stops being valid. |
| clerk_invitation_id | string | The sign-in provider's invitation id once delivered, else null. |
| delivery_operation_id | string | Id of the single delivery attempt once one starts, else null. |
| state* | string | Invitation state. one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN |
| version* | integer | Increments on every change. Send it back as expected_version. |
| created_by* | string | The user id of the administrator who prepared it. |
| created_at* | datetime | When it was prepared. |
| revoked_at | datetime | When it was revoked, else null. |
| accepted_at | datetime | When acceptance was observed, else null. |
| reconciled_at | datetime | When membership was reconciled, else null. |
Responses
{
"error": "INVITATION_DELIVERY_UNCONFIGURED",
"message": "invitation delivery is not configured",
"errors": [],
"request_id": "evt_3ss6bwba7cb1y4etkcwf"
}{
"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
}{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 403 | PERMISSION_DENIED | Lacks admin, is an API key, or the session lacks members.manage |
| 409 | CONFIRMATION_REQUIRED | confirmed is not true |
| 409 | STALE_VERSION_OR_STATE | Version mismatch, not PREPARED, or unknown id |
| 502 | DELIVERY_OUTCOME_UNCERTAIN | Delivery failed or could not be recorded; do not resend |
| 503 | INVITATION_DELIVERY_UNCONFIGURED | No delivery provider is configured (the current default) |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
INVITATION_DELIVERY_UNCONFIGURED and no email is sent.code, message and invitation, with no error or request_id. No route can clear OUTCOME_UNCERTAIN yet; contact Claim House support.Revoke an invitation
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.manageaction (API keys are refused) - Idempotency
- none
- Side effects
- Sets the invitation state to
REVOKEDand stampsrevoked_at. Does not contact the sign-in provider. - In the dashboard
- API only
Headers
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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* | string | Must be application/json. Without it the body is not parsed and the route answers 422 INVALID_REQUEST.e.g. application/json |
Path parameters
| Name | Type | Description |
|---|---|---|
| invitation_id* | string | The invitation id, inv_....e.g. inv_c78sg2v0veqke88d7h7s |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The invitation version you last read. Minimum 1.e.g. 1 |
| reason* | string | Why 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
| Name | Type | Description |
|---|---|---|
| seller_id* | string | Your seller id, sel_.... |
| invitation_id* | string | The invitation id, inv_.... |
| intended_email_digest* | string | SHA-256 hex digest of the lowercased invitee email. |
| intended_email_display* | string | The invitee email, lowercased. |
| role_template* | string | The role label proposed for the invitee. one of: owner · admin · submitter · viewer |
| proposed_scope_kind* | string | The office scope proposed for the invitee. one of: ALL · SUBSET · NONE |
| expires_at* | datetime | When the invitation stops being valid. |
| clerk_invitation_id | string | The sign-in provider's invitation id once delivered, else null. |
| delivery_operation_id | string | Id of the single delivery attempt once one starts, else null. |
| state* | string | Invitation state. one of: PREPARED · DELIVERY_PENDING · SENT · ACCEPTED · RECONCILED · EXPIRED · REVOKED · DELIVERY_FAILED · OUTCOME_UNCERTAIN |
| version* | integer | Increments on every change. Send it back as expected_version. |
| created_by* | string | The user id of the administrator who prepared it. |
| created_at* | datetime | When it was prepared. |
| revoked_at | datetime | When it was revoked, else null. |
| accepted_at | datetime | When acceptance was observed, else null. |
| reconciled_at | datetime | When membership was reconciled, else null. |
Responses
{
"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
}{
"error": "STALE_VERSION_OR_STATE",
"message": "invitation changed; reload before retrying",
"errors": [],
"request_id": "evt_bkfrar1bddj3efgmw6s0"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, revoked or unverifiable credential |
| 403 | PERMISSION_DENIED | Lacks admin, is an API key, or the session lacks members.manage |
| 409 | STALE_VERSION_OR_STATE | Version mismatch, not PREPARED or SENT, or unknown id |
| 422 | INVALID_REQUEST | Body, path or header does not fit the schema |
| 429 | TOO_MANY_REQUESTS | Rate or concurrency limit reached |
reason is required but is not stored today.SENT invitation does not withdraw the email already delivered by the sign-in provider.Read 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| version* | integer | Current preference version; pass back as expected_version on save.e.g. 3 |
| persisted* | boolean | False while the member still sees defaults. e.g. true |
| delivery_enabled* | boolean | Whether email delivery is active on this deployment. e.g. false |
| organization_name* | string | The member's organization name. e.g. Blue Line Dental |
| mode* | string | The member's organization mode. one of: test · live e.g. live |
| settings* | object | The preference document. |
| enabled* | boolean | Master switch. e.g. true |
| timezone* | string | IANA timezone used for daily and weekly digests. e.g. America/New_York |
| max_emails_per_day* | integer | 0–50. e.g. 5 |
| office_selection* | string | all_authorized or selected.one of: all_authorized · selected e.g. all_authorized |
| office_ids* | array | Practice ids when office_selection is selected. Empty otherwise.e.g. [] |
| categories* | object | Per-category frequency, off | immediate | daily | weekly.e.g. {"claim_status":"immediate","remittances":"daily"} |
Responses
{
"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
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | MEMBERSHIP_REQUIRED | the session has no active organization membership |
| 503 | NOTIFICATION_SETTINGS_UNAVAILABLE | notification settings are not connected |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
Save 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A signed-in dashboard session token as Bearer <token>. API keys are not accepted on this route.e.g. Bearer <session token> |
Request body
| Name | Type | Description |
|---|---|---|
| expected_version* | integer | The version from the last read.e.g. 3 |
| settings* | object | The full preference document — every field is required. |
| enabled* | boolean | Master switch. e.g. true |
| timezone* | string | IANA timezone. e.g. America/New_York |
| max_emails_per_day* | integer | 0–50. e.g. 5 |
| office_selection* | string | all_authorized or selected.one of: all_authorized · selected e.g. all_authorized |
| office_ids* | array | Practice ids when office_selection is selected; must be offices in your current access.e.g. [] |
| categories* | object | All 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| version* | integer | The new version after this save. e.g. 4 |
| persisted* | boolean | Always true after a save. e.g. true |
| delivery_enabled* | boolean | Whether email delivery is active on this deployment. e.g. false |
| organization_name* | string | The member's organization name. e.g. Blue Line Dental |
| mode* | string | The member's organization mode. one of: test · live e.g. live |
| settings* | object | The stored preference document, echoed back. e.g. {"enabled":true} |
Responses
{
"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"
}
}
}{
"error": "STALE_VERSION_OR_STATE",
"message": "Notification settings changed; reload before saving",
"errors": [],
"request_id": "evt_ang0n1gexynjw0dmfh8v"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | MEMBERSHIP_REQUIRED | the session has no active organization membership |
| 403 | FACILITY_NOT_GRANTED | office_ids names a practice outside your current access |
| 409 | STALE_VERSION_OR_STATE | settings changed since your read — reload and retry |
| 422 | VALIDATION | a field, timezone, practice id or category frequency is invalid |
| 503 | NOTIFICATION_SETTINGS_UNAVAILABLE | notification settings are not connected |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
office_ids is refused for offices you could not already see — the check compares against previously retained ids.Request a product entitlement
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A signed-in dashboard session token as Bearer <token>. API keys are not accepted on this route.e.g. Bearer <session token> |
Request body
| Name | Type | Description |
|---|---|---|
| product* | string | The product to enable. one of: dental_claim · eligibility · attachment_packet · remittance e.g. eligibility |
Request example
{
"product": "eligibility"
}Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| event_id* | string | The recorded request event. e.g. evt_EXAMPLE0000000000000040 |
| product* | string | The product requested. e.g. eligibility |
| kind* | string | Always REQUEST.e.g. REQUEST |
Responses
{
"request_id": "evt_ang0n1gexynjw0dmfh8v",
"event_id": "evt_fhnrqxwezs5rkevhmkc3",
"product": "eligibility",
"kind": "REQUEST"
}{
"request_id": "evt_ang0n1gexynjw0dmfh8v",
"event_id": "evt_fhnrqxwezs5rkevhmkc3",
"product": "eligibility",
"kind": "REQUEST"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | the session is not an owner or admin, or an API key is presented |
| 422 | VALIDATION | product is not one of the four products |
| 503 | REGISTRY_WRITE_FAILED | entitlement requests are not configured on this gateway |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
Look up candidate 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | A 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
| Name | Type | Description |
|---|---|---|
| organization_ids* | string | Comma-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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| organizations* | array | The candidates this identity belongs to, in candidate order. |
| organization_id* | string | The organization id. e.g. org_EXAMPLE0000000000000001 |
| name* | string | Organization display name. e.g. Blue Line Dental |
| state* | string | Membership readiness for this identity. one of: READY · SUSPENDED · PENDING · BLOCKED e.g. READY |
| authorization_version* | integer | The organization's authorization version at read time. e.g. 3 |
| can_enter* | boolean | True when the identity may enter this organization now (active membership, active organization, onboarding ready). e.g. true |
Responses
{
"request_id": "evt_ang0n1gexynjw0dmfh8v",
"organizations": [
{
"organization_id": "org_35ka7045j25e5w5h2a32",
"name": "Blue Line Dental",
"state": "READY",
"authorization_version": 3,
"can_enter": true
}
]
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or expired session token |
| 422 | INVALID_ORGANIZATION_CANDIDATES | zero or more than 100 ids, an empty value, or an id over 200 characters |
| 503 | IDENTITY_REGISTRY_UNAVAILABLE | organization access cannot be verified |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
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.
| Setting | Values | Meaning |
|---|---|---|
| Mode | test, production | A test key submits only to sandbox offices, a production key only to production offices. Reads are not mode filtered. |
Scope seller | Omit scopeIds | Every office of the seller, including offices created later. |
Scope group | Group ids of your seller | Every office currently in those groups, re-evaluated on each request. |
Scope facility | Office ids of your seller | Exactly those offices. |
| Permissions | submit, read, webhooks, admin | Any non-empty subset. GET /v1/me needs read, so a submit-only key cannot call it. |
| Minting caller | May create |
|---|---|
| Any caller | Only permissions it holds |
test mode caller | Only test keys |
seller scope | Any scope |
group scope | group keys for its own groups, or facility keys for offices in its groups. Not seller keys. |
facility scope | Only facility keys for its own offices |
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.List API 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| keys* | array | Array of key objects, every key of the seller, newest first. |
| key_id* | string | Key id. Safe to log. e.g. key_EXAMPLE0000000000000002 |
| name | string | Label given at creation. Null when empty. e.g. north office submitter |
| mode* | string | Key mode. one of: test · production e.g. test |
| scope_kind* | string | Scope kind. one of: seller · group · facility e.g. facility |
| scope_ids* | array | Array of strings. Granted ids; [<seller id>] for seller keys.e.g. ["fac_EXAMPLE0000000000000001"] |
| permissions* | array | Array of strings. Sorted permissions. e.g. ["read","submit"] |
| created_by | string | Key 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* | datetime | ISO 8601 creation time. e.g. 2026-09-14T15:00:00+00:00 |
| last_used_at | datetime | ISO 8601 time of the last successful authentication. Null if never used. e.g. null |
| revoked_at | datetime | ISO 8601 revocation time. Null while active. e.g. null |
| revoked_by | string | Key id or clerk: id that revoked the key. Null while active.e.g. null |
Responses
{
"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
}
]
}{
"error": "PERMISSION_DENIED",
"message": "permission denied",
"errors": [
{
"permission": "read"
}
],
"request_id": "evt_7rfv80tdkpyymp1xkewh"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks read |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
Create an API key
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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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* | string | Must be application/json.e.g. application/json |
Request body
| Name | Type | Description |
|---|---|---|
| name | string | Label shown in key lists. Up to 120 characters. Defaults to an empty string. e.g. north office submitter |
| mode | string | Key mode. Defaults to test.one of: test · production e.g. test |
| scopeKind | string | Scope kind. Defaults to seller.one of: seller · group · facility e.g. facility |
| scopeIds | array | Array 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* | array | Array 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
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| key_id* | string | The new key id. e.g. key_EXAMPLE0000000000000002 |
| name | string | Label. Null when empty. e.g. north office submitter |
| mode* | string | Key mode. one of: test · production e.g. test |
| scope_kind* | string | Scope kind. one of: seller · group · facility e.g. facility |
| scope_ids* | array | Array of strings. Stored grant ids; [<seller id>] when none were sent.e.g. ["fac_EXAMPLE0000000000000001"] |
| permissions* | array | Array of strings. Sorted permissions. e.g. ["read","submit"] |
| secret* | string | The 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* | boolean | Always true.e.g. true |
Responses
{
"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
}{
"error": "KEY_ESCALATION",
"message": "a key cannot grant permissions it does not hold",
"errors": [
{
"permissions": [
"webhooks"
]
}
],
"request_id": "evt_xrysvceszptzvc7p2t95"
}{
"error": "VALIDATION",
"message": "scopeIds name facilities this seller does not have",
"errors": [
{
"scope_ids": [
"fac_byjtcdyn6ftbhpxhczwr"
]
}
],
"request_id": "evt_5cx6w1pc0er88x0p46xn"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential |
| 403 | PERMISSION_DENIED | credential lacks admin |
| 403 | FACILITY_NOT_GRANTED | the caller's own group or office scope resolves to no office |
| 403 | KEY_ESCALATION | requested permissions, mode or scope beyond the caller's own |
| 422 | VALIDATION | scopeIds name offices or groups your seller does not have |
| 422 | INVALID_REQUEST | missing permissions, unknown permission or mode, more than 64 scopeIds, or an unknown field |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
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.VALIDATION, not INVALID_REQUEST. The body is strict: unknown fields are refused and strings are trimmed.Revoke an API key
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_atandrevoked_byand 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| key_id* | string | The key_... id, not the full key string. 1 to 64 characters.e.g. key_1n71fpwn02kz0k3m5bbw |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | Unique id for this request. Also returned in the X-Request-Id header.e.g. evt_EXAMPLE0000000000000031 |
| key_id* | string | The revoked key id. e.g. key_EXAMPLE0000000000000002 |
| revoked* | boolean | Always true.e.g. true |
Responses
{
"request_id": "evt_vc1amfhv11434kj9c446",
"key_id": "key_1n71fpwn02kz0k3m5bbw",
"revoked": true
}{
"error": "KEY_ALREADY_REVOKED",
"message": "that key is already revoked",
"errors": [],
"request_id": "evt_xv44qd2x2rp0sw9m8bs7"
}{
"error": "NOT_FOUND",
"message": "no such key",
"errors": [],
"request_id": "evt_3ew2wdypbanvkpamjd1n"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | missing, invalid or revoked credential, including a caller that revoked its own key earlier |
| 403 | PERMISSION_DENIED | credential lacks admin |
| 404 | NOT_FOUND | no such key in your seller |
| 409 | KEY_ALREADY_REVOKED | the key is already revoked |
| 422 | INVALID_REQUEST | key_id longer than 64 characters |
| 429 | TOO_MANY_REQUESTS | rate or concurrency limit reached |
KEY_ALREADY_REVOKED and changes nothing.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.
| Meter | Counts | Does not count |
|---|---|---|
claims_submitted | Submission records received, including held and rejected ones, in both test and production modes. A file with 12 claims counts as 1. | Idempotent replays |
eligibility_checks | Stored eligibility results, successful or vendor error. | Answers served from cache |
attachment_packets | Attachment packets created. | |
facilities_active | Offices on the account, regardless of status or period. |
Get 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
| Name | Type | Description |
|---|---|---|
| Authorization* | string | Your 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
| Name | Type | Description |
|---|---|---|
| from | date | First day of the period, inclusive (YYYY-MM-DD). Defaults to the first day of the month that contains to.e.g. 2026-09-01 |
| to | date | Last day of the period, inclusive (YYYY-MM-DD). Defaults to today in UTC.e.g. 2026-09-30 |
Request example
null
Response fields
| Name | Type | Description |
|---|---|---|
| request_id* | string | The request id. e.g. evt_EXAMPLE0000000000301 |
| seller_id* | string | Your seller id. e.g. sel_EXAMPLE0000000000001 |
| period* | object | The resolved period. |
| from* | date | The resolved first day. e.g. 2026-09-01 |
| to* | date | The resolved last day. e.g. 2026-09-30 |
| meters* | object | Counts for the period. Every meter is 0 when there is nothing to count. |
| claims_submitted* | integer | Submission 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* | integer | Stored eligibility results created, successful or vendor error. Cache answers are not counted. e.g. 130 |
| attachment_packets* | integer | Attachment packets created. e.g. 9 |
| facilities_active* | integer | Offices on your account regardless of status or period, counted up to 5,000. e.g. 3 |
| by_facility* | array | One entry per office with at least one submission in the period. Offices with none are omitted; order is not guaranteed. |
| facility_id* | string | The office id. e.g. fac_EXAMPLE0000000000001 |
| claims_submitted* | integer | Submissions for that office. The entries sum to meters.claims_submitted.e.g. 30 |
Responses
{
"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
}
]
}{
"error": "VALIDATION",
"message": "to must not precede from",
"errors": [],
"request_id": "evt_gqn68xwksccy64p7wzct"
}Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHORIZED | Missing, malformed, unknown or revoked key, or wrong secret |
| 403 | PERMISSION_DENIED | The credential lacks read |
| 422 | VALIDATION | to is earlier than from |
| 422 | INVALID_REQUEST | from or to is not a valid date |
| 429 | TOO_MANY_REQUESTS | More than 20 requests in flight or the read bucket is empty |
VALIDATION, not INVALID_REQUEST.FACILITY_NOT_GRANTED is never returned.