Skip to content

docs(ehr): add EHR developer API guide and SDK examples (revived from fm/pix-4408) - #26

Merged
daggerstuff merged 2 commits into
masterfrom
docs/ehr-developer-api-guide
Oct 5, 2026
Merged

daggerstuff merged 2 commits into
masterfrom
docs/ehr-developer-api-guide

Conversation

@daggerstuff

@daggerstuff daggerstuff commented Oct 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

Revives the EHR developer API guide that sat unmerged on fm/pix-4408-f26-open-api-developer-docs since Aug 28. Straight cherry-pick of d7c9455 onto current master — all nine files are new (634-line developers/ehr-api.md plus Python/TypeScript SDK examples for auth, patients, observations, webhooks), so there's nothing to conflict.

The companion feature (PIX-4408, EHR module) shipped long ago; this documentation just never made it over. The old branch is deleted in favor of this one.

Refs: PIX-4408 (F2.6 — Open API + Developer Documentation)

🤖 Generated with Claude Code

Summary by Sourcery

Add developer documentation and SDK examples for integrating with the Pixelated EHR API.

Enhancements:

  • Add a comprehensive developer guide for the Pixelated EHR API covering authentication, FHIR resources, endpoints, pagination, rate limits, webhooks, analytics, outcomes, and sandbox access.
  • Provide Python and TypeScript SDK examples for authentication, patient and observation workflows, pagination, and secure webhook handling.

Documentation:

  • Document the EHR API and provide language-specific integration examples for developers.

- ehr-api.md: comprehensive developer guide (auth, errors, webhooks,
  rate limits, pagination, FHIR CapabilityStatement)
- sdk-examples/typescript/: 4 files (auth, patients, observations, webhooks)
- sdk-examples/python/: 4 files (auth, patients, observations, webhooks)

Refs: PIX-4408 (F2.6 — Open API + Developer Documentation)
@sourcery-ai

sourcery-ai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Adds the revived EHR developer documentation set: a 634-line API guide describing authentication, FHIR-style resources, endpoints, webhooks, and operations, plus parallel Python and TypeScript SDK examples for authentication, patients, observations, and webhook handling.

Sequence diagram for authenticated EHR API access

sequenceDiagram
    participant App as SDK application
    participant API as EHR API

    App->>API: patients.list with X-API-Key
    API-->>App: 200 data and pagination
    App->>API: observations.create
    API-->>App: 201 created Observation
Loading

Sequence diagram for signed EHR webhook handling

sequenceDiagram
    participant API as EHR API
    participant App as Developer webhook endpoint

    API->>App: POST webhook with X-Webhook-Signature
    App->>App: verify_webhook_signature
    alt signature valid
        App->>App: handle_webhook
        App-->>API: 200 received
    else signature invalid
        App-->>API: 401 error
    end
Loading

File-Level Changes

Change Details Files
Add a comprehensive EHR API developer guide covering integration conventions, security, resources, and operational behavior.
  • Document API base URL, API-key authentication, scopes, key management, response envelopes, errors, pagination, rate limits, and sandbox access.
  • Describe webhook event payloads, HMAC verification, delivery retries, FHIR R4 compatibility, and CapabilityStatement discovery.
  • Document CRUD and query endpoints for patients, encounters, observations, clinical notes, analytics, and outcomes.
developers/ehr-api.md
Provide Python SDK examples for common EHR integrations.
  • Show client initialization, environment-based credentials, connectivity checks, and patient CRUD/pagination workflows.
  • Demonstrate observation searches and creation of PHQ-9/GAD-7 FHIR resources.
  • Provide webhook signature verification, event dispatch, and Flask/FastAPI integration patterns.
developers/sdk-examples/python/auth.py
developers/sdk-examples/python/patients.py
developers/sdk-examples/python/observations.py
developers/sdk-examples/python/webhooks.py
Provide TypeScript SDK examples paralleling the documented EHR workflows.
  • Show client configuration, retry/timeout settings, sandbox usage, and connectivity checks.
  • Demonstrate patient and observation search, CRUD, pagination, and outcome-score resource creation.
  • Provide typed webhook handling with raw-body HMAC verification and Express integration.
