Analyses
An analysis is one note and its result. You create one with a single request and get the result in the response, or create many at once with a batch.
Analyze a note
POST/v1/analyses
The request waits for the analysis to finish and returns the result. Most notes take under a minute. When the analysis service is slow and retries, a request can take up to about four minutes, so set your client timeout to 300 seconds.
If the connection drops before you get a response, send the request again with the same Idempotency-Key header: you
get the original result and aren't charged twice. For more information, see Idempotency.
Request body
Only document_text is required.
| Field | Type | Description |
|---|---|---|
document_text | string | The note, 20 to 100,000 characters. |
cpts | array of objects | Up to 50 CPT codes billed for the visit, for the Medicare checks. Each has code (5 characters), units (1 to 99, default 1), and modifiers (up to 4 strings of up to 4 characters, default []). |
icds | array of strings | Up to 100 ICD-10 codes, 3 to 8 characters each, such as E11.9. |
jurisdiction | string | The two-letter code, in capitals, of the state where the service took place, such as TX: one of the 50 states, DC, or a territory (AS, GU, MP, PR, VI). Any other code gets 400 invalid_request. It selects the Medicare rules for the checks. If you leave it out, your organization's default state applies, when one is set. |
visit_type | string | Optional context about the visit, up to 100 characters. |
provider_type | string | Optional context about the clinician, up to 100 characters. |
patient_demographics | object | Optional age (integer), gender, and dob (strings of up to 100 characters). |
active_problems | array | Up to 50 entries. Each is a string of up to 500 characters, or an object with icd10, description, onset_date, and status: description up to 500 characters, the others up to 100. |
client_reference | string | Your own identifier for the note, such as an encounter ID, up to 128 characters. It comes back with the result and is never logged. |
test_data | boolean | Required as true while your organization is in test mode. Default false. For more information, see Test mode. |
The Medicare checks in the report (cmsEditsAnalysis) run only when you send at least one CPT code and a state applies.
Fields that aren't in this table get 400 invalid_request, and so do unknown fields inside cpts,
patient_demographics, and active_problems: a misspelled field is refused instead of being ignored. The message
names an unknown field only when it's a field name of this API, and shows any other as ?.
The whole request body can be at most 1 MB. A larger body gets 413 request_too_large before anything is read.
Headers
| Header | Description |
|---|---|
Authorization | Required. Bearer followed by your access token. |
Content-Type | Required. application/json. |
Idempotency-Key | Optional. 1 to 255 printable ASCII characters, such as a UUID. |
Example
curl -s https://api.medchartscan.io/v1/analyses \
--max-time 300 \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2e0a-8d3b-4c55-9a1e-2b7d4f0c9e11" \
-d '{
"document_text": "Established patient seen for follow-up of type 2 diabetes and hypertension. Reports good adherence to metformin; no hypoglycemia. BP 138/86. A1c 7.4% reviewed. Assessment: diabetes, improving; hypertension, not at goal. Plan: increase lisinopril to 20 mg daily, recheck BMP in 2 weeks, follow up in 3 months.",
"cpts": [{"code": "99214", "units": 1, "modifiers": []}],
"icds": ["E11.9", "I10"],
"jurisdiction": "TX",
"client_reference": "enc-2026-10-25-0001"
}'Response
A finished analysis returns 200 with one of two statuses.
Succeeded. The report is in report, and one credit is charged:
{
"id": "1b593369-77ed-4eb1-8902-51a3af9f7e60",
"status": "succeeded",
"is_test": false,
"expires_at": null,
"credits_charged": 1,
"credits_remaining": 49,
"organization_status": "active",
"client_reference": "enc-2026-10-25-0001",
"channel": "api",
"submitted_code": "99214",
"coding_change": "maintained",
"missed_revenue_count": 0,
"denial_risk_count": 0,
"report": {
"codingAnalysis": {
"finalCptCode": "99214",
"codingBasis": "Medical Decision Making (MDM)",
"governingGuideline": "Health E/M Coding Guidelines (CY 2026)"
},
"timeBasedJustification": null,
"mdmAnalysis": {
"finalMdmLevel": "Moderate",
"mdmElements": [
{"elementName": "Problems Addressed", "levelAchieved": "Moderate", "justification": "The note names the problems addressed at this visit.", "exactSnippet": "Established patient seen for follow-up of type 2 diabetes and hypertension"},
{"elementName": "Data Reviewed", "levelAchieved": "Moderate", "justification": "The note records data reviewed or ordered.", "exactSnippet": "A1c 7.4% reviewed"},
{"elementName": "Risk of Management", "levelAchieved": "Moderate", "justification": "The note records a management decision.", "exactSnippet": "Plan: increase lisinopril to 20 mg daily, recheck BMP in 2 weeks, follow up in 3 months"}
]
},
"revenueAndCompliance": {"missedRevenueOpportunities": [], "denialRiskAssessment": []},
"supplementalCodes": {"addOnCodes": [], "modifiers": []}
}
}Out of scope. The note isn't an office or outpatient E/M visit, such as a telephone call, chronic care management, a procedure-only visit, or psychotherapy. There's no report, and nothing is charged:
{"id": "5cf9e37c-8d56-4f60-8585-91b24275b679", "status": "out_of_scope", "is_test": false, "expires_at": null, "credits_charged": 0, "credits_remaining": 49, "organization_status": "active", "client_reference": null, "channel": "api", "submitted_code": null, "coding_change": null, "missed_revenue_count": null, "denial_risk_count": null, "report": null}| Field | Description |
|---|---|
id | The analysis ID. |
status | succeeded or out_of_scope in this response. Results that you read later can also be queued, running, or failed (batch notes). |
is_test | true for a result created in test mode. |
expires_at | When a test result is deleted; null otherwise. |
credits_charged | 1 when the status is succeeded, otherwise 0. |
credits_remaining | Your organization's credit balance after this analysis. |
organization_status | pending, active, or paused. |
client_reference | The value you sent, or null. |
channel | How the result was run: api for one POST /v1/analyses call, api_batch for a note of a batch. |
submitted_code | The first E/M code among the cpts you sent. Add-on codes (such as 99417) and procedure codes are skipped. null when you sent no E/M code, and when the status isn't succeeded. |
coding_change | How the supported code (report.codingAnalysis.finalCptCode) compares with submitted_code: upgraded or downgraded (a higher or lower level in the same office-visit family: new patient 99202–99205, established 99211–99215), maintained (the same code), changed (another family, such as 99204 submitted and 99214 supported, or no single office code supported), or not_submitted (no E/M code sent). null when the status isn't succeeded. |
missed_revenue_count | The number of entries in report.revenueAndCompliance.missedRevenueOpportunities. null when the status isn't succeeded. |
denial_risk_count | The number of entries in report.revenueAndCompliance.denialRiskAssessment. null when the status isn't succeeded. |
report | The report when the status is succeeded, otherwise null. For every field, see The report. |
The review fields let you find the results to look at first, such as every downgraded result or every result with missed revenue, without reading each report. To filter by them, see Search results.
When an analysis fails
If the analysis can't be completed, the response is 502, and nothing is charged:
{"error": {"code": "analysis_failed", "message": "The analysis could not be completed. No credits were charged."}}You can send the note again. If the failed request had an Idempotency-Key, use a new key: the same key returns the
same 502.
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | A field is missing or invalid (the message names the field), or the Idempotency-Key header is invalid. |
401 | unauthorized | The access token is missing, invalid, or expired. |
402 | insufficient_credits | The balance is 0. Nothing is charged. |
403 | baa_resign_required | The organization is paused until an owner signs the updated BAA. |
409 | idempotency_conflict | The Idempotency-Key was used with a different request. |
409 | idempotency_in_progress | A request with this Idempotency-Key is still running. |
413 | note_too_long | document_text is longer than 100,000 characters. |
413 | request_too_large | The request body is larger than 1 MB. |
422 | test_data_required | The organization is in test mode and the request didn't send "test_data": true. |
429 | rate_limited | The organization's per-minute limit is reached. Wait for Retry-After seconds. |
429 | too_many_uncharged_runs | 25 of your organization's notes ended out of scope or failed in the last 24 hours. Wait for Retry-After seconds. For more information, see Rate limits. |
502 | analysis_failed | The analysis couldn't be completed. Nothing is charged. |
Get a result
GET/v1/analyses/{id}
Returns one result in the same shape as the POST /v1/analyses response. credits_remaining and
organization_status are current values. Use this endpoint to read batch notes, too.
curl -s https://api.medchartscan.io/v1/analyses/$ANALYSIS_ID \
-H "Authorization: Bearer $ACCESS_TOKEN"An ID that doesn't exist, belongs to another organization, or was created in the web app returns 404:
{"error": {"code": "not_found", "message": "Not found."}}List results
GET/v1/analyses
Lists the results created through the API in your organization, newest first.
| Query parameter | Description |
|---|---|
status | Only results with this status: queued, running, succeeded, failed, or out_of_scope. |
created_after | Only results created after this ISO 8601 time (exclusive), such as 2026-10-01T00:00:00Z. A time without an offset is UTC. Times before 2000-01-01 or after 2100-01-01 get 400 invalid_request. |
created_before | Only results created before this ISO 8601 time (exclusive), with the same rules. |
limit | Results per page, 1 to 100. Default 50. |
cursor | The next_cursor value from the previous page. |
curl -s "https://api.medchartscan.io/v1/analyses?status=succeeded&limit=10" \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"items": [
{"id": "ef7719d3-2e9c-4dc0-b4df-13e3363ebf7b", "status": "failed", "created_at": "2026-10-25T05:09:30.368638Z", "is_test": false, "expires_at": null, "client_reference": null, "batch_id": null, "final_cpt_code": null, "channel": "api", "submitted_code": null, "coding_change": null, "missed_revenue_count": null, "denial_risk_count": null},
{"id": "5cf9e37c-8d56-4f60-8585-91b24275b679", "status": "out_of_scope", "created_at": "2026-10-25T05:09:30.348075Z", "is_test": false, "expires_at": null, "client_reference": null, "batch_id": null, "final_cpt_code": null, "channel": "api", "submitted_code": null, "coding_change": null, "missed_revenue_count": null, "denial_risk_count": null}
],
"next_cursor": "MjAyNi0xMC0yNVQwNTowOTozMC4zNDgwNzUrMDA6MDB8NWNmOWUzN2MtOGQ1Ni00ZjYwLTg1ODUtOTFiMjQyNzViNjc5"
}Each item has the result's final_cpt_code, channel, the review fields (submitted_code, coding_change,
missed_revenue_count, denial_risk_count, described under Response) and, for a batch note, its
batch_id. To get the report, get the result by its ID. An invalid status, limit, or cursor returns 400 invalid_request.
Search results
POST/v1/analyses/search
Takes the same filters as the list, plus client_reference, channel, and the review filters, in a JSON body. A
reference can identify a patient, so it never goes in a URL. These filters are accepted only in the body:
GET /v1/analyses?client_reference=... or GET /v1/analyses?coding_change=... returns 400.
| Field | Description |
|---|---|
client_reference | Only results with exactly this reference. |
channel | api for single calls only, or api_batch for batch notes only. |
coding_change | Only results with this coding_change: upgraded, maintained, downgraded, changed, or not_submitted. |
has_missed_revenue | true: only results with at least one missed revenue opportunity. false: only succeeded results with none. |
has_denial_risk | true: only results with at least one denial risk. false: only succeeded results with none. |
status, created_after, created_before, limit, cursor | As in List results. |
Every field is optional, and filters combine. For example, {"has_missed_revenue": true, "coding_change": "downgraded"}
returns the downgraded results that also have missed revenue.
A search is a read: it counts toward the key's 600 reads per minute, like a GET. For more information, see
Rate limits.
curl -s https://api.medchartscan.io/v1/analyses/search \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"client_reference": "enc-2026-10-25-0001", "limit": 10}'{"items": [{"id": "1b593369-77ed-4eb1-8902-51a3af9f7e60", "status": "succeeded", "created_at": "2026-10-25T05:09:30.280592Z", "is_test": false, "expires_at": null, "client_reference": "enc-2026-10-25-0001", "batch_id": null, "final_cpt_code": "99214", "channel": "api", "submitted_code": "99214", "coding_change": "maintained", "missed_revenue_count": 0, "denial_risk_count": 0}], "next_cursor": null}Delete a result
DELETE/v1/analyses/{id}
Deletes the result with its note and report. The response is 204 with an empty body. If the note is a batch note
that's still queued, it never runs and its held credit returns to your balance. A note that's running can't be deleted:
the response is 409 analysis_running, and you can delete it once it has finished. Deleting results doesn't give
back rate-limit calls. A stored idempotent response for the
result is deleted too, so a replay of the original request returns 404 instead of the report.
curl -s -X DELETE https://api.medchartscan.io/v1/analyses/$ANALYSIS_ID \
-H "Authorization: Bearer $ACCESS_TOKEN"Deleting the same ID again, or an ID your keys can't see, returns 404 not_found.