Introduction
Hermes Health API
Medical records retrieval platform API.
Base URL
Every endpoint in this reference is relative to:
There is no separate sandbox host. Sandbox and production traffic use the same base URL and are separated by which API key you send — see Authentication.
Authentication
Send your API key as a bearer token on every request:
The Hermes Health API uses API keys to authenticate requests. You generate and rotate your API keys on the User Settings page. A key is shown once, when it is generated: Hermes stores only a fingerprint of it, so copy it into your secret store at that moment. If you lose a key, regenerate it; the previous key stops working as soon as the new one is issued.
Keys are issued per user, and there are two of them:
- the Sandbox API Key, scoped to projects whose status is
Sandbox. Sandbox traffic can never read or mutate production data. - the production API Key, for live projects.
A key is rejected outside its own scope, so the key you send is what decides whether you are working against real data or not. Requests with no credentials are rate-limited far more aggressively than authenticated ones — see Errors.
Idempotency
Idempotency allows you to retry a request without accidentally performing the same operation twice.
The Hermes Health API supports idempotency on PUT requests, which take an
object ID in the URL. Create a patient with PUT .../patients/{patientId}, and
if an error occurs partway through, you can resubmit the same request without
creating the patient twice.
Objects created with POST have their IDs generated by the API, so POST is
not idempotent.
Versioning
The Hermes Health API is still on version 0 and is unversioned beyond the
/v0 path prefix, because of the rate at which it is still changing.
A semantic versioning scheme, a changelog, and a deprecation notice system are coming. Until they land, treat any field you did not ask for as optional and ignore fields you do not recognise.
Errors
Every error returns the same JSON body, whatever the status code:
erroris always present: a human-readable sentence. Show it to your operators; do not parse it. Wording changes without notice.codeis optional and currently set on one condition only ("code": "roleNotAssigned"— a signed-in user whose account has no role yet). Treat its absence as normal and branch on the status code instead.errorIdis a correlation reference. It is present on errors that pass through central classification — which is every5xxand most4xxraised from domain logic — and absent on validation rejected at the route edge. Quote it when you contact support: it is the only way to find the matching server log line.
Status codes
A 500 body is always the literal string Internal server error (ref: <errorId>), and a rejected payload names the field that failed but
not the value it contained. 404 is returned for a resource outside your
key’s scope rather than 403, so that an error never confirms that a resource
you cannot see exists.
Rate limits
Two independent limits, both per rolling minute:
- Uncredentialed requests are limited per client IP — 30 requests per minute by default.
- Credentialed requests are limited per company — 600 requests per
minute by default. Sending an
Authorizationheader moves you off the IP limit and onto this one; it does not exempt you from limiting.
Exceeding either returns 429 with a Retry-After header in whole
seconds (minimum 1). Honour it rather than retrying immediately. Both
ceilings are configurable per deployment, so read Retry-After rather than
hard-coding either number.