Repository navigation
docs(ehr): add EHR developer API guide and SDK examples (revived from fm/pix-4408) - #26
Conversation
- 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)
Reviewer's GuideAdds 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 accesssequenceDiagram
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
Sequence diagram for signed EHR webhook handlingsequenceDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…CH, remove false DELETEs, PUT→POST for outcomes/config)
There was a problem hiding this comment.
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>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
| | `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 | |
There was a problem hiding this comment.
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.
| | `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 | |
| Every request requires an API key sent in the `X-API-Key` header. | ||
|
|
||
| ``` | ||
| X-API-Key: pk_live_abc123... |
There was a problem hiding this comment.
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.
| X-API-Key: pk_live_abc123... | |
| X-API-Key: dev_abc123... |
| 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}") |
There was a problem hiding this comment.
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.
| searchByPatient(patientId); | ||
| createPhq9Observation(patientId); |
There was a problem hiding this comment.
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.
| searchByPatient(patientId); | |
| createPhq9Observation(patientId); | |
| Promise.all([searchByPatient(patientId), createPhq9Observation(patientId)]).catch((err) => { | |
| console.error('Failed to run observation examples:', 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. |
There was a problem hiding this comment.
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.
| // 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. |
| searchByPatient(patientId); | ||
| createPhq9Observation(patientId); |
There was a problem hiding this comment.
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.
| # Send the full patient resource. Partial updates are not supported. | ||
| # The response returns the updated resource. |
There was a problem hiding this comment.
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.
Summary
Revives the EHR developer API guide that sat unmerged on
fm/pix-4408-f26-open-api-developer-docssince Aug 28. Straight cherry-pick of d7c9455 onto current master — all nine files are new (634-linedevelopers/ehr-api.mdplus 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:
Documentation: