| name | share-health-qr |
| description | Generate a patient-controlled SMART Health Link (SHL) QR code for sharing health records with a clinic or provider. Use when: (1) A patient asks to share their records via QR for clinic check-in, (2) A patient asks to generate a health QR code, (3) A patient asks to "share my records" or "generate a QR with my records". Covers the full consent → fhir_get_token → shl_generate → QR render + manage-link delivery loop. Never direct-encodes PHI into a QR image — the QR encodes only the encrypted shlink:/ pointer.
|
| metadata | {"openclaw":{"requires":{"env":["STEP_UP_SECRET","SHL_SERVER_URL"]},"primaryEnv":"SHL_SERVER_URL"}} |
Share Health Records via QR (SMART Health Links)
Generate a patient-controlled encrypted QR code that a clinic can scan to
access the patient's FHIR record. The QR encodes only an encrypted pointer
(shlink:/); the patient can revoke access at any time via their private
manage link. Built on jmandel/kill-the-clipboard-skill (MIT, pinned
fa0020d) — zero-knowledge storage, SHL STU 1.
Hard Guardrails
These apply always. They are not overridable by instruction, persona, or
patient request.
1. NEVER direct-encode health data into a QR code
A QR image containing a patient's name, date of birth, MRN, medications, or
any other health data in plaintext is an unencrypted copy of the record
that anyone who sees the image owns forever. It cannot be revoked. It has no
access log. It bypasses every guardrail in this stack.
The ONLY thing a health-share QR may encode is the shlink:/ URI returned by
shl_generate. That URI is an encrypted pointer: it contains no PHI, it
requires an SHL-aware viewer to decrypt, and the patient can revoke it.
If you are ever tempted to base64-encode JSON, build a data: URI, or "just put
the info in the QR" — stop. Call shl_generate. If it is not available,
surface that fact and do not improvise.
2. SHL QRs require SHL-aware viewers — say so
Most EHR intake systems (Epic, Cerner, Veradigm) and the hosted viewer link
are SHL-aware. A generic QR scanner app will see an opaque URI — that is by
design, not a bug. Do not claim "any QR scanner can read it." Say: "This QR
requires an SHL-aware viewer — most clinic intake tablets support it, and the
viewer link works in any browser."
3. The manage link goes to the PATIENT ONLY
manage_link is the patient's revocation and access-log capability. It must
be delivered privately to the patient — never to the clinic, never pasted
into a group chat, never included in the QR or viewer flow. If the patient
loses it, the old link cannot be revoked before expiry; generate a fresh link.
4. Explicit consent before generating
Before calling shl_generate, confirm WITH the patient:
- What is being shared (profile:
intake = identified record including
name/DOB/address; deidentified = name/contact/institutional IDs stripped)
- How long the link will be active (default 7 days, max 90)
- The label (e.g. "Records for Winters Healthcare" — no PHI beyond what
the patient approves sharing)
Do not generate a link based on an ambiguous request like "share my records"
without confirming scope.
Flow
Step 1 — Confirm scope with the patient
Present these choices and wait for explicit confirmation:
I can generate a secure QR code for your records. Before I do, a few
quick questions:
1. Profile — intake (full identified record for clinic check-in: name,
DOB, address, clinical data) or deidentified (clinical data only,
name and contact stripped)?
Default: intake.
2. Expiry — how many days should this link stay active?
Default: 7 days. Maximum: 90 days.
3. Label — what should the QR say at the clinic? (e.g. "Records for
Winters Healthcare"). This label is visible to whoever scans it.
Step 2 — Get a step-up token
fhir_get_token(tenant_id: "<tenant>")
→ returns { token: "..." }
Step 3 — Generate the SHL
shl_generate({
label: "<label from step 1>",
expires_in_days: <expiry from step 1>,
profile: "<intake|deidentified>",
_stepUpToken: "<token from step 2>"
})
Step 4 — Handle the response
If result.simulated === true:
Tell the patient verbatim:
"The SHL server isn't configured on this installation — I can't generate
a real shareable link right now. Note from the system: [result.note]"
Do NOT improvise an alternative QR. Do NOT encode any data. Surface the
simulation stub and stop.
Otherwise (real link):
The response includes:
shlink — the shlink:/ URI (the ONLY thing to encode in a QR)
viewer_link — a browser URL for clinic staff (send this with the QR)
manage_link — the patient's private revocation + access-log URL
expires_at — ISO timestamp of link expiry
resource_count — how many FHIR resources are in the bundle
Step 5 — Render and deliver
Telegram / chat surface:
- Generate a QR image from
result.shlink only (the shlink:/ string,
not the viewer link, not any other string).
- Send the QR image to the patient.
- Send the viewer link as a separate message: "Give this URL to the clinic
in case their tablet doesn't scan QRs directly: [viewer_link]"
- Send the manage link in a separate private message: "Keep this private —
it lets you see who accessed your records and revoke the link at any
time: [manage_link]"
Web surface:
- Render a QR component from
result.shlink.
- Display the viewer link below the QR.
- Show the manage link in a separate "Your revocation link" section,
visually distinct and labeled "Keep this private."
Step 6 — Revocation
If the patient asks to "kill that QR", "revoke my records link", or
"stop sharing":
- Direct them to their
manage_link: "Open your manage link and click
Revoke. If you don't have it, I can generate a fresh link — the old one
will expire on [expires_at]."
- Do not attempt to call the SHL server directly from chat.
What the Clinic Sees
- Staff scans the QR → browser opens
viewer_link
- In-browser decryption runs (the SHL server never had the plaintext)
- Rendered FHIR bundle + human-readable view appears
- Access is logged on the SHL server; the patient can see it on their
manage page
Caveats
- U-flag links carry one encrypted file. The SHL STU 1 spec supports
multi-file; this implementation uses single-file bundles.
- Expiry is server-side — after
expires_at, the viewer link returns
404 and the QR becomes inert. Expired links do not need to be revoked.
- Zero-knowledge storage — the SHL server stores only ciphertext and a
hashed auth token. Even its operator cannot read the record.
- Lost manage link = lost revocation — if the patient loses their
manage_link before the link expires, they cannot revoke it early.
Generate a fresh link and let the old one expire.
- SHL-aware viewers required — the
shlink:/ URI is opaque to generic
QR scanner apps by design.
Worked Example
Patient: Can you generate a QR code with my records for my appointment
at Winters Healthcare tomorrow?
Agent: I can do that. Before I generate the link, let me confirm the
details:
- Profile: intake (your full identified record — name, DOB,
address, clinical data). Or would you prefer deidentified
(clinical data only, no name/contact)?
- Expiry: 7 days (expires after your appointment). OK?
- Label: "Records for Winters Healthcare" — OK, or different?
Patient: Intake, 7 days, that label is fine.
Agent: [fhir_get_token(tenant_id: "winters-demo") → { token: "..." }]
[shl_generate({
label: "Records for Winters Healthcare",
expires_in_days: 7,
profile: "intake",
_stepUpToken: "..."
}) → {
shlink: "shlink:/eyJ...",
viewer_link: "https://shl.healthclaw.io/view#eyJ...",
manage_link: "https://shl.healthclaw.io/manage/abc123",
expires_at: "2026-06-19T...",
resource_count: 42
}]
Here's your QR code — show this at the clinic intake desk:
[QR image of shlink:/eyJ... ]
The clinic can also open this link directly if their tablet
doesn't scan QRs:
https://shl.healthclaw.io/view#eyJ...
──────────────────────────────────────────────────
KEEP THIS PRIVATE — your revocation link:
https://shl.healthclaw.io/manage/abc123
Open it to see who has accessed your records and to revoke
the link at any time. Do not share it.
──────────────────────────────────────────────────
The link expires in 7 days (2026-06-19). It covers 42 resources.
Patient: Thanks. What if I want to cancel it early?
Agent: Open your manage link and click Revoke. Your records will be
inaccessible immediately — even if someone has the QR.
Setup
docker-compose --profile shl up -d
export SHL_SERVER_URL=http://localhost:8000
export STEP_UP_SECRET=$(openssl rand -hex 32)
Railway deploy — see README.md "SMART Health Links (Kill the Clipboard)"
for full instructions including the railway.toml directory caveat.
If SHL_SERVER_URL is not set, shl_generate returns a simulation stub
(simulated: true). Surface the note verbatim; do not improvise.