Claim Chaser Docs

Step 0 — From nothing to a working API key

Who this is for: a developer who has never used Claim Chaser and has no account. If you already have a login and an organization, skip to Getting a key.

Time: about 5 minutes, all self-serve. You do not need to contact sales.


The mental model

Four things must exist, in this order, before an API key can be issued:

   your user account  →  your organization  →  signed legal agreements  →  API key
   (email + password)    (name + address)      (BAA, Terms, Privacy)      (cc_live_…)

Each one gates the next. You cannot reach the Developer tab until the first three are done, and the Developer tab is the only place a key can be created.

Steps 1–4 happen in a browser. There is no API for them, by design — the legal agreement in Step 4 has to be accepted by a person. Only Step 5 onward is scriptable.


Step 1 — Create your account

Go to app.claimchaser.ai/login.

There is no separate sign-up page. Under the login form is the line "Don't have an account? Sign up" — click Sign up. The form switches to "Create your account" and takes an email address and a password.

⚠️ app.claimchaser.ai/signup and /register are not valid URLs. They redirect to /login, which looks like a rejection. Use the Sign up toggle on the login page itself.

Submit, and you will see:

Please check your email to confirm your account. If you don't see it, check your spam folder.

Step 2 — Confirm your email

You will receive "Confirm Your Signup" from noreply@claimchaser.ai, normally within seconds.

⚠️ Open the link in the same browser you signed up in

The confirmation link is tied to the browser session that created the account. If you sign up on your laptop and open the email on your phone, the link will fail with a "PKCE code verifier not found" error, and the account cannot be confirmed.

If that happens, open the link in the browser you signed up in — the same machine and the same browser profile. That recovers the account; you do not need to start over.

Clicking the link confirms your account, signs you in, and drops you straight into Step 3.

Step 3 — Create your organization

You will land on /organizations/create automatically. Every other page redirects here until it is done.

FieldNotes
Your NameThe person creating the account.
Organization NameYour company or practice group. This is the tenant that owns all your data.
Organization AddressFree text. This is not an Office address — offices are separate records you create later, each with their own address, EIN and callback number.

All three are required. Creating an organization does not create an Office or a Doctor; your org starts completely empty.

An organization is your tenant boundary. Your API key will be bound to exactly this organization and can never see or touch another one. If you are an OEM partner provisioning multiple practices, see One organization per practice.

Step 4 — Accept the legal agreements

Next you will be sent to /organizations/accept-baa, which blocks the entire application — including the Developer tab — until completed.

Three separate acceptances are required:

  1. Business Associate Agreement (BAA) — required for HIPAA. Claim Chaser handles Protected Health Information on your behalf, so this agreement is between Claim Chaser LLC and your organization as the Covered Entity.
  2. Terms and Conditions
  3. Privacy Policy

Tick all three and submit. This is enforced server-side; all three are genuinely required.

Who should click this? Whoever is authorised to bind your organization to a HIPAA agreement — not necessarily the engineer doing the integration.

Step 5 — Generate your API key

You now have access to the app. Go to Developer in the left-hand navigation (app.claimchaser.ai/developer).

Give the key a name, then choose one access level:

ChooseYou get
readall read endpoints
writeread + claim upload/update + provisioning (offices, doctors, carrier requests, credentialing)
calls:writeread + write + triggering calls

These are cumulative tiers, not a multi-select. Picking calls:write grants read and write as well — the key comes back with "scopes": ["read","write","calls:write"]. Choose the lowest tier that covers what you need.

If you choose calls:write, you also set a daily call cap (1–500, default 50). Calls cost real money; set this low while you are building. A cap of 1 is a good safety net during development.

🔑 The key is shown exactly once

The full key (format cc_live_…) is displayed once, at creation. Claim Chaser stores only a hash and cannot show it to you again. Copy it before leaving the page. If you lose it, revoke it and create another.

Step 6 — Verify it works

export CLAIM_CHASER_API_KEY="cc_live_YOUR_KEY_HERE"

curl -s -H "x-api-key: $CLAIM_CHASER_API_KEY" \
  https://app.claimchaser.ai/api/uploads/context

A working key on a brand-new organization returns your org id, empty lists for the records you have not created yet, and the full shared carrier catalogue:

{
  "organization_id": "…",
  "offices": [],
  "doctors": [],
  "carriers": [ { "id": "…", "name": "Aetna", "in_network": false, … } ],
  "valueMappings": []
}

