Skip to content
Hermes Health API
GuidesOpenAPI spec

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:

  • error is always present: a human-readable sentence. Show it to your operators; do not parse it. Wording changes without notice.
  • code is 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.
  • errorId is a correlation reference. It is present on errors that pass through central classification — which is every 5xx and most 4xx raised 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 Authorization header 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.