AI agents and medical data
Controlled access for AI agents to patient records: OAuth scopes, the tools an agent needs, audit and provenance of what it reads and writes, and event-driven agents.
An AI agent is a program in which a language model decides, step by step, which actions to take — which data to read, which tool to call, what to write — to complete a task. When the task involves a patient's medical record, the agent needs controlled access: it should reach only the records it is allowed to, only the parts of them it needs, leave a trace of what it read and mark what it wrote as AI-generated. Anpheros provides that controlled access through its API; the agent framework and the model are yours.
Anpheros has no built-in agent runtime, no Model Context Protocol (MCP) server and no official integration with any agent framework or model provider. Agents use it the way any application does: through the REST and FHIR API or the SDKs.
The architecture
Patient ── grants access (OAuth scopes, duration) ────────────────┐
▼
Agent runtime (your code, any framework) ── tools ──► Anpheros API ──► the patient's FHIR record
│ ▲ │
│ └── context, search results ◄───────┘ every call: scope checks + access log
▼
The model you choose (hosted or local) ── plans the next step
The model proposes actions; your runtime executes them with the credential it holds. The model itself never holds an Anpheros key or token.
Controlled access
- Whose records. With an API key the agent sees the patients your project created. To act on someone's existing record it needs an OAuth grant from that person — for one patient, for 30 to 365 days.
- Which data. Scopes limit resource types and actions, optionally to a category:
patient/Observation.rs?category=laboratorylets an agent read and search lab results and nothing else. To build a context, the grant must allow reading observations, conditions, medications and allergies (for examplepatient/*.rs). - Read-only by default. Grant write scopes only when the agent is meant to record something.
- Revocable. The person can revoke the grant at any time; the agent's next call is refused.
Tools an agent typically needs
| Tool | Anpheros call | Notes |
|---|---|---|
| Get context for a task | POST /v1/context |
the first call for most tasks; budgeted and source-labelled (LLM applications and healthcare data) |
| List recent results | GET /v1/patients/{id}/observations?category=laboratory&from=… |
filters: category, code, from, to |
| Active medications | GET /v1/patients/{id}/medications?status=active |
|
| What happened when | GET /v1/patients/{id}/timeline?from=… |
one chronological list across types |
| Full FHIR detail | GET /fhir/R4/{type}?patient=… |
when a summary is not enough |
| A standard summary | GET /fhir/R4/Patient/{id}/$summary |
International Patient Summary bundle |
| Look up a code | GET /v1/terminology/loinc?q=… |
bundled LOINC and ATC subsets |
| Record a note | POST /v1/patients/{id}/observations with author_type: "ai" |
only with a write scope |
The OpenAPI specification (https://developers.anpheros.com/openapi.json) describes every endpoint and can be turned into tool definitions; the TypeScript and Dart SDKs can be wrapped as tools directly.
Example: a visit-preparation agent with the patient's consent
A patient who already keeps a record in Anpheros asks a third-party assistant to prepare questions for a cardiology visit. The agent needs read access to the record for a limited time — nothing more.
1. Consent (OAuth 2.1 + PKCE, SMART v2 scopes). The assistant sends the person to the consent page with the narrowest scopes that still allow a context (observations, conditions, medications and allergies):
import { Anpheros, OAuthAuth, OAuthFlow, generatePkce } from '@anpheros/sdk';
const flow = new OAuthFlow({ baseUrl: 'https://platform.anpheros.com', clientId: CLIENT_ID, redirectUri: REDIRECT_URI });
const pkce = await generatePkce();
const url = flow.authorizeUrl({
scopes: ['patient/Observation.rs', 'patient/Condition.rs', 'patient/MedicationStatement.rs',
'patient/AllergyIntolerance.rs', 'offline_access'],
state, pkce, purpose: 'treatment',
});
// … the person chooses the record and the duration (30–365 days) and allows; on the callback:
const tokens = await flow.exchange(code, pkce);
const anpheros = new Anpheros({ auth: new OAuthAuth({ tokens, onRefresh: flow.refresh }) });
const patient = tokens.patient!; // this application's own (pairwise) id for the person
2. Tools. The agent runtime exposes a few functions to the model; each one is a real API call made with the access token:
const tools = {
// POST /v1/context — relevant sections, within a budget, labelled with author type and source
get_context: (a: { question: string }) =>
anpheros.context.build({ patient, task: 'prepare a cardiology visit', question: a.question, budget_tokens: 2000, format: 'text' }),
// GET /v1/patients/{id}/observations?category=laboratory&code=…&from=…
list_lab_results: (a: { code?: string; from?: string }) =>
anpheros.observations.list(patient, { category: 'laboratory', code: a.code, from: a.from, limit: 20 }),
// GET /v1/patients/{id}/timeline?from=…
get_timeline: (a: { from: string }) => anpheros.timeline.list(patient, { from: a.from, limit: 50 }),
};
Describe them to the model in whatever tool or function-calling format your model API uses; the model proposes a call, your runtime runs it and returns the JSON. The model never sees the token.
3. What the platform enforces. The token reaches only that person's record and only the scoped types; anything else answers 403 (a type outside the scopes) or 404 (another patient). Every call — including each context request, with its manifest_id — appears in the person's access log for this application. If the person revokes access, the next call answers 401 and the agent must stop; the refresh token cannot bring it back.
4. Output. The agent returns questions for the doctor with the sources it used. It writes nothing back, because it was not given a write scope.
Audit and provenance
- Every call the agent makes is an access to the record and appears in the patient's access log for your application — including each context request, which also stores a manifest of the sections and sources used.
- Everything the agent writes is recorded as AI-authored in
Provenance. When a context is built later, AI-written values are listed underai_notes, apart from clinical facts, so an agent does not end up citing its own earlier output as evidence.
Starting agents from events
Webhooks let an agent run when something changes — a new lab result (resource.created), a new consent (consent.granted) — instead of polling. Events carry ids only; the agent then reads what its scopes allow. Webhooks
Design rules worth keeping
- Give the agent the narrowest scopes that complete the task.
- Start from a context, not from raw bulk reads; respect
omittedandwarnings. - Show the patient's data sources in the agent's answer.
- Never let the agent present a diagnosis or change a treatment on its own; route such outputs to a clinician.
- Log the
manifest_idof each context next to the agent's answer, so a person can later see what the answer was based on.