Two things worth noting from this response:

  • offices and doctors are empty — that is correct. Creating them is the next guide.
  • carriers is not empty — the carrier catalogue is shared across all customers. in_network tells you whether your organization is linked to that carrier yet.

claim-chaser whoami does not work with an API key — it uses the interactive login path and will report "Not logged in". Use a keyed read like the call above to verify a key.

You are done. Continue to the API onboarding guide to create your first office, doctor and claim.


Troubleshooting

"Too many email requests. Please wait N seconds"

Resend is rate-limited to roughly one email per minute. The message names the exact wait. Wait it out and resend — the email arrives.

"PKCE code verifier not found in storage"

You opened the confirmation link in a different browser, a different device, or after clearing cookies. Open the same link again in the browser you signed up in and it will confirm normally.

"This confirmation link has expired"

Confirmation links are time-limited. Use "Resend confirmation email" to get a fresh one — it is rate-limited to about one per minute, so if you are told to wait, wait and try again.

403 {"error":"Forbidden: Invalid origin"}

You are calling a Developer-page endpoint (/api/developer/…) directly. Those are browser-only and CSRF-protected. Key creation is not scriptable — use the Developer page.

401 {"error":"Unauthorized"}

Missing, malformed, revoked, or expired key. Check you are sending the header x-api-key — not Authorization: Bearer.


What you cannot do with an API key

A person must do these in the app. There is no key-reachable endpoint:

  • Creating an account, an organization, or accepting the legal agreements (this page)
  • Creating, revoking, or re-capping API keys
  • Registering webhook endpoints or rotating their signing secrets
  • Editing the carrier catalogue or carrier phone numbers, or turning a carrier on/off for your org
  • Turning voice calling on or off

One organization per practice

If you are provisioning multiple practices (an OEM integration), each practice is a separate organization with its own API key, and each is its own Covered Entity under the BAA in Step 4.

The practice's authorised signatory must accept the BAA (Step 4). A partner cannot accept it on a practice's behalf unless a separate written agreement expressly permits it. Steps 1–4 are browser-only, so provisioning a new practice is not fully headless; if you need an arrangement to complete them for a practice, contact us before onboarding practices.


Appendix — corrections to the existing /docs/api §10 script

The current "full journey, end to end" script does not run as written. Corrected version:

export KEY="cc_live_YOUR_KEY_HERE"; export BASE="https://app.claimchaser.ai"