developers/sdk-examples/typescript/auth.ts
developers/sdk-examples/typescript/patients.ts
developers/sdk-examples/typescript/observations.ts
developers/sdk-examples/typescript/webhooks.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@linear-code

linear-code Bot commented Oct 5, 2026

Copy link
Copy Markdown

PIX-4408

…CH, remove false DELETEs, PUT→POST for outcomes/config)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="developers/ehr-api.md" line_range="32-34" />
<code_context>
+
+| Scope | Description |
+|-------|-------------|
+| `read` | Read access to EHR resources (patients, encounters, observations, notes, analytics, outcomes) |
+| `write` | Create and update EHR resources |
+| `admin` | Full access including key management, configuration, and administrative endpoints |
+
+A key's scope determines which endpoints it can call. A `read`-scoped key
</code_context>
<issue_to_address>
**issue (bug_risk):** The guide assigns API-key management to the `admin` scope, but the repository's route configuration requires `developer:manage` for `/api/developer/api-keys/*`. A key created with the documented scope cannot perform the documented key-management operations.

**Triggers:** When an integrator follows the documented scope table to create an administrative key.

**Suggested fix:** Document `developer:manage` as the required key-management scope and distinguish it from the `admin` scope.

```suggestion
| `read` | Read access to EHR resources (patients, encounters, observations, notes, analytics, outcomes) |
| `write` | Create and update EHR resources |
| `developer:manage` | Required for API-key management operations |
| `admin` | Full access to configuration and administrative endpoints |
```
</issue_to_address>

### Comment 2
<location path="developers/ehr-api.md" line_range="21" />
<code_context>
+
+## Authentication
+
+Every request requires an API key sent in the `X-API-Key` header.
+
+```
+X-API-Key: pk_live_abc123...
+```
</code_context>
<issue_to_address>
**issue (bug_risk):** The authentication example uses a `pk_live_` key, but the repository's API-key implementation generates and validates `dev_`-prefixed keys. Copying the documented example format produces a key that fails API-key validation.

**Triggers:** When a developer uses the documented key format or assumes production keys use the `pk_live_` prefix.

**Suggested fix:** Use the actual `dev_` key format in the example and explain that the full key is returned only at creation.

```suggestion
X-API-Key: dev_abc123...
```
</issue_to_address>

### Comment 3
<location path="developers/sdk-examples/python/webhooks.py" line_range="97-109" />
<code_context>
+
+def verify_connection() -> None:
+    """Call a lightweight endpoint to confirm the key works."""
+    try:
+        patients = client.patients.list(limit=1)
+        total = patients.pagination.total
</code_context>
<issue_to_address>
**issue (broader_impact):** The examples declare `patient.updated`, `encounter.updated`, `observation.updated`, and `note.created` as supported webhook events, but dispatch handlers exist only for patient-created, patient-updated, encounter-created, observation-created, and note-signed. The declared-but-unhandled events are acknowledged with HTTP 200 and are therefore silently discarded without processing or retry.

**Triggers:** When the API delivers `encounter.updated`, `observation.updated`, or `note.created` events.

**Suggested fix:** Add handlers and dispatch cases for every declared event, or remove unsupported events from `WEBHOOK_EVENTS`; do not return 200 for events that were not processed.
</issue_to_address>

### Comment 4
<location path="developers/sdk-examples/typescript/observations.ts" line_range="161-162" />
<code_context>
+
+// Run examples
+const patientId = 'patient-001';
+searchByPatient(patientId);
+createPhq9Observation(patientId);
</code_context>
<issue_to_address>
**issue (bug_risk):** The example starts `searchByPatient` and `createPhq9Observation` without awaiting either promise or attaching rejection handlers. If either request fails, the failure becomes an unhandled promise rejection and the example can exit without reporting the operation error.

**Triggers:** When either asynchronous API call rejects, such as for an invalid key, validation failure, or network error.

