API
Seven operations
Send a bill and the Explanation of Benefits your insurer issued for the same care. Get back where the two disagree, and a letter you can send about it.
Status: live. The base URL is https://app.medbillanalyzer.com. Most operations answer immediately; check_medical_bill reads the documents with a model and usually takes 10–30 seconds, so allow a timeout of a few minutes. The document below is generated from the code that serves these operations, not written alongside it.
Shape
What the calls are
| Call | Does | Costs | Access |
|---|---|---|---|
| POST /v1/cases | Reads the documents and compares them. Returns how many findings there are and how much is in question. | Free | Write |
| GET /v1/cases/{id} | The same summary, plus every finding in detail once the case is unlocked. | Free / paid | Read |
| POST /v1/cases/{id}/unlock | Given a Stripe shared payment token, charges $10 and unlocks the case in the same call. Without one, returns a checkout URL and charges nothing itself. | $10 per bill | Sensitive write |
| POST /v1/cases/{id}/outputs | The dispute letter and the phone script, in the patient's name. | Paid | Read |
| GET /v1/cases/{id}/export | Everything held about the case, as JSON. Needs no token and costs nothing — you cannot be charged to see what we hold about you. | Free | Read |
| POST /v1/cases/{id}/outcome | What came of the letter — corrected, refused, ignored, or never sent. The answer is counted in aggregate and never recorded against the case. | Free | Write |
| DELETE /v1/cases/{id} | Destroys the case. Needs the one-time delete token issued when it was created; the case id alone reads, it does not erase. | Free | Sensitive write |
Access is how each call should be approved. A Read changes nothing. A Write creates or changes a case. A Sensitive write is a purchase or a deletion, and the person should approve it every time.
Seven, deliberately. Four do the work; two exist because a person is entitled to take their record back and to destroy it, and a right nobody can exercise through the API is not a right; one asks what came of it. An agent works better against a surface it can hold at once, so there are no other operations — only the file handoff below, which carries bytes rather than doing anything to a case.
Handoff
A file you cannot attach
An Explanation of Benefits usually sits behind the insurer’s login. There is nothing to attach and no link this service can follow — it sends no cookies and holds no credentials — so the patient downloads it and the file is handed over in two steps, then named in the scan as { "upload_id": "…" }.
| Call | Does | Access |
|---|---|---|
| POST /v1/uploads | Reserves a slot: one file, 15 minutes, held in memory only. Returns upload_id and upload_url. | Write |
| PUT /v1/uploads/{upload_id} | The file itself as the request body — raw bytes, not JSON or base64. PDF, JPEG, PNG, GIF or WebP, up to 12 MB. upload_url is where this goes; it is not a page to send the patient to. | Write |
| GET /v1/capabilities | Whether this deployment can read an uploaded document at all. Ask before asking a patient for a file. | Read |
Two things that bite
Read these before you call it
- Never add the findings together. amount_in_question_cents is already de-duplicated. Two findings routinely describe the same dollars from different angles — a line billed above the contracted rate is usually also part of a bill that asks for more than the plan says is owed — so summing them overstates the problem in a letter a patient signs and sends. Use the total we give you.
- next_step is addressed to you, not to the patient. It says things like "Ask the patient for a clearer copy". If you are rendering straight to a person, use you_can, which is the same guidance in the second person.
- The comparison is not done by a model. A reader transcribes what is printed; fixed rules decide what it means. The same documents always produce the same findings, and every finding names the rule behind it.
- Send the document, not your own transcription. Both work, but bill_document is read by an extractor tuned for these forms and measured against a field-accuracy harness. Your own parse is not.
Errors
Every failure says what to do
Each error carries a stable code to branch on, a message that is safe to show a patient, and guidance. The status is specific on purpose: a generic 4xx sends an agent into a debugging loop that burns the user's usage meter. The OpenAPI document lists, for each operation, the statuses it can actually answer with and the codes under each.
| Status | Means |
|---|---|
| 402 | The case is not unlocked. Call unlock first. |
| 409 | Nothing to unlock — there were no findings. Do not offer to sell anything. |
| 415 | We cannot read that kind of file. |
| 422 | The document was too poor to check. Ask for a clearer copy. |
| 429 | Back off and retry. |
| 503 | A dependency is down, or the feature is not switched on here. next_step says which, and what to do. |
Also
The same operations as MCP tools
The same seven operations are also served as MCP tools at https://app.medbillanalyzer.com/mcp (Streamable HTTP, no sign-in), with the same names and the same schemas — both faces are generated from one declaration, so they cannot drift apart, and they share one store: a case created through either is the same case. Each tool carries a title and read-only or destructive annotations. To use them in Claude, add a custom connector; in Grok, use https://app.medbillanalyzer.com/mcp/grok, the same server, added the same way. check_medical_bill, get_case, unlock_case, get_case_outputs, delete_case, export_case, report_outcome.
The machine-readable description is at /openapi.json. It is generated from the routing table, so it describes what the server actually answers rather than what someone remembered to write down.