Server backend
A trusted server route or serverless function that can keep credentials secret and call HTTPS APIs.
Complete integration documentation
This is the implementation contract for EMR teams and their coding assistants. The recommended picker integration requires a backend, an iframe-capable browser interface, and storage for the returned assignment snapshot.
Coding-assistant handoff
Replace the three angle-bracket placeholders, then paste this into Codex, Cursor, Claude Code, or your preferred repository-aware coding assistant.
# Integrate the PT365 HEP picker into this EMR
You are working in an existing EMR codebase. Inspect its framework, authentication, persistence, testing conventions, and content-security policy before making changes. Implement the smallest native integration that follows the repository's existing patterns.
## PT365 configuration
- Environment: sandbox
- API base URL: https://www.physicaltherapy365.com/api/v1
- Client ID: <YOUR_PT365_CLIENT_ID>
- Allowlisted parent origin: <YOUR_EMR_ORIGIN>
- OpenAPI contract: https://www.physicaltherapy365.com/api/v1/openapi.json
- Human integration guide: https://www.physicaltherapy365.com/hep/developers/guide
The PT365 client secret will be supplied separately. Store it only in the application's server-side secret manager as PT365_CLIENT_SECRET. Never paste it into source code, browser JavaScript, logs, test fixtures, or this prompt.
## Non-negotiable privacy boundary
Never send PT365 a patient name, patient/chart/encounter ID, diagnosis, note, date of birth, email, phone number, address, insurance data, or other patient context. Reject accidental patient-oriented fields before making the request. PT365 receives only an exact parent origin, an opaque clinician identifier, optional visual theme, and optional existing exercise selections.
## Required implementation
1. Add server-only environment configuration for PT365_CLIENT_ID, PT365_CLIENT_SECRET, and PT365_API_BASE_URL.
2. Add a server-side OAuth client-credentials helper. POST grant_type=client_credentials to /oauth/token using HTTP Basic authentication. Cache the access token until shortly before its one-hour expiry.
3. Add an authenticated EMR backend endpoint that creates a picker session by POSTing to /picker/sessions with catalog:read/picker:create access. Do not allow the browser to call PT365 with the client secret.
4. Derive a stable opaque clinician ID with a one-way keyed hash of the EMR's internal clinician ID. Do not send the raw internal ID.
5. Send this picker-session body:
{"origin":"<YOUR_EMR_ORIGIN>","clinicianId":"<opaque-hash>","theme":{"brandName":"<EMR_NAME>","primaryColor":"#215C5C","backgroundColor":"#F5F8F8"}}
6. Open the returned data.url in a responsive modal iframe. Add https://www.physicaltherapy365.com to the application's Content-Security-Policy frame-src directive.
7. Listen for window message events. Accept events only when event.origin is exactly https://www.physicaltherapy365.com and event.source is the picker iframe's contentWindow.
8. Handle pt365.ready, pt365.selection.completed, and pt365.cancelled. Treat iframe load failures, session expiry, and malformed messages as local integration errors. On completion, validate and store payload.items in the EMR.
9. Preserve each returned exercise ID, revision, ordered prescription, clinician instruction, and full content snapshot. Never replace an existing assignment snapshot when newer catalog content appears.
10. When editing, create a new picker session with initialSelection built from the stored item prescriptions. Patient context and clinician instructions must not be included in the server request; instructions are entered in the picker and returned browser-to-browser.
11. Add clear loading, timeout, cancellation, and retry states. Never interrupt an already-open picker because of a licensing warning or temporary catalog-sync problem.
12. Add automated tests for server-only credentials, token caching, forbidden patient fields, origin validation, event-source validation, completion storage, cancellation, editing, and PT365 outage behavior.
## Browser completion contract
The completion event is shaped as:
{
"type": "pt365.selection.completed",
"payload": {
"items": [
{
"order": 1,
"prescription": {
"exerciseId": "ex_...",
"revision": "...",
"side": "left|right|bilateral|not_applicable",
"sets": 3,
"repetitions": 10,
"holdSeconds": 5,
"durationMinutes": null,
"frequency": { "times": 1, "period": "day" },
"resistance": null,
"restSeconds": null,
"customDosage": null,
"clinicianInstructions": ""
},
"exercise": { "id": "ex_...", "revision": "..." }
}
]
}
}
Use the OpenAPI document as the source of truth for complete schemas. Preserve unknown response fields for forward compatibility, but do not send undocumented request fields.
## Definition of done
- A clinician can open the sandbox picker, search the fixed 10 exercises, prescribe and reorder items, complete the picker, edit the stored program, and cancel safely.
- No PT365 secret reaches the browser.
- No patient or encounter data reaches PT365.
- Origin and iframe-window validation are both enforced.
- The exact returned revision and snapshot are stored with the assignment.
- Relevant tests, type checks, and linters pass.
- Document the files changed, environment variables required, and a manual sandbox test procedure.
Proceed with the implementation. Ask for input only if the existing application lacks a server-side execution environment, authenticated backend route, persistent assignment storage, or iframe support.01 · Before you start
A trusted server route or serverless function that can keep credentials secret and call HTTPS APIs.
An iframe-capable web interface with exact-origin and iframe-window postMessage validation.
Persistent JSON storage for ordered prescriptions and the exact exercise content snapshot returned at completion.
A stable, non-reversible identifier used only for monthly-active licensing. Never send the raw internal user ID.
02 · Server only
Store PT365_CLIENT_ID and PT365_CLIENT_SECRET in the EMR's secret manager. Use HTTP Basic authentication and cache the returned bearer token until shortly before its one-hour expiration.
curl -u "$PT365_CLIENT_ID:$PT365_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
https://www.physicaltherapy365.com/api/v1/oauth/tokenProduction and sandbox use the same schemas. Sandbox credentials are restricted to the ten designated exercises and cannot access production content.
03 · Recommended path
The EMR backend creates a 15-minute session. The origin must exactly equal an origin registered for the credential. The clinician identifier must be an opaque 8–128 character value.
curl -X POST \
-H "Authorization: Bearer $PT365_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"origin": "https://sandbox.your-emr.com",
"clinicianId": "opaque-clinician-7f21",
"theme": {
"brandName": "Your HEP",
"primaryColor": "#215C5C",
"backgroundColor": "#F5F8F8"
}
}' \
https://www.physicaltherapy365.com/api/v1/picker/sessionsRender the returned data.url as the iframe source. Add https://www.physicaltherapy365.com to the EMR's Content-Security-Policy frame-src directive.
const iframe = document.querySelector("#pt365-picker");
window.addEventListener("message", (event) => {
if (event.origin !== "https://www.physicaltherapy365.com") return;
if (event.source !== iframe.contentWindow) return;
if (event.data?.type === "pt365.selection.completed") {
saveExactExerciseSnapshots(event.data.payload.items);
}
});04 · Medical-record durability
The completion payload contains ordered items. Each item has an order, editable prescription, and complete versioned exercise snapshot. Store all three together in the EMR assignment record.
Side, sets, repetitions, hold, duration, frequency, resistance, rest, custom dosage, and clinician instructions.
Immutable opaque exercise ID and exact content revision used for this assignment.
Name, description, steps, cue, taxonomy, defaults, and checksummed start/end media.
Never rewrite an old assignment when a new exercise revision is published or withdrawn.
05 · Existing programs
Build initialSelection from the stored prescriptions and request another picker session. PT365 validates that every selected exercise and revision is available in the credential's licensed environment.
Clinician instructions are intentionally not accepted in the server-created session. They are entered in the picker browser and returned directly to the EMR, keeping patient-specific free text away from PT365 request infrastructure.
06 · Optional advanced integration
/api/v1/exercisesSearch or page the licensed catalog with stable opaque cursors.
/api/v1/exercises/{id}?revision=…Retrieve current or historical immutable content.
/api/v1/taxonomiesDiscover controlled filter values instead of hardcoding them.
/api/v1/catalog/changes?cursor=…Apply ordered additions, updates, deprecations, and withdrawals.
Commit the next cursor only after successfully storing the entire page. Treat cursors as opaque. A withdrawal should prevent new assignment while preserving historical assignments and their stored snapshots.
07 · Optional notifications
Create a webhook for exercise.published, exercise.updated, exercise.deprecated, and exercise.withdrawn. Store the returned signing secret when the endpoint is created; it is shown only once.
Verify X-PT365-Signature against the unmodified body using HMAC-SHA256 of <X-PT365-Timestamp>.<raw-body>. Reject stale timestamps, compare signatures in constant time, and deduplicate using X-PT365-Delivery. Return a successful response quickly and use the change feed as the recovery source. Non-2xx deliveries are retried up to eight times.
08 · No-PHI boundary
Only the partner origin, opaque clinician identifier, visual theme, and structured exercise selections belong in a picker-session request. Unknown fields are rejected.
09 · Definition of done
sandbox.initialSelection and edit it.