openapi: 3.2.0 info: title: Medblocks Patients API version: '2026-04-25' description: 'Developer API for the Medblocks Platform. Connect any patient to one or more EHRs (Epic, Cerner, Athena, …) via a single hosted PatientSession, then pull their unified FHIR records through this API. Server-to-server only — no browser-side calls. Authenticate every request with a Bearer secret key. **Resources:** `PatientSession`, `Patient`, `Connection`, `FhirSource`. **Conventions:** date-pinned versioning (optional `Version` header), a typed error envelope on every non-2xx response.' license: name: Proprietary url: https://medblocks.com/terms servers: - url: https://app.medblocks.com security: - BearerAuth: [] tags: - name: Patients paths: /patients: post: operationId: api.createPatient summary: Create a patient description: Create a patient with your own `patient_id` plus optional email, name, and metadata. The id is unique within your organization. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PatientCreateInput' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Patient' '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' parameters: - $ref: '#/components/parameters/Version' tags: - Patients get: operationId: api.listPatients summary: List patients description: List your organization's patients with cursor pagination and optional exact-match filters on `patient_id` or `email`. parameters: - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 10 description: Maximum number of items to return. allowEmptyValue: true allowReserved: true - name: starting_after in: query schema: type: string description: Pagination cursor from `next_cursor`. allowEmptyValue: true allowReserved: true - name: patient_id in: query schema: type: string description: Exact-match filter on `patient_id`. allowEmptyValue: true allowReserved: true - name: email in: query schema: type: string description: Exact-match filter on `email`. allowEmptyValue: true allowReserved: true - $ref: '#/components/parameters/Version' responses: '200': description: OK content: application/json: schema: type: object properties: resource_type: const: list description: Resource type discriminator. Always `"list"` for paginated responses. data: type: array items: $ref: '#/components/schemas/Patient' description: Items on this page. has_more: type: boolean description: '`true` when there''s at least one more page after this one.' next_cursor: anyOf: - type: string - type: 'null' description: Pass as `starting_after` to fetch the next page. `null` when there's no more data. required: - resource_type - data - has_more - next_cursor examples: - resource_type: list data: - id: user_42 resource_type: patient email: jane@example.com name: Jane Doe metadata: plan: premium created_at: '2026-04-25T14:30:00.000Z' has_more: false next_cursor: null '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Patients /patients/{id}: get: operationId: api.getPatient summary: Retrieve a patient description: Retrieve a patient by their `patient_id` - the developer-provided identifier set on creation. parameters: - name: id in: path required: true schema: type: string description: Your `patient_id` from Patient creation or Session upsert. - $ref: '#/components/parameters/Version' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/PatientDetail' '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Patients put: operationId: api.updatePatient summary: Update a patient description: Update a patient by id. Omitted optional fields are cleared. `patient_id` in the body must match the URL. parameters: - name: id in: path required: true schema: type: string description: Your `patient_id` from Patient creation or Session upsert. - $ref: '#/components/parameters/Version' requestBody: required: true content: application/json: schema: type: object properties: patient_id: type: string minLength: 1 maxLength: 200 description: Must match the `{id}` URL param. 400 if mismatched. Immutable. email: anyOf: - type: string format: email - type: 'null' description: New email. Omit or pass `null` to clear. name: anyOf: - type: string maxLength: 200 - type: 'null' description: New name. Omit or pass `null` to clear (UI falls back to the patient_id). metadata: type: object propertyNames: type: string additionalProperties: {} description: New metadata. Omit to clear (replaced with `{}`). required: - patient_id responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Patient' '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Patients delete: operationId: api.deletePatient summary: Delete a patient description: Permanently delete a patient and their connections by `patient_id`. Returns a tombstone confirming the deletion. parameters: - name: id in: path required: true schema: type: string description: Your `patient_id` from Patient creation or Session upsert. - $ref: '#/components/parameters/Version' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/PatientDeleted' '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Patients /patients/{id}/connections/{connection_id}: delete: operationId: api.disconnectPatientConnection summary: Disconnect a connection description: Revoke your organization's access to one of the patient's connections by its `conn_` id. The patient's link to the source EHR is removed for your organization; stored tokens are revoked when no other workspace still uses them. Returns a tombstone confirming the disconnect. parameters: - name: id in: path required: true schema: type: string description: Your `patient_id` from Patient creation or Session upsert. - name: connection_id in: path required: true schema: type: string description: The `conn_` connection id from the patient's `connections[]`. - $ref: '#/components/parameters/Version' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ConnectionDisconnected' '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Patients /patients/{id}/patient-sessions: get: operationId: api.listPatientSessionsForPatient summary: List patient sessions for a patient description: List a patient's sessions with cursor pagination and an optional `status` filter, identified by their `patient_id`. parameters: - name: id in: path required: true schema: type: string description: Your `patient_id` from Patient creation or Session upsert. - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 10 description: Maximum number of items to return. allowEmptyValue: true allowReserved: true - name: starting_after in: query schema: type: string description: Pagination cursor from `next_cursor`. allowEmptyValue: true allowReserved: true - name: status in: query schema: enum: - open - complete - expired type: string description: Filter by patient_session status. allowEmptyValue: true allowReserved: true - $ref: '#/components/parameters/Version' responses: '200': description: OK content: application/json: schema: type: object properties: resource_type: const: list description: Resource type discriminator. Always `"list"` for paginated responses. data: type: array items: $ref: '#/components/schemas/PatientSession' description: Items on this page. has_more: type: boolean description: '`true` when there''s at least one more page after this one.' next_cursor: anyOf: - type: string - type: 'null' description: Pass as `starting_after` to fetch the next page. `null` when there's no more data. required: - resource_type - data - has_more - next_cursor examples: - resource_type: list data: - id: ps_01J9YQ8M2X4VZ6P2K5RH7M3KQT resource_type: patient_session status: open url: https://app.medblocks.com/c/9c9b6f7a8e4f4a3b9c1e6f3a2d8b7c4d patient_id: user_42 connection_id: null recommended_connection_ids: - fhirsrc_02Zh2bnvRoWQJYgOAkI0Twcc - fhirsrc_0a9Ea55aR4u7HaWMyFBN8wcc - fhirsrc_lcvXRHLSSI2WK9Prrt5Qggcc return_url: https://app.example.com/connected return_button_label: Acme Health connections: [] expires_at: '2026-04-25T15:00:00.000Z' metadata: signup_source: checkout created_at: '2026-04-25T14:30:00.000Z' has_more: false next_cursor: null '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Patients /patients/{id}/records: get: operationId: api.getPatientRecords summary: Get patient records description: Return the patient's latest stored FHIR resources with their source EHR, paginated. parameters: - name: id in: path required: true schema: type: string description: Your `patient_id` from Patient creation or Session upsert. - name: type in: query schema: type: string description: Comma-separated FHIR resource types to include, e.g. `Observation,Condition`. allowEmptyValue: true allowReserved: true - name: since in: query schema: anyOf: - type: string format: date-time - type: string format: date description: ISO 8601 date or timestamp. Returns current resources changed after this time. allowEmptyValue: true allowReserved: true - name: count in: query schema: type: integer minimum: 1 maximum: 100 default: 100 description: Maximum items per page. allowEmptyValue: true allowReserved: true - name: starting_after in: query schema: type: string description: Opaque 12-character cursor from a previous response's `next_cursor`. allowEmptyValue: true allowReserved: true - name: ending_before in: query schema: type: string description: Opaque 12-character cursor from a previous response's `previous_cursor`. allowEmptyValue: true allowReserved: true - $ref: '#/components/parameters/Version' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/PatientRecords' '400': description: '400' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': description: '401' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '403': description: '403' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: '404' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: '409' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '429': description: '429' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: Retry-After: $ref: '#/components/headers/RetryAfter' '500': description: '500' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Patients components: parameters: Version: name: Version in: header required: false description: Date-pinned API version. If omitted, Medblocks uses the version pinned on your API key. example: '2026-04-25' schema: type: string schemas: Patient: type: object properties: id: type: string description: Your developer-provided patient identifier. Use this in URL paths. resource_type: const: patient description: Resource type discriminator. Always `"patient"`. email: anyOf: - type: string - type: 'null' description: Patient's email. Null if never supplied. name: anyOf: - type: string - type: 'null' description: Patient's display name. Null if never set. metadata: type: object propertyNames: type: string additionalProperties: {} description: Developer-supplied metadata key/value pairs. created_at: type: string description: ISO 8601 timestamp of when the patient was created. required: - id - resource_type - email - name - metadata - created_at examples: - id: user_42 resource_type: patient email: jane@example.com name: Jane Doe metadata: plan: premium created_at: '2026-04-25T14:30:00.000Z' ErrorEnvelope: type: object properties: error: type: object properties: type: type: string description: Error category (e.g. authentication_error, permission_error, conflict_error). code: type: string description: Stable code within the type (e.g. invalid_api_key, external_id_already_exists). message: type: string description: Human-readable error message. param: anyOf: - type: string - type: 'null' description: Affected request parameter, if applicable. doc_url: type: string description: Link to the API errors reference. request_id: type: string description: Correlation id - include in support tickets. Echoes the `X-Request-Id` response header. required: - type - code - message - param - doc_url - request_id required: - error examples: - error: type: authentication_error code: invalid_api_key message: API key invalid param: null doc_url: https://medblocks.com/docs/reference/errors request_id: 9c9b6f7a-8e4f-4a3b-9c1e-6f3a2d8b7c4d PatientDetail: type: object properties: id: type: string description: Your developer-provided patient identifier. Use this in URL paths. resource_type: const: patient description: Resource type discriminator. Always `"patient"`. email: anyOf: - type: string - type: 'null' description: Patient's email. Null if never supplied. name: anyOf: - type: string - type: 'null' description: Patient's display name. Null if never set. metadata: type: object propertyNames: type: string additionalProperties: {} description: Developer-supplied metadata key/value pairs. created_at: type: string description: ISO 8601 timestamp of when the patient was created. connections: type: array items: type: object properties: id: type: string resource_type: const: connection patient_session_id: anyOf: - type: string - type: 'null' connection_id: type: string status: enum: - active - failed - expired - refresh_failed - disconnected type: string scopes: type: array items: type: string failure_code: anyOf: - type: string - type: 'null' failure_at: anyOf: - type: string - type: 'null' created_at: type: string updated_at: type: string required: - id - resource_type - patient_session_id - connection_id - status - scopes - failure_code - failure_at - created_at - updated_at description: All connections for this patient in this org, most-recent first. Only included on GET /patients/{id}. required: - id - resource_type - email - name - metadata - created_at - connections examples: - id: user_42 resource_type: patient email: jane@example.com name: Jane Doe metadata: plan: premium created_at: '2026-04-25T14:30:00.000Z' connections: - id: conn_01J9YR9N3X4VZ6P2K5RH7M3LMP resource_type: connection patient_session_id: ps_01J9YQ8M2X4VZ6P2K5RH7M3KQT connection_id: fhirsrc_02Zh2bnvRoWQJYgOAkI0Twcc status: active scopes: - patient/*.read - offline_access failure_code: null failure_at: null created_at: '2026-04-25T14:35:00.000Z' updated_at: '2026-04-25T14:35:00.000Z' ConnectionDisconnected: type: object properties: id: type: string description: Id of the disconnected connection. resource_type: const: connection disconnected: const: true description: Always `true` for a successful disconnect. required: - id - resource_type - disconnected examples: - id: conn_01J9YR9N3X4VZ6P2K5RH7M3LMP resource_type: connection disconnected: true PatientCreateInput: type: object properties: patient_id: type: string minLength: 1 maxLength: 200 description: Your stable identifier for this patient. Unique within your organization. email: type: string format: email description: Patient email for display and matching in your app. name: type: string maxLength: 200 description: Patient display name. metadata: type: object propertyNames: type: string additionalProperties: {} description: Additional metadata returned with the Patient. required: - patient_id examples: - patient_id: user_42 email: jane@example.com name: Jane Doe metadata: plan: premium PatientSession: type: object properties: id: type: string description: Public id, prefixed `ps_`. resource_type: const: patient_session description: Resource type discriminator. Always `"patient_session"` for this resource. status: enum: - open - complete - expired type: string description: Lifecycle state. `open` while the patient is interacting; `complete` after they click Done; `expired` after `expires_at`. url: type: string description: Patient-facing URL. Redirect the patient browser to this on patient_session creation. patient_id: type: string description: The patient's `patient_id` (developer-provided). connection_id: anyOf: - type: string - type: 'null' description: Connection ID (`fhirsrc_*`) when the session was started for one facility. Null when the patient chooses on the Medblocks-hosted page. recommended_connection_ids: anyOf: - type: array items: type: string - type: 'null' description: Connection IDs (`fhirsrc_*`) shown first on the Medblocks-hosted page. Null when the session was started for one facility. return_url: type: string description: Where the patient lands when finishing the flow. Echoes the request value. return_button_label: anyOf: - type: string - type: 'null' description: Custom label on the "Done — return to {label}" button. Null when not set. connections: type: array items: type: object properties: id: anyOf: - type: string - type: 'null' description: Connection id (`conn_*`). Null for failed auth attempts. connection_id: type: string description: EHR id (`fhirsrc_*`). status: enum: - active - failed - expired - refresh_failed - disconnected type: string description: Connection lifecycle state. `active` = data flowing; `disconnected` = access was revoked; everything else needs patient remediation. failure_code: anyOf: - type: string - type: 'null' description: Stable failure code when `status=failed`. Null otherwise. failure_at: anyOf: - type: string - type: 'null' description: ISO 8601 timestamp of the failure when `status=failed`. Null otherwise. created_at: type: string description: ISO 8601 timestamp of when this connection was created. required: - id - connection_id - status - failure_code - failure_at - created_at description: Connections created during this patient_session — including failed attempts. Empty when no EHR has been connected yet. expires_at: type: string description: ISO 8601 timestamp at which the patient_session token becomes invalid. metadata: type: object propertyNames: type: string additionalProperties: {} description: Developer-supplied metadata key/value pairs. created_at: type: string description: ISO 8601 timestamp of when the patient_session was created. required: - id - resource_type - status - url - patient_id - connection_id - recommended_connection_ids - return_url - return_button_label - connections - expires_at - metadata - created_at examples: - id: ps_01J9YQ8M2X4VZ6P2K5RH7M3KQT resource_type: patient_session status: open url: https://app.medblocks.com/c/9c9b6f7a8e4f4a3b9c1e6f3a2d8b7c4d patient_id: user_42 connection_id: null recommended_connection_ids: - fhirsrc_02Zh2bnvRoWQJYgOAkI0Twcc - fhirsrc_0a9Ea55aR4u7HaWMyFBN8wcc - fhirsrc_lcvXRHLSSI2WK9Prrt5Qggcc return_url: https://app.example.com/connected return_button_label: Acme Health connections: [] expires_at: '2026-04-25T15:00:00.000Z' metadata: signup_source: checkout created_at: '2026-04-25T14:30:00.000Z' PatientDeleted: type: object properties: id: type: string description: Id of the deleted patient. resource_type: const: patient deleted: const: true description: Always `true` for a successful delete. required: - id - resource_type - deleted examples: - id: user_42 resource_type: patient deleted: true PatientRecords: type: object properties: resource_type: const: list patient_id: type: string data: type: array items: type: object properties: source: anyOf: - type: string - type: 'null' description: fhirsrc id of the source EHR, resolvable via the FHIR sources API resource: type: object properties: resourceType: type: string description: FHIR resource type, e.g. `Observation`. id: type: string description: The source EHR's native logical id. required: - resourceType additionalProperties: {} required: - source - resource has_more: type: boolean next_cursor: anyOf: - type: string - type: 'null' previous_cursor: anyOf: - type: string - type: 'null' required: - resource_type - patient_id - data - has_more - next_cursor - previous_cursor examples: - resource_type: list patient_id: user_42 data: - source: fhirsrc_SMsb6YLMQLal1qm9AbdSPAcc resource: resourceType: Observation id: bmi-1 status: final has_more: true next_cursor: aB3xK9mQp2Lz previous_cursor: null headers: RetryAfter: description: Whole seconds to wait before retrying a rate-limited request. schema: type: integer minimum: 1 securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: API key (mb_sk_live_...) description: Medblocks API key for server-side requests.