PatientFlow
mb.patientFlow.init and retrieve. The primary Connect integration primitive.
A PatientFlow is one patient’s pass through the picker, OAuth login, token exchange, and return-to-app sequence. The SDK exposes two methods on mb.patientFlow:
| Method | HTTP |
|---|---|
mb.patientFlow.init(input) | POST /patient-flows |
mb.patientFlow.retrieve(id) | GET /patient-flows/{id} |
Pick picker mode when you want Medblocks to host the EHR search UI; pick direct mode when your app already knows which EHR the patient is connecting. Both modes use the same init call. The request shape selects the mode.
Picker Mode
Omit connection_id. Optionally pass recommended_connection_ids to push specific EHRs to the top of the picker.
import { mb } from "../medblocks";
export async function startPicker(patientId: string, patientEmail: string) {
const flow = await mb.patientFlow.init({
patient_id: patientId,
patient_email: patientEmail,
return_url: "https://app.example.com/connected",
return_button_label: "Back to Acme Health",
recommended_connection_ids: ["fhirsrc_epic_mychart"],
});
return { url: flow.url };
}async function startConnect(patientId: string, patientEmail: string) {
const response = await fetch("/api/start-picker", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ patientId, patientEmail }),
});
const { url } = await response.json();
window.location.href = url;
}After the patient clicks Done on the Medblocks completion screen, they land on your return_url with ?patient_id=<your-external-id>&patient_flow_id=pf_* appended. Use patient_id with mb.patients.retrieve for the patient’s cumulative state; use patient_flow_id with mb.patientFlow.retrieve for this session specifically.
Direct Mode
Pass a single connection_id returned from mb.connections.list. The Medblocks-hosted page redirects to that EHR’s SMART login in about 200 ms. No Medblocks UI is rendered.
import { mb } from "../medblocks";
export async function startDirect(input: {
patientId: string;
connectionId: string;
}) {
const flow = await mb.patientFlow.init({
patient_id: input.patientId,
connection_id: input.connectionId,
return_url: "https://app.example.com/connected",
});
return { url: flow.url };
}async function connectFacility(patientId: string, connectionId: string) {
const response = await fetch("/api/start-direct", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ patientId, connectionId }),
});
const { url } = await response.json();
window.location.href = url;
}The return URL carries patient_id, patient_flow_id, success, connection_id, and on failure error / error_description. Use parseReturnUrl to read these in a typed way.
connection_id accepts either the fhirsrc_* public id or the raw fhir_base_url.
Retrieve
Read the PatientFlow from the server after the patient returns. The connections array is the source of truth for what actually got authorized.
import { mb } from "../medblocks";
export async function connectStatus(id: string) {
const flow = await mb.patientFlow.retrieve(id);
return {
status: flow.status,
active: flow.connections.filter((c) => c.status === "active"),
failed: flow.connections.filter((c) => c.status === "failed"),
};
}flow.status is the lifecycle of the browser flow itself. open, complete, expired. flow.connections[*].status describes each authorization attempt. active, failed, expired, refresh_failed.
Input Reference
InitPatientFlowInput is the type of mb.patientFlow.init’s argument. The schema lives in Initialize a Connect PatientFlow.
| Field | Required | Notes |
|---|---|---|
patient_id | Yes | Your stable patient identifier. Reserved prefixes (pat_, pf_, conn_, fhirsrc_, wh_, evt_) are rejected. |
return_url | Yes | Absolute URL the patient lands on after the flow. |
patient_email | No | Stored or updated on the patient. |
patient_name | No | Stored or updated on the patient. |
connection_id | No | EHR id for direct mode. Mutually exclusive with recommended_connection_ids. |
recommended_connection_ids | No | EHR ids surfaced first in picker mode. Mutually exclusive with connection_id. |
return_button_label | No | Custom label for the “Done. Return to {label}” button. Max 60 chars. |
expires_in | No | Seconds until the URL expires. Default 1800 (30 min). Max 86400 (24 h). |
metadata | No | Arbitrary key/value pairs echoed back on reads. |
If both connection_id and recommended_connection_ids are present, the SDK forwards the request and the API returns MedblocksInvalidRequestError.
Errors
| Subclass | When |
|---|---|
MedblocksInvalidRequestError | connection_id and recommended_connection_ids both set; return_url malformed; reserved-prefix patient_id. |
MedblocksConflictError | patient_id already exists in a way that conflicts with the supplied email/name. |
MedblocksNotFoundError | connection_id does not exist or is not visible to your organization. |
MedblocksRateLimitError | Quota exceeded. Honors Retry-After. |
See Errors for the full hierarchy.
Related
- Concept · PatientFlow
- SDK · Connections. Build a custom picker.
- SDK · Return URL. Parse what comes back.
- SDK · Patients · listPatientFlows. Flow history for a patient.
- Webhooks ·
patient_flow.completed. Skip polling.
