Build with AI

Set up AI coding agents and assistants to build with Medblocks.

Give your AI agent the same context you would give a new engineer.

Most integrations get built with an AI agent in the loop now, and the difference between a good build and a bad one is what the agent knows before it starts. Brief it well and it writes the integration a senior engineer would. Leave it guessing and it invents SDK methods that never shipped, puts secrets in browser code, and hands you an afternoon of debugging code that merely looked plausible.

So we packaged that context for you. A skill that teaches coding agents our integration flows. An MCP server that turns an assistant into a hands-on operator of your live workspace. Ready-made prompts and clean Markdown exports for everything else. Set up whichever matches how you build.

Install the skill for your coding agent

The Medblocks skill teaches agents such as Claude Code, Cursor, and Codex the Patient Access flow, record export, and the security rules that go with them. It also tells the agent to read the live docs and generated API reference instead of relying on memory.

# Adds the Medblocks marketplace, then installs the skills as a plugin with automatic updates
claude plugin marketplace add medblocks/skills
claude plugin install medblocks-skills@medblocks
# Installs the skills to .agents/skills in your repo, which Codex reads natively
npx skills add medblocks/skills -a codex
# Installs the skills to .agents/skills in your repo, which Cursor reads natively
npx skills add medblocks/skills -a cursor
# One command installs the skills for every agent the skills CLI supports
npx skills add medblocks/skills

Skills do not read your local files by themselves. They guide the coding agent that is already running in your editor or terminal.

Connect an assistant to your workspace

The MCP server does something different from the skill. Where the skill helps an agent write code, the MCP server needs no code at all. Connect it and an assistant such as Claude or ChatGPT can look up who is set up in your workspace, search for hospitals, start portal connections, and read the records that come back, all behind a consent screen where you choose exactly what to grant.

Follow MCP server to connect Claude Code, Codex, ChatGPT, or claude.ai.

Start from a ready-made prompt

These prompts point an agent at the right docs and give it a work order. Copy one into your tool, or open it directly in Cursor, Claude, or ChatGPT.

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.
Set up Medblocks Platform API in my app
Open in
Show prompt text
You are an AI coding agent integrating the Medblocks Platform API into the codebase that is currently open. Medblocks is a healthcare data platform; the Platform API is a server-to-server REST API that creates Patients, opens Connect PatientSessions (patient-mediated EHR/FHIR authorization), searches the FHIR source catalog, and reports connection status. This is a PHI-adjacent integration — do not guess your way through it.

Work in the five phases below. Do not skip ahead. When anything is ambiguous, STOP AND ASK rather than assume.

========================================
PHASE 0 — AUTHORITATIVE SOURCES
========================================
Fetch and read these before writing anything. If you cannot fetch a URL, ask the user to paste it. The generated API reference is the public source of truth for routes, params, headers, request/response shapes, and error codes. If your tool has local filesystem access and the current repo contains openapi/medblocks.json, read it too.

- Setup + API keys + Version header:  https://medblocks.com/docs/workspace
- API overview + auth + fetch helper: https://medblocks.com/docs/reference/conventions
- Patients:                           https://medblocks.com/docs/patient-access/create-patients
- Medblocks-hosted page (hosted UI):  https://medblocks.com/docs/patient-access/create-patient-session/medblocks-hosted-page
- Your own UI (you build the picker): https://medblocks.com/docs/patient-access/create-patient-session/your-own-ui
- After connection (return + status): https://medblocks.com/docs/patient-access/after-connection
- FHIR access (roadmap):              https://medblocks.com/docs/patient-access/get-the-data
- Error envelope + code list:         https://medblocks.com/docs/reference/errors
- Generated reference + playground:   https://medblocks.com/docs/reference/api

From the generated API reference, and local openapi/medblocks.json when available, record before moving on:
- Current documented API version behavior and required headers.
- The exact routes, params, request bodies, responses, and error envelope for the flow.
- The documented return behavior for patient sessions.

If a route or field you'd like to use is not documented, do not invent it. Say so and stop.

========================================
PHASE 1 — RECONNAISSANCE (analyze before writing)
========================================
Read the project before adding files. Determine, with evidence (cite the file you saw it in):

1. Language + runtime: look at package.json / bun.lock / pnpm-lock.yaml / yarn.lock / deno.json / requirements.txt / pyproject.toml / go.mod / Gemfile / Cargo.toml / pom.xml / build.gradle / *.csproj. Identify the runtime (Node, Bun, Deno, CPython, Ruby, Go, JVM, .NET, etc.) and exact framework versions.
2. HTTP framework: Next.js (App Router or Pages Router?), Remix, SvelteKit, Astro, Nuxt, Express, Hono, Fastify, Koa, NestJS, Bun.serve, FastAPI, Flask, Django (DRF?), Rails, Gin/Chi/Echo, Spring Boot, ASP.NET Core, Phoenix, etc.
3. Frontend layer (if any): React/Vue/Svelte/Solid? SSR or SPA? Build tool (Vite, Webpack, Bun, Turbopack)? Is there a frontend at all, or is this a backend-only service?
4. Existing API-client conventions: is there a services/, lib/, infra/, api/ folder? Are typed clients already generated from OpenAPI specs (openapi-typescript, openapi-fetch, orval, hey-api, swagger-codegen, openapi-python-client, oapi-codegen)? Is there a shared fetch wrapper or error class you should extend instead of duplicating?
5. Secrets + config: where are env vars loaded (.env, .env.local, dotenv, Bun's built-in loader, Pydantic Settings, Viper, dotenv-rails, Doppler, Vercel/Fly/Railway env)? Where is .env.example?
6. Auth + user model: does the app already have a "current user/patient" with a stable internal ID? That ID will become the Medblocks patient_id.
7. Logging + error handling: what logger (pino, winston, structlog, zerolog, slf4j, Python logging)? Is there a request-ID convention you should propagate?
8. Tests: which runner (bun test, vitest, jest, pytest, go test, rspec, junit)? Are HTTP calls mocked (msw, nock, responses, vcr)? Is there a sandbox or test API key already available?
9. Deployment target: Vercel / Cloudflare Workers / AWS Lambda / Fly / Railway / bare metal / on-prem? This affects timeout caps, edge-vs-node runtime, and where the backend route the browser calls actually runs.

Print a short reconnaissance report (5–8 bullets) before doing anything else. If you cannot identify any of items 1–6 with confidence, STOP and ask.

========================================
PHASE 2 — CLARIFYING QUESTIONS
========================================
Ask the user — by name, in a numbered list — every question below whose answer is not already obvious from Phase 1. Wait for answers. Do not stub things out and "fill in later".

A. Connect mode
   - Picker Mode (Medblocks hosts the source picker), Direct Mode (your app picks the source and passes connection_id), or both?
   - If Direct: where in the existing UI does the source-search experience belong?

B. Patient identity
   - What is the stable, never-changing internal ID this app already has for a patient/user? It must not start with the Medblocks-reserved prefixes pat_, ps_, conn_, fhirsrc_, wh_, or evt_.
   - Create Patients explicitly via POST /patients during intake, or upsert implicitly the first time a PatientSession is initialized? (Upsert is the shorter path for most products.)

C. Return URL UX
   - What absolute URL should Medblocks redirect the patient to after the hosted flow? (Usually a route this product owns, e.g. https://<app>/connected.)
   - What product-specific label should the "Done" button show? ("Back to <ProductName>" beats "Continue".)
   - On the return page, what should the patient see for success, failure, and cancellation?

D. Server boundary
   - Confirm: this app has (or can add) a backend that holds MEDBLOCKS_API_KEY. The browser must never see it. Where in the codebase should the server-only Medblocks client live, given existing conventions?

E. Version pinning
   - Should this app pin the documented Version header in production, or leave it off during local exploration? Use only the value documented in the current API reference or local spec when one is available.

F. Idempotency + retries
   - Is PatientSession initialization always user-initiated (one click → one PatientSession) or also driven by a job that can retry? Medblocks does not currently document an Idempotency-Key header — re-check the spec before adding hand-rolled retry-with-jitter on writes.

G. Storage in this app's DB
   - Should the app persist Medblocks patient_session_id, connection_id, and last-known status in its own database, or treat Medblocks as the system of record and read on demand?

H. Eventing
   - Webhooks are available — endpoints can subscribe to patient_session.completed, connection.token_refresh_failed, records.sync.completed, records.sync.failed, and webhook.endpoint.disabled. Deliveries are signed with HMAC-SHA256 via the Medblocks-Signature header. Will this app register a receiver, or stick to polling (GET /patient-sessions/{id} on return_url, GET /patients/{id} or GET /patients/{id}/patient-sessions for status)?

I. Tests
   - Unit only with mocked fetch, contract tests against medblocks.json, or live integration tests against a sandbox key?

J. Compliance + logging
   - Any PHI redaction rules, BAA scope, or logging restrictions that affect what we can record (request bodies, patient names, emails)?

K. Anything else surprising in this codebase you want me to know before I touch it.

Where an answer is obvious from Phase 1 evidence, do not ask — state your assumption inline: "Assuming X because <file:line>; tell me if not."

========================================
PHASE 3 — PROPOSE A PLAN
========================================
Before generating code, post a short plan and pause for confirmation. The plan must include:

1. The exact files you will create or modify, with paths that follow THIS codebase's conventions (not generic Next.js paths if this isn't Next.js).
2. New env vars: MEDBLOCKS_API_KEY (required), MEDBLOCKS_API_URL (default https://app.medblocks.com), and MEDBLOCKS_API_VERSION only if the current docs or local spec require a pinned Version header. Where they will be documented (.env.example, README, deploy config).
3. Which endpoints you will call, by operationId from the spec. At minimum:
   - api.initPatientSession (POST /patient-sessions) — Picker or Direct
   - api.retrievePatientSession (GET /patient-sessions/{id}) — return_url verification
   - api.createPatient (POST /patients) — only if explicit creation chosen
   - api.getPatient (GET /patients/{id}) — only if patient-status UI needed
   - api.listPatientSessionsForPatient (GET /patients/{id}/patient-sessions) — only if history UI needed
   - api.listFhirSources (GET /connections) — only for Direct mode source search
   - api.createWebhook / api.listWebhooks / api.getWebhook / api.updateWebhook / api.deleteWebhook / api.rotateWebhookSecret / api.listWebhookEvents / api.redeliverWebhookEvent — only if subscribing to events
4. Where the new UI hooks in: which existing component holds the "Connect" button, which page owns the return_url, and how status surfaces back to the user.
5. Error-mapping strategy: how Medblocks error.code + error.type values translate into this app's existing exception/HTTP shapes.
6. Test plan: what cases you'll cover and at what level (unit / contract / integration).

Wait for "go ahead" before writing code, unless the user explicitly told you to one-shot the implementation.

========================================
PHASE 4 — IMPLEMENTATION
========================================
Follow the conventions of THIS codebase. Do not impose Next.js patterns on Express, Express on FastAPI, etc. Generic rules:

1. ENV + CONFIG
   - Add MEDBLOCKS_API_KEY to .env.example with a one-line comment about what it is and where to mint it. Update the project's env docs.
   - NEVER expose the key through NEXT_PUBLIC_*, VITE_*, EXPO_PUBLIC_*, PUBLIC_*, or any other client-bundled prefix. NEVER import it from a module that ships to the browser. NEVER call the Medblocks API from a Server Component that streams to the client without first ensuring the key isn't serialized into the payload.
   - Default MEDBLOCKS_API_URL to https://app.medblocks.com; allow override for sandboxes.

2. TYPED CLIENT
   - If the project already has an OpenAPI codegen pipeline, extend it: feed medblocks.json through the same tool (openapi-typescript / orval / hey-api / openapi-python-client / oapi-codegen / quicktype / NSwag, whatever is in use).
   - Otherwise, drop in one server-only module in the conventional location for this stack:
     - Next.js (App Router):  lib/medblocks.ts (or src/lib/medblocks.ts)
     - Next.js (Pages):       lib/medblocks.ts
     - Remix / SvelteKit:     app/lib/medblocks.server.ts (note the .server.ts suffix to lock it server-side)
     - Express/Hono/NestJS:   src/services/medblocks.ts
     - Bun.serve project:     src/medblocks.ts
     - FastAPI:               app/services/medblocks.py
     - Django:                <project>/services/medblocks.py
     - Rails:                 app/services/medblocks.rb
     - Go:                    internal/medblocks/client.go
     - .NET:                  Services/MedblocksClient.cs
     - …or wherever this repo already keeps third-party API clients.
   - Every request must send:
     - Authorization: Bearer ${MEDBLOCKS_API_KEY}
     - Version: ${MEDBLOCKS_API_VERSION}        (only when the current docs or local spec require it)
     - Content-Type: application/json            (on POST/PUT)
   - Implement an error type that captures HTTP status + the full envelope: error.type, error.code, error.message, error.param, error.doc_url, error.request_id. Never throw raw Error("Medblocks failed") — keep the envelope.
   - Use cursor pagination: list responses include has_more and next_cursor; pass starting_after on the next call.

3. SERVER ROUTES THE BROWSER CAN CALL
   - One route per flow, all server-only. Route paths follow the framework's conventions. Each route forwards the upstream HTTP status and the Medblocks error body verbatim on failures, so the frontend can render error.message.
     - Start PatientSession (Picker): POST → calls api.initPatientSession without connection_id. Body: { patient_id, return_url, return_button_label, patient_name?, patient_email?, recommended_connection_ids?, metadata? }. Response surfaces { id, url, status }.
     - Start PatientSession (Direct): POST → calls api.initPatientSession WITH connection_id. Body: same as above but connection_id replaces recommended_connection_ids (the two are mutually exclusive — verify the spec).
     - Read PatientSession:           GET ?id=... → calls api.retrievePatientSession. Used by the return_url page.
     - Search sources:             GET ?q=... (Direct only) → calls api.listFhirSources with q + limit.
     - (Optional) Patient:         GET ?id=... → calls api.getPatient. Used for connected-source UI.
     - (Optional) History:         GET ?patient_id=... → calls api.listPatientSessionsForPatient.
     - (Optional) Webhook receiver: POST → verify Medblocks-Signature header with HMAC-SHA256 of `${t}.${rawBody}` keyed by the endpoint's signing secret, then dedupe by event.id and act on event.type. Must read the RAW request body before any JSON parsing — re-serialization breaks the signature.

4. FRONTEND WIRING
   - "Connect" button: POSTs to your server's start-patient-session route, then sets window.location.href = body.url. Disable while pending.
   - Return page: read patient_session_id from the query string (always present). In Direct mode you also get success, connection_id, error, error_description. Treat the query string as a hint only. The source of truth is GET /patient-sessions/{id}.connections — connections[].status === "active" means the patient is actually connected. flow.status === "complete" only means the hosted flow finished.
   - Render distinct UI for success / partial failure / outright failure / patient-cancelled. On every failure, log error.request_id so support can correlate.

5. BACKGROUND PULL — SET EXPECTATIONS EXPLICITLY
   - A "complete" PatientSession does NOT mean records are pulled. Medblocks schedules background retrieval after authorization. Surface "Connected — pulling records" to the user, then rely on the records.sync.completed webhook (or dashboard exports) for record-level signals. Do not block the patient UI on data availability.

6. ERROR HANDLING
   - Branch on error.code (stable) first, error.type (broad) only as a fallback. Common branches to handle by code:
     - missing_api_key / invalid_api_key / expired_api_key → 500 to user, page on-call.
     - unsupported_api_version → fail loudly in CI before deploy.
     - bad_request / invalid_data → surface error.param to the caller with a 400.
     - resource_not_found → 404 with a retry path.
     - external_id_already_exists / resource_conflict → product decision on collision policy.
     - throttled / quota_exceeded → exponential backoff with jitter; emit telemetry.
     - 5xx api_error → bounded retry on idempotent GETs only; never silent-retry writes unless the spec adds Idempotency-Key.
     - 502 ehr_error / oauth_error / token_exchange_failed / token_unavailable → surface to the user with a "reconnect" CTA; do NOT show raw portal text.
   - Use the project's existing logger. Required log fields on every Medblocks failure: http_status, error.code, error.type, error.request_id, and the relevant patient_id / patient_session_id.

7. IDEMPOTENCY + CONCURRENCY
   - patient_id is the stable upsert key — re-sending POST /patients or upserting via POST /patient-sessions with the same patient_id is safe. Do not invent surrogate IDs.
   - PatientSession initialization is not idempotent: every call creates a new ps_*. If a background worker creates PatientSessions, persist the resulting patient_session_id keyed by the originating intent BEFORE redirecting, to avoid duplicates.
   - Webhook receivers MUST be idempotent — dedupe by event.id. Retries and manual redelivery (POST /events/{id}/redeliver) reuse the same id.

8. SECURITY + PII
   - No key in client bundles, ever. Re-check after bundling (grep the built output for the key prefix during CI).
   - Do not log full patient names / emails unless the product's PII policy allows it. Always log patient_session_id and request_id.
   - All Medblocks traffic is over HTTPS. Verify the base URL is https://, not http://.
   - Webhook signing secrets (whsec_*) are returned ONCE on create + rotate-secret. Store them in the same secret manager that holds MEDBLOCKS_API_KEY; you cannot retrieve them again.

========================================
PHASE 5 — VERIFY (before reporting done)
========================================
Don't claim success until you've actually run things. Report results in this order:

1. Static checks pass: the project's typechecker (tsc --noEmit / mypy / pyright / go vet / cargo check / dotnet build), linter, and formatter.
2. Tests pass: existing suite + any new tests you wrote.
3. A real smoke test using a sandbox or dev API key:
   a. Create or upsert a Patient.
   b. Initialize a Picker (and/or Direct) PatientSession — confirm the response has id (ps_*) and url.
   c. Hit url in a browser, complete the hosted step in a sandbox EHR.
   d. Land on your return_url. Confirm the page calls GET /patient-sessions/{id} and renders connections[].status correctly.
4. Final report to the user:
   - Files changed (paths).
   - Env vars to set, where, and any documented Version header value you used.
   - Exact manual steps to repeat the smoke test.
   - What you deliberately did NOT implement and why.
   - Any assumption from Phase 2 that still needs human confirmation.

========================================
NON-NEGOTIABLES
========================================
- Server-only. API key never reaches a browser.
- Only call routes that exist in medblocks.json. Don't fabricate endpoints, fields, headers, or error codes.
- After every return_url, re-read GET /patient-sessions/{id} from the server. The query string is a hint, not a source of truth.
- If you send a Version header, it must match the current docs or local spec for production deploys.
- Log error.request_id on every failure.
- No retry-with-jitter on write endpoints without explicit spec support for idempotency.
- Webhook receivers verify the Medblocks-Signature header over the RAW body and dedupe by event.id.
- When uncertain, STOP and ask. In a healthcare integration, a wrong assumption baked in is worse than a slow integration.

========================================
DECISION HINTS
========================================
- Product already has a facility/EHR picker UX → Direct Mode.
- Product just wants a "Connect your records" button → Picker Mode.
- Both modes can coexist on the same API key.
- Stable patient_id ≠ email. Use an internal user ID, member ID, or chart ID — anything that won't change for that human.
- "PatientSession complete" = patient finished the hosted flow. "Connection active" = Medblocks holds a usable token. Use connections[].status === "active" as the success signal, not flow.status.

Hand your agent the right docs

Every docs page has a Markdown version behind the Copy Markdown action at the top of the page, and the docs expose /docs/llms.txt as a compact index and /docs/llms-full.txt as a full export. Markdown beats copied HTML because tabs, callouts, and code blocks stay parseable.

Which pages to hand over depends on what you are building.

For patient access work, also say whether Medblocks hosts the source search page or you are building source search into your own UI. For TypeScript projects, prefer the SDK. For other stacks, keep the Medblocks API key on the server and treat the reference as the source of truth for request and response shapes.

Ground rules for any agent

Whichever tool you use, hold the agent to these rules.

  • Keep MEDBLOCKS_API_KEY on the server, never in browser code.
  • Verify webhook signatures before trusting event payloads.
  • Use the API Reference as the source of truth for wire shapes.
  • Log Medblocks error codes and request IDs on the server.
  • Treat the patient return URL as UI state. Confirm record availability with server-side reads or webhooks.

See also

Need help?

Running into an issue? Get support from our team and we will get back to you.

How is this guide?