# auth.md — how to get access to the ExpressAgain Partner API

This file is for an AI agent or a developer who wants to call the ExpressAgain Partner API. It says what the API is, who may use it, how access is granted, and how to authenticate. Everything here describes what the service does today.

## What the Partner API is

The Partner API (`https://www.expressagain.com/api/partner/v1`) lets a facility, a home-health or home care agency, a health plan or a vendor read the home-care record a family keeps in ExpressAgain (readings, the medication and allergy list, the care plan, visits) and write readings, home visits and PDF documents back. It only ever answers for patients whose families explicitly shared with that organization, and only within the scopes the family chose. The public reference is https://www.expressagain.com/developers.

ExpressAgain also has a free, read-only public API, an MCP server and an A2A agent that need no credentials at all (public facts, Help Center search, public pages as markdown). This file is about the Partner API, which does need credentials.

## Who gets access

- Access is for an **organization**, never for an individual or an anonymous agent.
- The organization signs ExpressAgain's Business Associate Agreement (BAA) first.
- A **person at ExpressAgain reviews every request** and issues the credentials. There is no self-service or programmatic registration: no dynamic client registration, no device or claim ceremony, no sign-in flow for an agent.

## Step 1: Discover

- Protected resource metadata (RFC 9728): `https://www.expressagain.com/.well-known/oauth-protected-resource` (also at `https://www.expressagain.com/.well-known/oauth-protected-resource/api/partner/v1`, the path-suffixed form for the resource `https://www.expressagain.com/api/partner/v1`). A 401 from the API carries `WWW-Authenticate: Bearer realm="ExpressAgain Partner API", …, resource_metadata="https://www.expressagain.com/.well-known/oauth-protected-resource/api/partner/v1"`.
- Authorization server metadata (RFC 8414): `https://www.expressagain.com/.well-known/oauth-authorization-server`. Its `agent_auth` block names this file and the registration address. There is no OpenID Connect provider, so there is no `/.well-known/openid-configuration`.

## Step 2: Request access (a person replies)

Send the request form at https://www.expressagain.com/developers#request-access: who you are, your organization, your role, the system you connect from and what you want to do. A person replies with the BAA. Once it is signed, ExpressAgain creates your organization, gives it a six-character **share code** that families type to share with you, and issues an API key with the scopes your agreement covers.

## Step 3: The API key

- A key looks like `ea_live_` followed by 40 URL-safe characters. It is shown **once**, when it is issued; ExpressAgain stores only its SHA-256 hash. Keep it in a secret store and never log it.
- The key's **prefix** (`ea_live_` plus the first 8 characters) is its public name on ExpressAgain's side. It is your OAuth `client_id`; the whole key is your `client_secret`.
- A key can be used directly as a bearer token on every Partner API call:

```http
GET https://www.expressagain.com/api/partner/v1/me
Authorization: Bearer ea_live_…
```

## Step 4: Exchange the key for a short-lived access token (optional)

The token endpoint (`https://www.expressagain.com/api/oauth/token`) supports one grant, `client_credentials`, and two client authentication methods, `client_secret_basic` and `client_secret_post` (RFC 6749 §2.3.1 and §4.4). `scope` is optional and space-separated; when present it must be a subset of the key's scopes, otherwise the answer is `invalid_scope`. When absent, the token carries every scope the key has.

```sh
curl -s -X POST https://www.expressagain.com/api/oauth/token \
  -u "ea_live_XXXXXXXX:ea_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -d grant_type=client_credentials \
  -d "scope=read:observations read:medications"
```

```json
{ "access_token": "ea_at_…", "token_type": "Bearer", "expires_in": 3600, "scope": "read:observations read:medications" }
```

- An access token looks like `ea_at_` followed by 40 URL-safe characters, lives **60 minutes**, and is stored hashed. The response carries `Cache-Control: no-store`.
- There is **no refresh token**: when a token expires, call the token endpoint again with the key.
- Errors follow RFC 6749 §5.2: `invalid_request`, `invalid_client` (401, with a `WWW-Authenticate` header when you used HTTP Basic; also for a revoked key or a suspended organization), `unsupported_grant_type`, `invalid_scope`. Too many calls answer 429 `rate_limited` with `Retry-After`.

## Step 5: Call the API

Send either the key or a live token as a bearer token in the `Authorization` header (the only bearer method; RFC 6750 §2.1). Every response carries `X-Request-Id` and `Cache-Control: no-store`.

- Scopes on a token are at most the key's scopes, and what a call may do for a patient is the token's (or key's) scopes **intersected with what that family granted**. A missing scope answers 403 `insufficient_scope` and says whether the key or the family is the reason.
- A patient that has not shared with your organization answers 404 `patient_not_found`, never "exists but not yours".
- An expired or revoked token answers 401 `invalid_token` with `WWW-Authenticate: Bearer … error="invalid_token"` (RFC 6750 §3.1). An unknown or revoked key answers 401 `invalid_key`.
- Rate limit: 600 calls per minute per key, shared by the key and every token issued from it; `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` on a 429.

## The scopes

- `read:observations`: Read the patient's readings (glucose, blood pressure, weight, SpO₂, temperature, sleep)
- `read:medications`: Read the patient's medication and allergy list
- `read:care-plan`: Read the patient's care plan (targets, instructions, the day plan)
- `read:visits`: Read visit notes and clock-ins (only for the agency on record)
- `write:observations`: Add readings to Health Tracking, marked from the organization
- `write:visits`: Log home visits: the readings taken and what the clinician found, marked from the organization
- `write:documents`: Send documents (PDF) into the app
- `webhooks`: Receive event notifications (organization-level, not per patient) (a key-level scope; a family never grants it)

## Revocation

- Revoke one access token at `https://www.expressagain.com/api/oauth/revoke` (RFC 7009), authenticating as the same client:

```sh
curl -s -X POST https://www.expressagain.com/api/oauth/revoke \
  -u "ea_live_XXXXXXXX:ea_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -d token=ea_at_…
```

  The answer is 200 whether or not the token was still live. A client can only revoke tokens issued from its own key.
- A key is revoked by ExpressAgain on request (a new one is issued for a rotation). A revoked key stops working at once, and so does every token issued from it.
- A family can revoke its grant to your organization at any time in its own settings; access to that patient ends immediately.

## Links

- Developers (the full reference): https://www.expressagain.com/developers
- Authorization server metadata: https://www.expressagain.com/.well-known/oauth-authorization-server
- Protected resource metadata: https://www.expressagain.com/.well-known/oauth-protected-resource
- Privacy: https://www.expressagain.com/privacy · Terms: https://www.expressagain.com/terms
