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:
| Part | Format | Where you see it |
|---|---|---|
| Client ID | mcs_ci_ followed by 24 characters | Always, on the API keys page |
| Client secret | mcs_sk_ followed by 43 characters | Once, 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.
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) withclient_idandclient_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_typein 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 -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 -s https://api.medchartscan.io/v1/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=client_credentialsA 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.
{"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 -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:
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:
{"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:
{"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.
| Status | error | Cause |
|---|---|---|
400 | invalid_request | grant_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. |
400 | unsupported_grant_type | grant_type isn't client_credentials. |
401 | invalid_client | The 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". |
403 | org_locked | MedChartScan support locked the key's organization, so no token is issued. Write to support@medchartscan.io. A suspended organization still gets tokens. |
429 | slow_down | More 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.