Skip to content

Errors

The API answers errors with an HTTP status and a JSON body that names the error. Branch on the code, not the message.

Error shape

Every endpoint except the token endpoint returns errors in this shape:

Error body
{"error": {"code": "insufficient_credits", "message": "Not enough credits for this analysis."}}
  • code is stable within /v1. Use it in your code.
  • message is a sentence for people. It can change, and it never repeats what you sent: a validation error names the field, never its value, so error responses and your logs stay free of note text.

The token endpoint, POST /v1/oauth/token, follows OAuth 2.0 and returns {"error": "<code>"}. For its codes, see Token endpoint errors.

Error codes

StatusCodeCauseWhat to do
400invalid_requestThe body or a parameter is invalid; the message names the fields. Also a body that isn't a JSON object or isn't valid JSON (or UTF-8), an unknown field, an unknown jurisdiction code, an invalid Idempotency-Key header, an invalid cursor, a time before 2000-01-01 or after 2100-01-01, or client_reference in a query string.Fix the request. Don't retry it unchanged.
401unauthorizedThe access token is missing, malformed, or expired; its key was revoked; or its organization was deleted. The response has WWW-Authenticate: Bearer.Get a new token and retry once. If it fails again, check the key.
402insufficient_creditsThe balance doesn't cover the note, the whole batch, or the notes to retry. Nothing is charged; for a batch, nothing is stored or held.Buy credits on the Billing page, or send fewer notes.
403baa_resign_requiredThe organization is past its due date to sign an updated BAA, so new notes and batches are paused.Ask an owner to sign the updated BAA in the app, in Settings > BAA. Processing resumes as soon as it's signed.
403org_suspendedMedChartScan support suspended the organization. New notes and batches (POST /v1/analyses, POST /v1/batches, batch retries) are refused; reads, search and deletes still work.Write to support@medchartscan.io. Don't retry until support restores the organization.
403org_lockedMedChartScan support locked the organization. Every request is refused, and the token endpoint issues no token.Write to support@medchartscan.io. Don't retry.
404not_foundThe ID doesn't exist, is malformed, belongs to another organization, or was created in the web app. Also an idempotent replay of a result that was deleted, and an unknown path.Check the ID. Don't retry.
409idempotency_conflictThe Idempotency-Key was already used with a different body or on the other endpoint.Use a new key for a new request.
409idempotency_in_progressThe first request with this Idempotency-Key is still running. The response has Retry-After: 2.Retry with the same key after Retry-After seconds.
409not_retryableA batch note you asked to retry didn't fail, or its 7-day retry window has passed. Nothing changes.Retry only notes with "retryable": true.
409analysis_runningYou asked to delete a note that's still running. Nothing is deleted.Delete it once it has finished.
413note_too_longA note is longer than 100,000 characters.Shorten or split the note.
413request_too_largeThe request body is larger than 1 MB, or 12 MB for POST /v1/batches.Send fewer or shorter notes per request.
422test_data_requiredThe organization is in test mode and the request didn't send "test_data": true.Send synthetic notes with "test_data": true, or accept the BAA.
429rate_limitedThe organization's per-minute limit on analyses is reached (Retry-After: 60), or the key sent more than 600 reads (GET requests and POST /v1/analyses/search) this minute.Wait for Retry-After seconds, then retry.
429too_many_uncharged_runs25 of the organization's notes ended out of scope or failed in the last 24 hours, or a batch would put more notes in progress than the uncharged limit allows.Wait for Retry-After seconds, or send a smaller batch. Check why your notes fail or are out of scope.
429too_many_batchesThe organization has 5 unfinished batches.Wait for a batch to finish, then retry.
500internal_errorAn unexpected error occurred on the server. Nothing is charged.Retry with backoff. If it continues, contact support.
502analysis_failedThe analysis couldn't be completed. Nothing is charged.Send the note again, with a new Idempotency-Key if you used one.

Other client errors, such as a method that a path doesn't support, return the status with the code http_error. A 405 has an Allow header that lists the methods the path supports.

Validation messages

A 400 invalid_request message names what's wrong without repeating your values:

MessageCause
Invalid request: document_text, cpts.0.codeThose fields are missing or invalid. List positions count from 0.
Invalid request: unknown field cpts.0.unitA field that the endpoint doesn't take, here inside the first CPT. Requests with unknown fields are refused rather than ignored, so a misspelled field can't change what the request does. An unknown key that isn't a field name of this API shows as ?, because it could be patient data.
Invalid request: body must be a JSON object.The body is missing, or is an array, a string, a number, or form text.
Invalid request: body is not valid JSON.The body can't be parsed as JSON.
Invalid request: body is not valid UTF-8 JSON.The body has bytes that aren't UTF-8.

Retry safely

Retry only the errors that can succeed later: 401 (after you get a new token), 409 idempotency_in_progress, 429, 500, and 502, plus network errors and timeouts. A 502 counts toward the uncharged notes limit, so don't retry the same failing note many times. For a POST request, send an Idempotency-Key so that a retry can't charge twice. For more information, see Idempotency.

Wait between attempts, and wait longer after each one: for example 2, 4, then 8 seconds, plus a random part of a second. When the response has a Retry-After header, wait at least that many seconds.

Python
import random
import time
import uuid

import requests

RETRYABLE = {409, 429, 500, 502}


def analyze(note: str, token: str) -> dict:
    key = str(uuid.uuid4())  # one key for every attempt of this note
    for attempt in range(5):
        try:
            resp = requests.post(
                f"{API}/v1/analyses",
                headers={"Authorization": f"Bearer {token}", "Idempotency-Key": key},
                json={"document_text": note},
                timeout=300,
            )
        except requests.RequestException:
            resp = None
        if resp is not None and resp.status_code not in RETRYABLE:
            resp.raise_for_status()
            return resp.json()
        if resp is not None and resp.status_code == 409:
            if resp.json()["error"]["code"] != "idempotency_in_progress":
                resp.raise_for_status()
        if resp is not None and resp.status_code == 502:
            key = str(uuid.uuid4())  # the same key would replay the failure
        wait = 2 ** (attempt + 1) + random.random()
        if resp is not None and "Retry-After" in resp.headers:
            wait = max(wait, int(resp.headers["Retry-After"]))
        time.sleep(wait)
    raise RuntimeError("The analysis didn't finish after 5 attempts.")
Errors · MedChartScan E/M