Skip to content

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.

FieldTypeDescription
document_textstringThe note, 20 to 100,000 characters.
cptsarray of objectsUp 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 []).
icdsarray of stringsUp to 100 ICD-10 codes, 3 to 8 characters each, such as E11.9.
jurisdictionstringThe 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_typestringOptional context about the visit, up to 100 characters.
provider_typestringOptional context about the clinician, up to 100 characters.
patient_demographicsobjectOptional age (integer), gender, and dob (strings of up to 100 characters).
active_problemsarrayUp 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_referencestringYour 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_databooleanRequired 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

HeaderDescription
AuthorizationRequired. Bearer followed by your access token.
Content-TypeRequired. application/json.
Idempotency-KeyOptional. 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:

200 OK
{
  "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:

200 OK
{"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}
FieldDescription
idThe analysis ID.
statussucceeded or out_of_scope in this response. Results that you read later can also be queued, running, or failed (batch notes).
is_testtrue for a result created in test mode.
expires_atWhen a test result is deleted; null otherwise.
credits_charged1 when the status is succeeded, otherwise 0.
credits_remainingYour organization's credit balance after this analysis.
organization_statuspending, active, or paused.
client_referenceThe value you sent, or null.
channelHow the result was run: api for one POST /v1/analyses call, api_batch for a note of a batch.
submitted_codeThe 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_changeHow 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_countThe number of entries in report.revenueAndCompliance.missedRevenueOpportunities. null when the status isn't succeeded.
denial_risk_countThe number of entries in report.revenueAndCompliance.denialRiskAssessment. null when the status isn't succeeded.
reportThe 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:

502 Bad Gateway
{"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

StatusCodeCause
400invalid_requestA field is missing or invalid (the message names the field), or the Idempotency-Key header is invalid.
401unauthorizedThe access token is missing, invalid, or expired.
402insufficient_creditsThe balance is 0. Nothing is charged.
403baa_resign_requiredThe organization is paused until an owner signs the updated BAA.
409idempotency_conflictThe Idempotency-Key was used with a different request.
409idempotency_in_progressA request with this Idempotency-Key is still running.
413note_too_longdocument_text is longer than 100,000 characters.
413request_too_largeThe request body is larger than 1 MB.
422test_data_requiredThe organization is in test mode and the request didn't send "test_data": true.
429rate_limitedThe organization's per-minute limit is reached. Wait for Retry-After seconds.
429too_many_uncharged_runs25 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.
502analysis_failedThe 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:

404 Not Found
{"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 parameterDescription
statusOnly results with this status: queued, running, succeeded, failed, or out_of_scope.
created_afterOnly 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_beforeOnly results created before this ISO 8601 time (exclusive), with the same rules.
limitResults per page, 1 to 100. Default 50.
cursorThe next_cursor value from the previous page.
curl -s "https://api.medchartscan.io/v1/analyses?status=succeeded&limit=10" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
200 OK
{
  "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.

FieldDescription
client_referenceOnly results with exactly this reference.
channelapi for single calls only, or api_batch for batch notes only.
coding_changeOnly results with this coding_change: upgraded, maintained, downgraded, changed, or not_submitted.
has_missed_revenuetrue: only results with at least one missed revenue opportunity. false: only succeeded results with none.
has_denial_risktrue: only results with at least one denial risk. false: only succeeded results with none.
status, created_after, created_before, limit, cursorAs 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}'
200 OK
{"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.

Analyses · MedChartScan E/M