Patient-authorized EHR integration
Delegated agent credentials
Hand an agent a short-lived credential for one patient and a few categories instead of your app key.
Your app key can read every patient who shares with your app. When an agent only needs one, delegate a narrower credential from your server. Delegated credentials work with the REST API, not the MCP server.
Create one
TOKEN_FILE=$(mktemp)
RESPONSE=$(curl -X POST "https://api.finchnode.com/api/v1/agent-credentials" -s --fail-with-body \
-H "Authorization: Bearer $FINCHNODE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subject": "u_4f3a9c1e2b7d6a05",
"categories": ["medications"],
"purpose": "Summarize current medications",
"operations": ["records:read"],
"durationSeconds": 900
}') &&
printf '%s' "$RESPONSE" | jq -er .access_token > "$TOKEN_FILE" &&
printf '%s' "$RESPONSE" | jq 'del(.access_token)'
The token goes to a private temporary file, and only the rest of the response is printed. The subject in that example is synthetic.
| Field | Rules |
|---|---|
subject |
One patient who shares with your app. An unknown subject returns 404 not_found. |
categories |
Categories that patient shares. If some aren't shared, you get 403 consent_scope_exceeded; if none are, 410 consent_inactive. An unknown name returns 400 invalid_categories. |
purpose |
Why the agent needs it, up to 500 characters. The patient can see it, so leave out names, dates, and clinical details. |
operations |
Any of records:read, changes:read, receipts:read. Defaults to all three. |
durationSeconds |
A whole number from 1 to 3600. Defaults to 900. |
Use the response
Without the capture, you'd get a 201 like this (synthetic):
{
"access_token": "fn_agent_...",
"token_type": "Bearer",
"expires_in": 900,
"grant_id": "0b7e4c2a-9d31-4f6e-8a52-3c1d7e9f4b60",
"credential_id": "5f2a8d1c-6b43-4e7a-9c05-e8d3b2a1f794",
"scope": "records:medications"
}
access_tokenappears once. Store it on your server.- Save
credential_id; you need it to revoke. - Give the token to the agent's HTTP client through an environment variable, never in a prompt. Anything in a prompt can end up in transcripts and logs.
- Add
fn_agent_to the patterns your secret scanning looks for.
The agent then reads with it:
export FINCHNODE_AGENT_TOKEN=$(cat "$TOKEN_FILE")
curl "https://api.finchnode.com/api/v1/users/$SUBJECT/records/medications" \
-H "Authorization: Bearer $FINCHNODE_AGENT_TOKEN"
Know its limits
- It reads only that subject, those categories, and those operations. Each read checks consent again.
- It can't list patients, create sessions, or delegate further.
- It doesn't create consent. The patient must already share those categories.
- It keeps the receipts it was created with. If the patient shares again later, delegate a new credential.
- It stops working when it expires, when you revoke it, or when you revoke the app key that created it.
- Delegated credentials for an app and environment share one rate limit.
Revoke one
curl -X DELETE "https://api.finchnode.com/api/v1/agent-credentials/$CREDENTIAL_ID" \
-H "Authorization: Bearer $FINCHNODE_API_KEY"
$CREDENTIAL_ID is the credential_id from the response. A 204 means it's revoked.
Follow changes
A credential's snapshot has meta.changeCursor: null. Use meta.changeCursors[category], or the changeCursor from a category page.