**Suggested fix:** Await the calls in an async entry point, or use `Promise.all` with explicit error handling.

```suggestion
Promise.all([searchByPatient(patientId), createPhq9Observation(patientId)]).catch((err) => {
  console.error('Failed to run observation examples:', err);
});
```
</issue_to_address>

### Comment 5
<location path="developers/sdk-examples/typescript/webhooks.ts" line_range="142-144" />
<code_context>
+        console.log(`Unhandled event type: ${payload.eventType}`);
+    }
+  } catch (err) {
+    // Log the error but still return 200 to prevent retries.
+    // The API retries on non-2xx responses. If your handler fails
+    // transiently, return 500 to trigger a retry.
+    console.error('Webhook handler error:', err);
+    return {
</code_context>
<issue_to_address>
**nitpick:** The webhook handler comment says it returns 200 to prevent retries, but the catch block immediately returns status 500 to trigger a retry when a handler fails. Readers following the comment can misunderstand the actual retry behavior.

**Triggers:** When an event handler throws.

**Suggested fix:** Change the comment to state that successful processing returns 200 and handler failures return 500 to request a retry.

```suggestion
    // Successful processing returns 200.
    // Handler failures return 500 to request a retry.
```
</issue_to_address>

Fix all in Cursor

Sourcery assessment

Approval pending. 4 findings to address first.

Blocking findings: developers/ehr-api.md:34, developers/ehr-api.md:21, developers/sdk-examples/python/webhooks.py:109, developers/sdk-examples/typescript/observations.ts:162


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread developers/ehr-api.md
Comment on lines +32 to +34
| `read` | Read access to EHR resources (patients, encounters, observations, notes, analytics, outcomes) |
| `write` | Create and update EHR resources |
| `admin` | Full access including key management, configuration, and administrative endpoints |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The guide assigns API-key management to the admin scope, but the repository's route configuration requires developer:manage for /api/developer/api-keys/*. A key created with the documented scope cannot perform the documented key-management operations.

Triggers: When an integrator follows the documented scope table to create an administrative key.

Suggested fix: Document developer:manage as the required key-management scope and distinguish it from the admin scope.

Suggested change
| `read` | Read access to EHR resources (patients, encounters, observations, notes, analytics, outcomes) |
| `write` | Create and update EHR resources |
| `admin` | Full access including key management, configuration, and administrative endpoints |
| `read` | Read access to EHR resources (patients, encounters, observations, notes, analytics, outcomes) |
| `write` | Create and update EHR resources |
| `developer:manage` | Required for API-key management operations |
| `admin` | Full access to configuration and administrative endpoints |

Fix in Cursor

Comment thread developers/ehr-api.md
Every request requires an API key sent in the `X-API-Key` header.

```
X-API-Key: pk_live_abc123...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The authentication example uses a pk_live_ key, but the repository's API-key implementation generates and validates dev_-prefixed keys. Copying the documented example format produces a key that fails API-key validation.

Triggers: When a developer uses the documented key format or assumes production keys use the pk_live_ prefix.

Suggested fix: Use the actual dev_ key format in the example and explain that the full key is returned only at creation.

Suggested change
X-API-Key: pk_live_abc123...
X-API-Key: dev_abc123...

Fix in Cursor

Comment on lines +97 to +109
try:
if event_type == "patient.created":
on_patient_created(payload)
elif event_type == "patient.updated":
on_patient_updated(payload)
elif event_type == "encounter.created":
on_encounter_created(payload)
elif event_type == "observation.created":
on_observation_created(payload)
elif event_type == "note.signed":
on_note_signed(payload)
else:
print(f"Unhandled event type: {event_type}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (broader_impact): The examples declare patient.updated, encounter.updated, observation.updated, and note.created as supported webhook events, but dispatch handlers exist only for patient-created, patient-updated, encounter-created, observation-created, and note-signed. The declared-but-unhandled events are acknowledged with HTTP 200 and are therefore silently discarded without processing or retry.

Triggers: When the API delivers encounter.updated, observation.updated, or note.created events.

Suggested fix: Add handlers and dispatch cases for every declared event, or remove unsupported events from WEBHOOK_EVENTS; do not return 200 for events that were not processed.

Fix in Cursor

Comment on lines +161 to +162
searchByPatient(patientId);
createPhq9Observation(patientId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The example starts searchByPatient and createPhq9Observation without awaiting either promise or attaching rejection handlers. If either request fails, the failure becomes an unhandled promise rejection and the example can exit without reporting the operation error.

Triggers: When either asynchronous API call rejects, such as for an invalid key, validation failure, or network error.

Suggested fix: Await the calls in an async entry point, or use Promise.all with explicit error handling.

Suggested change
searchByPatient(patientId);
createPhq9Observation(patientId);
Promise.all([searchByPatient(patientId), createPhq9Observation(patientId)]).catch((err) => {
console.error('Failed to run observation examples:', err);
});

Fix in Cursor

Comment on lines +142 to +144
// Log the error but still return 200 to prevent retries.
// The API retries on non-2xx responses. If your handler fails
// transiently, return 500 to trigger a retry.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: The webhook handler comment says it returns 200 to prevent retries, but the catch block immediately returns status 500 to trigger a retry when a handler fails. Readers following the comment can misunderstand the actual retry behavior.

Triggers: When an event handler throws.

Suggested fix: Change the comment to state that successful processing returns 200 and handler failures return 500 to request a retry.

Suggested change
// Log the error but still return 200 to prevent retries.
// The API retries on non-2xx responses. If your handler fails
// transiently, return 500 to trigger a retry.
// Successful processing returns 200.
// Handler failures return 500 to request a retry.

Fix in Cursor

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

Comment on lines +161 to +162
searchByPatient(patientId);
createPhq9Observation(patientId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The observations.ts example script has unhandled promise rejections. Asynchronous API calls are made without await or .catch(), which will crash the Node.js process on any API failure.
Severity: HIGH

Suggested Fix

Wrap the top-level asynchronous calls in an async IIFE (Immediately Invoked Function Expression) and use a try...catch block to handle errors. Alternatively, chain a .catch() handler to each promise-returning function call to log the error and prevent the process from crashing, similar to the pattern used in patients.ts.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: developers/sdk-examples/typescript/observations.ts#L161-L162

Potential issue: The `observations.ts` example script makes asynchronous API calls at
the module level without handling potential promise rejections. Specifically,
`searchByPatient(patientId)` and `createPhq9Observation(patientId)` are called without
`await` or a `.catch()` block. If these API calls fail for any reason, such as a missing
`PIXELATED_API_KEY` environment variable or a network issue, the resulting rejected
promise will be unhandled. In Node.js versions 15 and later, an unhandled promise
rejection terminates the process, causing the script to crash.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines +89 to +90
# Send the full patient resource. Partial updates are not supported.
# The response returns the updated resource.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Comments in the Python and TypeScript patient examples incorrectly state that partial updates are not supported, contradicting the API's PATCH functionality and potentially misleading developers.
Severity: MEDIUM

Suggested Fix

Update the comments in developers/sdk-examples/python/patients.py and developers/sdk-examples/typescript/patients.ts to accurately reflect that the API uses PATCH and supports partial updates. The comment should encourage sending only the fields that need to be changed.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: developers/sdk-examples/python/patients.py#L89-L90

Potential issue: The comments in the `patients.py` and `patients.ts` SDK examples are
outdated and misleading. They incorrectly state that for patient updates, "Partial
updates are not supported" and that the "full patient resource" must be sent. This
contradicts the API's use of the `PATCH` method, which is designed for partial updates.
Developers following this incorrect guidance may write inefficient code that sends
unnecessary data, or worse, cause unintentional data loss by sending `null` for fields
they did not intend to update.

Also affects:

  • developers/sdk-examples/typescript/patients.ts:91~92

Did we get this right? 👍 / 👎 to inform future reviews.

@daggerstuff
daggerstuff merged commit 5791323 into master Oct 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant