Get the data

Read connected records on demand or send them to your own systems with exports.

Build Patient Access with AI
Open in
Show prompt text
You are an AI coding agent helping integrate Medblocks Patient Access into the codebase that is currently open. Treat the live Medblocks docs and generated API reference as the source of truth. If you are running inside a local repo that contains openapi/medblocks.json, read it too. Do not rely on endpoint names, SDK calls, response fields, or code snippets from memory.

Before changing code, read the current docs directly.

- Patient Access overview https://medblocks.com/docs/patient-access/overview
- Find connections https://medblocks.com/docs/patient-access/find-connections
- Create patients https://medblocks.com/docs/patient-access/create-patients
- Create a patient session https://medblocks.com/docs/patient-access/create-patient-session
- Medblocks-hosted page https://medblocks.com/docs/patient-access/create-patient-session/medblocks-hosted-page
- Your own UI https://medblocks.com/docs/patient-access/create-patient-session/your-own-ui
- Handle the return https://medblocks.com/docs/patient-access/handle-the-return
- After connection https://medblocks.com/docs/patient-access/after-connection
- Get the data https://medblocks.com/docs/patient-access/get-the-data
- API conventions https://medblocks.com/docs/reference/conventions
- API reference https://medblocks.com/docs/reference/api

Use the page I copied this from as the immediate task context, then use the rest of the Build pages to understand the full flow.

Work in this order.

1. Inspect this codebase first. Identify the app framework, server boundary, environment variable pattern, existing API client pattern, current patient or user identity model, logging style, and test runner.
2. Read the generated API reference before naming any route, field, enum, header, error code, or SDK method. If your tool has local filesystem access and this repo has openapi/medblocks.json, read that too. If the docs and reference disagree, stop and ask the user which source to follow.
3. Decide whether this app should use the Medblocks-hosted page, your own UI, or both. Ask the user if the answer is not obvious from the product.
4. Keep the Medblocks API key server-side. Never expose it through public environment variable prefixes, frontend bundles, browser logs, or client-side fetches.
5. Implement the smallest complete path for the selected page. Follow existing project conventions for file locations, naming, validation, errors, loading states, and tests.
6. After a patient returns from authorization, verify the result from the server using the latest documented Patient Access status flow. Do not trust query string values as the final source of truth.
7. Preserve Medblocks request IDs and documented error details in logs and server responses where safe. Avoid logging PHI unless this codebase already has an approved pattern.
8. Run the relevant type checks and tests before reporting done. If a live smoke test needs credentials the repo does not have, explain the exact manual smoke test steps.

If any required detail is missing, ask concise questions before writing code. In a healthcare integration, a correct pause is better than an incorrect assumption.

Once a patient connects, Medblocks pulls their records from the facility or payer they approved in the background. This page shows how your backend reads those records.

Read on demand

Once a patient’s connection is active and the background pull has had time to land records, read them with mb.patients.records. It returns the latest stored resources for that patient as a paginated list, and each item names the EHR source it came from. The SDK calls GET /patients/{id}/records under the hood, so the same read works from any HTTP client.

server/read-patient-records.ts
import { Medblocks } from "medblocks";

const mb = new Medblocks(process.env.MEDBLOCKS_API_KEY!);

const page = await mb.patients.records("user_42", { count: 100 });

for (const item of page.data) {
  console.log(item.source, item.resource.resourceType);
}
Parameters
idstringrequired

Your patient_id from Patient creation or Session upsert.

countinteger

Maximum items per page.

Parameters
AuthorizationBearer <token>required

Medblocks API key for server-side requests.

Versionstring

Date-pinned API version. If omitted, Medblocks uses the version pinned on your API key.

app/load-records.ts
// Your API key stays on the server. The browser calls your own route,
// which runs the read above and returns the page as JSON.
const response = await fetch("/api/records?patient_id=user_42");
const page = await response.json();

for (const item of page.data) {
  console.log(item.source, item.resource.resourceType);
}
Parameters
idstringrequired

Your patient_id from Patient creation or Session upsert.

countinteger

Maximum items per page.

Each item carries the resource exactly as the source EHR returned it, with one addition. Your patient_id is appended to the Patient resource’s identifier list under the urn:medblocks:patient-id system, so the record stays matched to your patient wherever it lands. Medblocks does not rewrite ids or references.

{
  "resource_type": "list",
  "patient_id": "user_42",
  "data": [
    {
      "source": "fhirsrc_SMsb6YLMQLal1qm9AbdSPAcc",
      "resource": {
        "resourceType": "Patient",
        "id": "eCB8Nuu2lGe...",
        "identifier": [
          { "system": "urn:oid:1.2.840.114350...", "value": "MRN12345" },
          { "system": "urn:medblocks:patient-id", "value": "user_42" } 
        ]
      }
    },
    {
      "source": "fhirsrc_SMsb6YLMQLal1qm9AbdSPAcc",
      "resource": { "resourceType": "Observation", "id": "eWDRZ...", "status": "final" }
    }
  ],
  "has_more": true,
  "next_cursor": "aB3xK9mQp2Lz",
  "previous_cursor": null
}

The source on each item is the id of the EHR the resource was pulled from. Resolve it with the SDK to get the EHR’s name, type, and base URL. The FHIR source reference documents the underlying endpoint.

const ehr = await mb.connections.retrieve(item.source);

When a patient has connected more than one EHR, a single read returns items from every source in the same list, and source is what tells them apart. Group a page client-side when your code handles each EHR separately.

const bySource = Object.groupBy(page.data, (item) => item.source);

Filter the read

Two query parameters narrow what comes back. type takes comma-separated FHIR resource types, and since keeps only resources changed after the timestamp you pass.

server/read-recent-results.ts
const page = await mb.patients.records("user_42", {
  type: "Observation,Condition",
  since: "2026-06-01T00:00:00Z",
});
Parameters
typestring

Comma-separated FHIR resource types to include, e.g. Observation,Condition.

sincestring

ISO 8601 date or timestamp. Returns current resources changed after this time.

Use since to read incrementally when you keep your own copy of the record. Anything not ISO 8601 is rejected. Filters are validated against the pagination cursor, so a read keeps the same filters from its first page to its last.

Pagination

A patient’s records can run to more than one page. Each response carries a next_cursor when more records remain, so you keep passing it back as starting_after and stop once has_more is false. The example below does this and returns every resource.

Your result set is fixed the moment you request the first page. Records that arrive while you are still reading are not slipped into the pages you are working through, so you never see a resource twice or in a shifting order. You pick up the new records the next time you read from the start.

src/read-all-records.ts
import { mb } from "./medblocks";

/**
 * Reads every page of a patient's records by following `next_cursor`,
 * and returns all collected resources.
 */
export async function readAllRecords(patientId: string) {
  const resources = [];
  let starting_after: string | undefined;

  while (true) {
    const page = await mb.patients.records(patientId, {
      count: 100,
      starting_after,
    });
    resources.push(...page.data.map((item) => item.resource));

    if (!page.has_more || !page.next_cursor) return resources;
    starting_after = page.next_cursor;
  }
}
Parameters
idstringrequired

Your patient_id from Patient creation or Session upsert.

countinteger

Maximum items per page.

starting_afterstring

Opaque 12-character cursor from a previous response's next_cursor.

Parameters
AuthorizationBearer <token>required

Medblocks API key for server-side requests.

Versionstring

Date-pinned API version. If omitted, Medblocks uses the version pinned on your API key.

app/load-all-records.ts
// Your server walks the pages and returns the full list. The browser
// makes one call and never touches cursors or the API key.
const response = await fetch("/api/records?patient_id=user_42");
const resources = await response.json();

console.log(`Loaded ${resources.length} resources`);
Parameters
idstringrequired

Your patient_id from Patient creation or Session upsert.

countinteger

Maximum items per page.

Cursors are short opaque tokens. Pass them back exactly as received and never store them long term. Each cursor stays valid for 7 days from the page response that issued it, and every new page issues fresh cursors, so a read that keeps going never expires. Only a cursor left unused for more than 7 days goes stale, and the API answers it with bad_request and the message Invalid or expired cursor. When that happens, start the read again from the first page.

One caveat. When a patient revokes a source’s access while you are paging, the very next page stops returning that source’s records, so a read can end with fewer resources than earlier pages implied. Consent wins over consistency.

Export to your systems

You do not have to pull records yourself. An export delivers them for you. Pick a FHIR server or object storage destination once in the dashboard, and Medblocks keeps it current as new records land, with nothing to build or maintain on your side. Data out walks through setting one up.

See also