# 1. office — NOTE: "create": true is REQUIRED.
#    Without it, a new organization gets {"matched":false,"candidates":[]} and NOTHING is created.
OFFICE_ID=$(curl -sX POST -H "x-api-key: $KEY" -H "Content-Type: application/json" $BASE/api/offices \
  -d '{"name":"Main Street Clinic","address":"123 Main St","callback_number":"2085551234","ein":"123456789","create":true}' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')

# 2. doctor — "create": true likewise. npi is REQUIRED (10 digits), unlike in the app.
DOCTOR_ID=$(curl -sX POST -H "x-api-key: $KEY" -H "Content-Type: application/json" $BASE/api/doctors \
  -d "{\"name\":\"Dr. Smith\",\"npi\":\"1234567890\",\"office_id\":\"$OFFICE_ID\",\"create\":true}" \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')

# 3. carrier — ⚠️ CHECK match.exact BEFORE USING THE RESULT.
#    This endpoint fuzzy-matches. A request for a carrier not in the catalogue can silently
#    return a DIFFERENT carrier. Only trust the result when "exact": true.
curl -sX POST -H "x-api-key: $KEY" -H "Content-Type: application/json" $BASE/api/carrier-requests \
  -d '{"requested_name":"Cigna Dental"}'
#    → {"matched":true,"carrier_id":"…","carrier_name":"Cigna Dental","match":{"exact":true,"score":1}}
#    If "exact" is false, do NOT use carrier_id — look the carrier up in GET /api/carriers instead.

# 4. credential — NOTE: "kind" is REQUIRED. Keyed callers send "customer".
curl -sX POST -H "x-api-key: $KEY" -H "Content-Type: application/json" $BASE/api/doctor-carriers/turn-on \
  -d "{\"doctor_id\":\"$DOCTOR_ID\",\"carrier_id\":\"$CARRIER_ID\",\"kind\":\"customer\"}"

# 5. upload the claim
#    NOTE: claim_status is matched case-insensitively; "Denied", "DENIED" and "denied" all work.
#    An unrecognised status is dropped (claim defaults to Open, which is dialable) and reported on the
#    response in ignored_status_values: [{ row, claim_number, value }], not stored blindly.
curl -sX POST -H "x-api-key: $KEY" -H "Content-Type: application/json" $BASE/api/uploads/claims \
  -d "{\"action\":\"insert\",\"claims\":[{\"patient_name\":\"Jane Doe\",\"subscriber_id\":\"W123\",\"carrier_id\":\"$CARRIER_ID\",\"office_id\":\"$OFFICE_ID\",\"doctor_id\":\"$DOCTOR_ID\",\"date_of_birth\":\"1985-04-12\",\"date_of_service\":\"2026-05-01\",\"claim_status\":\"Denied\"}]}"

# 6. confirm callable (no dial, no bill)
curl -s -H "x-api-key: $KEY" "$BASE/api/claims/$CLAIM_ID/callable"

Other corrections needed in the existing docs

Doc statementReality
match_type is "npi" / "name"Returns "exact", "identifier", "fuzzy_auto"
dispatch_blockers "possible values are VOICE_OFF and HOLIDAY_WEEKEND_FLOOR"Eight values, and knowing which one you have is not enough — see the section below
event_types drawn from two valuesThree: carrier.request_resolved is also valid
"You can save a provider before you have their NPI"True in the app, false in the API — npi is required
claim_status "Accepts the app's status strings"Accepts any string; only the exact-cased documented values are dialable
Nothing documents POST /api/claims/deleteIt exists and is partner-reachable
Nothing documents POST /api/uploads/value-mappingsIt exists (201), lowercases input_value, and is only mentioned inside another endpoint's error message. Results are readable via valueMappings in GET /api/uploads/context

Reading dispatch_blockers — what to do, not just what it means

Placed here because it is the first thing people need after they get a key, and it is not documented anywhere else yet. Its permanent home is the API Reference.

GET /api/claims/{id}/callable and the make-call dry run both return a dispatch_blockers array. "callable": false is usually not a problem. What matters is which group the blocker is in, because the three groups need three different responses from you.

You do not need a calls:write key to check callability. GET /api/claims/{id}/callable works with read. Only the make-call dry run needs calls:write — which is why the CLI's claims callable command requires it, since that wrapper calls make-call. If you are only checking whether a claim is ready, use the endpoint directly.

1 · Fix your claim — these are about your data

BlockerMeaning
MISSING_FIELDSthe claim lacks data a call needs
STATUS_NOT_DIALABLEclaim_status isn't a dialable value (e.g. a terminal status like Paid)
NO_CALLABLE_PHONEno usable insurance_phone, and none resolvable from the carrier
PAIDa payment amount is recorded, so we will not chase it

2 · Wait — these clear on their own

BlockerClears when
CARRIER_HOURSthe carrier's calling hours start
HOLIDAY_WEEKEND_FLOORthe next business day
COOLDOWNthe cooldown window since the last call elapses

3 · Someone must act — this one never clears by waiting

BlockerWhat to do
VOICE_OFFVoice calling is switched off for the organization. Turn it on — you can do this with your API key.

⚠️ VOICE_OFF will not clear on its own — but you can clear it in one request

No amount of polling changes it. It stays until voice is switched on. You do not need the app and you do not need a person — your existing write-scope key can do it:

curl -sX POST -H "x-api-key: $KEY" -H "Content-Type: application/json" \
  $BASE/api/voice-settings -d '{"enabled": true}'
{"success":true,"enabled":true}

Check the current state any time:

curl -s -H "x-api-key: $KEY" $BASE/api/voice-settings
{"enabled":false,"schedule_last_turned_on_at":null,"schedule":{ … }}

The field is enabled — sending voice_on returns 400 {"error":"enabled must be a boolean"}.

⚠️ Turning voice on starts real calls and real spend for every claim in the organization that is already callable. Confirm with the practice before you flip it, and use GET /api/claims/{id}/callable first to see what would go out.

This is the blocker that costs people time: VOICE_OFF sits alongside genuinely temporary blockers like COOLDOWN, so it looks like something that will resolve itself. It will not — and unlike the others, it is entirely in your hands.

A correctly-provisioned claim on an organization that has not enabled calling yet returns:

{"callable":false,"dispatch_blockers":["COOLDOWN","VOICE_OFF"]}

Nothing is wrong with that claim. COOLDOWN clears by itself; VOICE_OFF is waiting on you.