Skip to content

Authentication

The API uses the OAuth 2.0 client credentials flow. You exchange an API key for an access token, then send the token with every request.

API keys

An API key has two parts:

PartFormatWhere you see it
Client IDmcs_ci_ followed by 24 charactersAlways, on the API keys page
Client secretmcs_sk_ followed by 43 charactersOnce, when you create the key

Owners and admins create and revoke keys on the API keys page in the app. MedChartScan stores only a hash of the secret, so it can't show the secret again. If you lose a secret, create a new key and revoke the old one.

  • An organization can have up to 25 active keys. Revoked keys don't count.
  • Your organization's owners get an email each time someone creates or revokes a key, and the audit log records it.
  • Every key of an organization sees the same results: the analyses and batches created through the API in that organization.

A key can analyze notes, run batches, read, list, and delete what the API created, and read the credit balance. It can't manage keys, members, billing, or the BAA, and it can't see results created in the web app.

Warning:

Keep the client secret on your server. Don't put it in browser code, mobile apps, URLs, or source control. Load it from an environment variable or a secret manager.

Get an access token

POST/v1/oauth/token

Send grant_type=client_credentials with the key's client ID and secret. You can send the credentials in one of three ways, and only one per request:

  • A form body (application/x-www-form-urlencoded) with client_id and client_secret.
  • A JSON body with the same three fields.
  • HTTP Basic authentication with the client ID as the user name and the secret as the password, and grant_type in a form body.
curl -s https://api.medchartscan.io/v1/oauth/token \
  -d grant_type=client_credentials \
  -d client_id="$CLIENT_ID" \
  -d client_secret="$CLIENT_SECRET"

The same request with a JSON body:

curl
curl -s https://api.medchartscan.io/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d "{\"grant_type\": \"client_credentials\", \"client_id\": \"$CLIENT_ID\", \"client_secret\": \"$CLIENT_SECRET\"}"

The same request with HTTP Basic authentication:

curl
curl -s https://api.medchartscan.io/v1/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials

A successful response has the token and its lifetime in seconds. It carries Cache-Control: no-store, so don't cache it in a shared cache.

200 OK
{"access_token": "mcs_at_0TSMMdMumGLrM21zFYAa_tguW2mymxcUh51LgRHIH2g", "token_type": "Bearer", "expires_in": 3600}

The request body can be at most 8 KB.

Use the token

Send the token in the Authorization header of every other request:

curl
curl -s https://api.medchartscan.io/v1/credits \
  -H "Authorization: Bearer $ACCESS_TOKEN"

A token lasts one hour (expires_in is 3600). There's no refresh token: when a token expires, get a new one with the same request. Reuse one token for its whole lifetime instead of requesting a token for each call, because token requests have their own rate limit.

The following Python helper keeps one token and renews it a minute before it expires:

Python
import os
import time
import requests

API = "https://api.medchartscan.io"
_token = {"value": None, "expires_at": 0.0}


def access_token() -> str:
    if _token["value"] is None or time.time() > _token["expires_at"] - 60:
        resp = requests.post(
            f"{API}/v1/oauth/token",
            data={
                "grant_type": "client_credentials",
                "client_id": os.environ["CLIENT_ID"],
                "client_secret": os.environ["CLIENT_SECRET"],
            },
            timeout=30,
        )
        resp.raise_for_status()
        body = resp.json()
        _token["value"] = body["access_token"]
        _token["expires_at"] = time.time() + body["expires_in"]
    return _token["value"]

When a request without a valid token reaches any other endpoint, the response is 401 with the header WWW-Authenticate: Bearer:

401 Unauthorized
{"error": {"code": "unauthorized", "message": "Missing, invalid or expired access token."}}

The token is missing, malformed, expired, or its key was revoked. If the token expired, get a new one and retry the request once.

Token rate limits

Each key can get at most 20 tokens per minute. Each key also accepts at most 20 wrong secrets per minute; past that, a wrong secret gets 429 instead of 401. A wrong secret never blocks requests that use the right one for the same key.

Each IP address can have at most 30 failed authentications per minute, for any key: requests that get 401 invalid_client. Malformed requests (400) don't count. Past that, each further request from that address that fails to authenticate gets 429 instead of 401 until the next minute. A request with the right client ID and secret still gets a token, so other integrations behind the same address aren't locked out. A failed authentication takes at least 400 milliseconds to answer.

The limits count in fixed one-minute windows that start on the minute. Over a limit, the response is 429 with a Retry-After header that gives the seconds until the next minute starts:

429 Too Many Requests
{"error": "slow_down"}

For the limits on analyses and batches, see Rate limits.

Token endpoint errors

The token endpoint answers errors in the OAuth 2.0 shape, {"error": "<code>"}. Only org_locked also has an error_description.

StatuserrorCause
400invalid_requestgrant_type is missing; the body isn't valid form or JSON data, or has a parameter twice; a JSON value isn't a string; the content type isn't form or JSON; credentials were sent both with HTTP Basic and in the body; the body is larger than 8 KB.
400unsupported_grant_typegrant_type isn't client_credentials.
401invalid_clientThe client ID is unknown, the secret is wrong or missing, the key is revoked, or the HTTP Basic header is malformed. With HTTP Basic, the response also has WWW-Authenticate: Basic realm="MedChartScan E/M API".
403org_lockedMedChartScan support locked the key's organization, so no token is issued. Write to support@medchartscan.io. A suspended organization still gets tokens.
429slow_downMore than 20 tokens, or more than 20 wrong secrets, for this key in the current minute, or a failed authentication after 30 from your IP address in the current minute. Wait for the number of seconds in Retry-After.

Revoke a key

Owners and admins revoke keys on the API keys page. Revoking a key deletes all of its access tokens at once: the next request with any of them gets 401, and the key can't get new tokens. Results and batches that the key created stay available to the organization's other keys.

To rotate a key without downtime, create a new key, deploy its secret, then revoke the old key.

Authentication · MedChartScan E/M