ezpharma API docs Client workspace Get API access ↗

Scoped client resource API · 2026-10

Test tenants only · live access disabled

This version requires the matching application and schema deployment. It is available only for approved synthetic and sandbox tenants; real-patient access remains disabled. Create an expiring credential with explicit resource scopes and patient restrictions in your client workspace. Keep it on your backend. Tenant authority comes from the credential; never send a tenant header or tenant identifier as authorization.

POST /v1/client/2026-10/query

Use a JSON body with resource, optional exact patientId/id, limit (1–100) and cursor. Resources: patients, episodes, intakes, consents, appointments, prescriptions, labs, results, records, documents, payments, fulfillment, packages, communications and renewals. Each requires its own resource:read scope. These are minimum-necessary summaries: no intake answers, raw clinical content, private document links or card data. Clinical rows also require the current environment and tenant capability gates.

Backend request · synthetic identifiers only
const response = await fetch(baseUrl + '/v1/client/2026-10/query', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + serverCredential,
    'Content-Type': 'application/json' },
  body: JSON.stringify({ resource: 'patients', limit: 50 })
})
if (!response.ok) throw new Error('integration_request_failed')
const page = await response.json()
// Store permitted data securely. Never log the retrieval payload.

Responses include data, nextCursor and changeCursor. Save the first change cursor before finishing snapshot pagination across resources. Then POST to /v1/client/2026-10/events with that cursor. Apply each page and its checkpoint in one durable consumer transaction. Keep polling to reconcile missed callbacks. Signed cursors bind to the credential and filters; after seven days, 410 requires a fresh snapshot.

POST /v1/client/2026-10/crosswalks with resource, id, externalId requires mappings:write plus read scope. Exact retries are safe; conflicting immutable links return 409. Use only opaque external IDs in bodies.

Minimal signed notifications

Subscribe an existing approved sandbox lifecycle endpoint to selected resources. Notifications contain only id, schemaVersion, type, resource, resourceId, occurredAt. Types are resource.changed and resource.removed. Fetch current authorized state; never infer a clinical or payment action from delivery.

Verify HMAC-SHA256 of the exact timestamp, a period and raw body using the endpoint key. Headers are x-ezpharma-timestamp, x-ezpharma-signature: sha256=… and x-ezpharma-event-id. Enforce a 300-second clock window, constant-time comparison, matching signed payload/header IDs and durable event deduplication. Timestamp checking alone does not prevent duplicate effects.

Delivery is at least once and unordered, with eight automatic attempts, exponential minute backoff capped at eight hours, fenced 60-second leases, pause and controlled retry. Immutable coded attempt evidence is preserved. Synthetic callbacks never reach a network. API credentials, scopes, patient restrictions and capability/connection availability are rechecked before dispatch.

Errors: 401 credential denied; 403 scope/capability/live boundary; 404 not found/unauthorized; 409 conflict; 410 resync; 422 malformed request/cursor; 429 shared 120/minute/credential with Retry-After: 60; 503 dependency unavailable. No external chart, prescribing, payment or care-state write is authorized by these read credentials.

Download OpenAPI contract · Notification schema · Signature and transactional sync example

Customize intakes and embed a care cart

Client acceptance pending

Open Customize intake in your workspace to change bounded typography, colors, spacing, layout, progress, completion text and operational fields, then preview at desktop or 390px. Governed identity/clinical/consent content, products/prices and payment policy require staff review. Drafts preserve the published version; stale editors are rejected and unchanged retries reuse a command ID.

Inline care cart
<script src="https://www.ezpharma.app/intake-embed.js"></script>
<div id="care-cart"></div>
<script>
  EzPharma.mountCart('#care-cart', { publicKey: 'YOUR_TEST_INSTALLATION_KEY' })
</script>

EzPharma.openCart({ publicKey }) opens the same modal flow. A hosted cart uses /intake-frame?publicKey=YOUR_TEST_INSTALLATION_KEY&mode=hosted&cart=true. Only one plan at quantity one is supported. Multi-item/quantity, coupons, taxes and cross-currency pricing are unsupported. Selection/removal uses the existing durable intake; required intake and consent precede the authoritative quote and connected-account hosted Checkout. Browser price/quantity fields never control the charge.

Opaque resume credentials remain in the frame’s sessionStorage; answers remain server-side and in memory. In-progress sessions load their original journey via GET /v1/intake/sessions/:sessionId/journey with a Bearer session token. Publishing a price or design revision does not change those sessions. Frames retain origin approval and the parent challenge. Resume is tab/device scoped; storage restrictions can prevent interruption recovery.

Completion retains the opaque session reference onComplete({ sessionId, status: 'completed' }). No answer, direct patient identity or secret enters host callbacks. Checkout retains existing server/provider idempotency and signed-webhook status; payment never implies clinical approval, prescribing, dispensing or shipment. Keep uncertified live checkout disabled. Copy intake/cart snippets from the approved installation card and test every supported placement before enabling it.

EzPharma API · v1

Guided product tour — browser-only orientation with role paths and explicit availability limits. Simulated outcomes are not API verification or partner approval.

One integration for the medication journey.

Build secure patient intake, receive verified clinical and pharmacy updates, and keep every approved system aligned from visit through delivery.

API available Production access requires certification Last reviewed October 30, 2026

Tenant-resolved access

Credentials and installation keys select the tenant. A caller-supplied tenant ID never grants access.

Safe retries

Session creation and inbound events are designed for idempotent, retryable delivery.

Evidence first

Source events are retained and mapped through explicit identifiers—never patient-name guessing.

Independent state

Clinical, payment, prescription, dispense, and shipment states do not imply one another.

Controlled production access

The API is deployed, but access is provisioned per client and connection. Sandbox validation, credential exchange, domain approval, data agreements, and partner certification may be required before live data or money is enabled.

Start here

Quickstart

Every integration starts in an approved sandbox connection. Your onboarding package contains only the credentials and identifiers required for your use case.

Request access

Tell us whether you are integrating intake, clinical events, pharmacy events, or client timelines.

Verify in sandbox

Sign one synthetic request, retry it, and confirm the resulting timeline or lifecycle callback.

Certify for live use

Approve mappings, failure paths, operators, and data controls before the connection is enabled.

Send your first signed event

Compute the signature over the exact request bytes. Do not parse and reserialize the body after signing.

timestamp=$(date +%s)
body='{"schema_version":"1.0","event_id":"11111111-1111-4111-8111-111111111111","event_type":"visit.completed","tenant_id":"tenant_from_onboarding","care_episode_id":"episode_demo_123","source":{"organization_id":"organization_from_onboarding","connection_id":"00000000-0000-4000-8000-000000000000","system":"example-ehr","source_event_id":"ehr-event-88421"},"subject":{"patient_id":"synthetic-patient-42"},"occurred_at":"2026-10-22T14:30:00Z","received_at":"2026-10-22T14:30:00Z","data":{"status":"completed"}}'
signature=$(printf '%s' "$timestamp.$body" | openssl dgst -sha256 -hmac "$EZPHARMA_WEBHOOK_SECRET" -hex | sed 's/^.* //')

curl https://www.ezpharma.app/v1/integrations/$EZPHARMA_CONNECTION_ID/events \
  -H "Content-Type: application/json" \
  -H "X-EzPharma-Timestamp: $timestamp" \
  -H "X-EzPharma-Signature: sha256=$signature" \
  --data-raw "$body"
Expected response

A new accepted event returns 202. Replaying the same source event returns 200 with duplicate: true and does not create a second state transition.

Access

Environments & access

Sandbox and live resources are separate even when they share the same API origin. A credential, installation, connection, mapping, or merchant account belongs to exactly one environment.

EnvironmentBase URLUse
Sandboxhttps://www.ezpharma.appSynthetic patients, test payments, mapping validation, retry and failure testing.
Livehttps://www.ezpharma.appEnabled only after the specific client, partner, domains, credentials, and operating controls are certified.

Environment is carried by the provisioned resource—not a query parameter. Never infer live access from an HTTP success in sandbox.

Core concept

Authentication

EzPharma uses the narrowest credential for each surface. The credential resolves tenant access server-side.

SurfaceCredentialTransport
IntakePublishable installation key, then opaque session tokenRequest body or Authorization: Bearer
Inbound partner eventsConnection-specific HMAC secretX-EzPharma-Timestamp and X-EzPharma-Signature
Client timelineTenant-scoped API keyAuthorization: Bearer <key>
Dashboard APIsIndividual user session plus membershipSecure HTTP-only session cookie
Patient portalShort-lived patient session resolved from emailed-code verificationSecure HTTP-only session cookie

HMAC construction

Signature input
signed_payload = unix_timestamp + "." + exact_raw_request_body
digest = HMAC_SHA256(connection_secret, signed_payload)
header = "sha256=" + lowercase_hex(digest)

Requests outside the configured replay window are rejected. Rotate credentials through your EzPharma onboarding contact; secrets are revealed only through an approved secure exchange.

No tenant selector

Do not send a tenant header or tenant query parameter. If a canonical event contains tenant_id, it must match the tenant already resolved by the connection.

Core concept

Idempotency & retries

Network failures are normal. EzPharma separates “safe to retry” from “accepted exactly once” by operation.

OperationIdempotency keyRetry behavior
Create intake sessionIdempotency-Key header, up to 160 charactersReuse the same key for the same logical creation attempt.
Canonical eventsource.connection_id + source.source_event_idDuplicate delivery returns success without a new event.
Pharmacy eventConnection + event_idDuplicate delivery returns success without a new event.
Lifecycle callbackX-EzPharma-Event-IdConsumers must persist and deduplicate this ID.
Patient care messagecommandId in the JSON bodyExact retries return the original message; conflicting reuse is rejected.

Retry 429 and 5xx responses with bounded exponential backoff and jitter. Do not automatically retry validation, authentication, or mapping errors without changing the request or configuration.

Core concept

States stay independent

A successful payment is not a clinical approval. A signed prescription is not a filled dispense. A shipping label is not a delivery.

Clinicalrequires_review · under_review · approved · not_approved
Paymentnot_required · pending · authorized · succeeded · failed
Prescriptiondrafted · signed · sent · received · clarification_requested · cancelled
Dispensepharmacist_review · insurance_processing · preparing · filled · partially_filled
Shipmentlabel_created · picked_up · in_transit · delayed · delivered · exception

Integrations should render and reconcile each domain separately. Unknown external statuses are rejected for explicit mapping rather than silently coerced.

Core concept

Errors

Errors are JSON objects with a stable machine-readable error code. Some validation responses also include safe details.

Example response
{
  "error": "external_mapping_required",
  "domain": "prescription",
  "externalEventId": "partner-event-123"
}
StatusMeaningAction
400Malformed JSON or request.Correct the payload; do not retry unchanged.
401Missing, invalid, inactive, or expired credential/signature.Check credential and clock; rotate only through the approved path.
403Credential is valid but not allowed for the resource or adapter.Confirm the provisioned connection and domain.
404Resource does not exist within the credential's tenant.Check the tenant-scoped identifier.
409Required external identifier mapping is missing or ambiguous.Add or correct the mapping; never guess from demographics.
422Payload shape or status is unsupported.Fix the contract or approve a new explicit mapping.
429Rate limit reached.Honor Retry-After and back off.
5xxTemporary server failure.Retry idempotently with exponential backoff.
Intake API

Retrieve an installation

GET/v1/intake/installations/{publicKey}Browser-safe

Returns the approved public bootstrap for an active installation: brand, published journey, modes, and safe configuration.

ParameterTypeDescription
publicKey requiredstring · pathPublishable key issued for one sandbox or live website installation.
Request
curl https://www.ezpharma.app/v1/intake/installations/pk_sandbox_example

A public key identifies an installation but is not a general API credential. Session creation still enforces installation state, allowed origin, request limits, environment, and live approval.

Intake API

Create a session

POST/v1/intake/sessionsOrigin checked

Creates a version-pinned intake session for an approved installation.

FieldTypeDescription
publicKey requiredstringInstallation publishable key.
installationModestringinline, modal, hosted, or headless. Defaults to inline.
sourcestringSafe source label for attribution. Do not include patient data.
externalReferencestringOptional client correlation value approved for this flow.
Request
curl https://www.ezpharma.app/v1/intake/sessions \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: intake-attempt-018fa' \
  -H 'X-EzPharma-Parent-Origin: https://client.example' \
  -d '{
    "publicKey": "pk_sandbox_example",
    "installationMode": "inline",
    "source": "client-site"
  }'

The 201 response contains session, including the opaque token needed for subsequent session operations. Keep the token out of URLs, analytics, and logs.

Resume a session

GET/v1/intake/sessions/{sessionId}

Send the opaque token as Authorization: Bearer <session_token> to retrieve the version-pinned session. An invalid token and an unknown session both return 404 session_not_found.

Intake API

Save answers

PATCH/v1/intake/sessions/{sessionId}/answers

Validates and saves the current answer set against the session's immutable journey version.

FieldTypeDescription
token requiredstringOpaque token returned at session creation.
answers requiredobjectKeys and values defined by the pinned journey. Send only requested information.
currentStepintegerZero-based current step. Defaults to 0.
Request
curl https://www.ezpharma.app/v1/intake/sessions/session_123/answers \
  -X PATCH \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "session_token_from_create",
    "currentStep": 1,
    "answers": {
      "synthetic_example_field": "example value"
    }
  }'
Handle clinical data deliberately

Clinical answers belong only in the encrypted request body and approved durable store. Never copy them into query strings, URLs, browser analytics, payment metadata, lifecycle metadata, or application logs.

Intake API

Create a quote and checkout

POST/v1/intake/sessions/{sessionId}/quote

Creates the authoritative, immutable quote for the selected product configuration.

POST/v1/intake/sessions/{sessionId}/checkoutPer-client enablement

Creates a provider-hosted checkout using a tenant-owned merchant connection. EzPharma does not receive raw card data.

Quote, then checkout
curl https://www.ezpharma.app/v1/intake/sessions/session_123/quote \
  -X POST -H 'Content-Type: application/json' \
  -d '{"token":"session_token_from_create"}'

curl https://www.ezpharma.app/v1/intake/sessions/session_123/checkout \
  -X POST -H 'Content-Type: application/json' \
  -d '{
    "token":"session_token_from_create",
    "merchantConnectionId":"merchant_connection_from_bootstrap"
  }'

Checkout creation does not mark payment successful. The payment state changes only after the provider's signed webhook is verified. Payment state does not change clinical state.

Intake API

Complete a session

POST/v1/intake/sessions/{sessionId}/complete

Finalizes a valid session and returns safe completion content plus patient-status access when configured.

Request
curl https://www.ezpharma.app/v1/intake/sessions/session_123/complete \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{"token":"session_token_from_create"}'

Store only the minimum result needed by the approved user experience. Treat patient-status access as a secret and do not put it into analytics or lifecycle metadata.

Patient portal

Care dashboard

GET/v1/patient/portal/sessionPatient session required

Returns { authenticated, portal } using the HTTP-only patient session. Invalid, expired and revoked sessions return authenticated: false.

The server resolves account and tenant; caller-supplied identities grant no access. Only completed, explicitly linked intakes are included. Responses use no-store and no-referrer. Do not copy this response into browser storage, logs, analytics or partner lifecycle metadata.

ProjectionMeaning
portal.care.available / syntheticClinical capability availability and explicit synthetic-practice label. Unavailable care returns empty dashboard arrays.
appointmentsOrder-bound ID, status, modality and ISO start/end. Display with an explicit timezone. Internal joining links and notes are excluded; request instructions or changes through secure messaging.
fulfillmentIndependent order and item state, each package's status, reshipOf reference, carrier/tracking text, last recorded update, estimated date and support flag. Missing movement never proves delivery.
renewalsEnrollment status/due date, latest cycle status/evaluation time and allowlisted gate key/status pairs. Missing evidence is unavailable. Clinical approval remains independent of payment.

Released prescription content includes separate signingStatus and transmissionStatus. Unsigned cancelled drafts and unordered laboratory drafts stay hidden. Pharmacy acknowledgement and dispensing remain separate episode evidence. There are no patient-authorized schedule or renewal lifecycle commands: use the existing secure message route to request care-team action. Reads are safe to retry; message commands require an unchanged commandId and payload after uncertain responses.

Real care and actual partners require separate client and partner acceptance. Synthetic records and simulator movement do not establish certification or physical delivery.

Patient portal

Send a secure care-team message

POST/v1/patient/portal/orders/{orderId}/messagesClinical enablement required

Appends a patient-authored message to the care episode linked to a completed portal order.

FieldTypeDescription
commandId requiredstringPatient-generated idempotency key for this send attempt.
text requiredstringSecure message text, at most 8,000 characters.

The endpoint accepts only the portal's HTTP-only patient session. The server resolves the tenant, patient account, completed intake, chart, and care episode; request bodies cannot select those identities. Exact retries return the original message, while conflicting command reuse returns 409 care_message_conflict.

Clinical content

Do not place message text in URLs, analytics, lifecycle metadata, or logs. Clinician-authored messages and lab results appear in the portal only after explicit patient release.

Patient portal

Choose a preferred pharmacy

POST/v1/patient/portal/orders/{orderId}/pharmacy-selectionClinical enablement required

Records a patient-authored pharmacy preference for the care episode linked to a completed portal order.

FieldTypeDescription
commandId requiredstringPatient-generated idempotency key.
pharmacyKey requiredstringStable preference or pharmacy-directory identifier; it does not establish an eligibility match.
displayName requiredstringPatient-facing pharmacy name.
jurisdiction requiredstringTwo-letter jurisdiction matching the server-resolved review case.

The server resolves tenant, patient account, and care episode from the HTTP-only portal session. A patient preference is always stored as review_required; it cannot establish pharmacy eligibility or authorize transmission. The native portal may generate an opaque preference reference for a named pharmacy/location; the care team must separately verify an approved match. GET /v1/patient/portal/session includes up to 100 of this account's patient-authored choices in portal.clinical.pharmacyChoices (id, orderId, displayName, jurisdiction, eligibilityStatus, createdAt), only for linked orders when clinical gates permit. Patient API responses use no-store and no-referrer.

Partner API

Send a canonical event

POST/v1/integrations/{connectionId}/eventsCertified connections

Ingests a signed EzPharma canonical event, preserves the original request, and projects an eligible state transition.

FieldTypeDescription
schema_version requiredstringCurrently 1.0.
event_id requiredUUIDCanonical event identifier.
event_type requiredstringNamespaced event such as visit.completed or shipment.in_transit.
tenant_id requiredstringMust match the tenant resolved from the connection; it cannot grant access.
care_episode_id requiredstringTenant-owned care episode.
source requiredobjectProvisioned organization and connection IDs, system, and stable source event ID.
subject requiredobjectInternal patient ID and applicable resource IDs.
occurred_at requireddate-timeWhen the source says the action occurred.
received_at requireddate-timeAccepted for schema compatibility; EzPharma records authoritative receipt time.
data requiredobjectMinimum normalized fields needed for the event.
Accepted response
{
  "duplicate": false,
  "projected": true
}

Out-of-order and terminal-state conflicts are retained as event evidence even when they cannot update the current projection. Corrections are new events; history is never rewritten.

Partner API

Send a FHIR event

POST/v1/integrations/{connectionId}/fhir/eventsClinical connections

Accepts a signed FHIR R4 Appointment or Encounter, resolves explicit external links, and translates a supported status into a canonical event.

FHIR R4 Encounter
{
  "resourceType": "Encounter",
  "id": "encounter-demo-1042",
  "status": "finished",
  "meta": {
    "versionId": "1",
    "lastUpdated": "2026-10-22T14:30:00Z"
  },
  "subject": { "reference": "Patient/synthetic-patient-42" },
  "appointment": [{ "reference": "Appointment/demo-appointment-9" }],
  "period": { "end": "2026-10-22T14:30:00Z" }
}

Use Content-Type: application/fhir+json and the standard EzPharma HMAC headers. Clinical or approved technology connections may use this adapter. Missing or ambiguous links return 409 external_mapping_required.

Partner API

Send a pharmacy event

POST/v1/integrations/{connectionId}/pharmacy/eventsPharmacy & fulfillment

Accepts a signed prescription, dispense, or shipment envelope and maps approved partner statuses to independent canonical resources.

Shipment event
{
  "event_id": "partner-event-123",
  "event_type": "shipment.status",
  "occurred_at": "2026-10-22T18:10:00Z",
  "patient_id": "synthetic-patient-42",
  "prescription_id": "partner-rx-15",
  "order_id": "partner-order-81",
  "shipment_id": "partner-shipment-9",
  "status": "in_transit",
  "tracking": {
    "number": "SYNTHETIC-TRACKING-123",
    "carrier": "example-carrier",
    "url": "https://carrier.example/track/SYNTHETIC-TRACKING-123"
  }
}
event_typeRequired resourceExample statuses
prescription.statusprescription_idsigned, sent, received, clarification_required
dispense.statusorder_idunder_review, preparing, filled, partial
shipment.statusshipment_idcreated, picked_up, in_transit, delivered

The patient and domain resource must already have tenant-and-connection-scoped links. EzPharma never joins an event using a name, date of birth, medication name, or other probabilistic demographic match.

Client API

Retrieve an episode timeline

GET/v1/care-episodes/{careEpisodeId}/timelineClient API key

Returns the authenticated tenant's canonical events for one care episode.

Request
curl https://www.ezpharma.app/v1/care-episodes/episode_demo_123/timeline \
  -H "Authorization: Bearer $EZPHARMA_API_KEY"
Response
{
  "careEpisodeId": "episode_demo_123",
  "events": [
    {
      "eventType": "visit.completed",
      "occurredAt": "2026-10-22T14:30:00Z",
      "receivedAt": "2026-10-22T14:30:04Z",
      "sourceSystem": "example-ehr"
    }
  ]
}

A care episode outside the API key's tenant returns 404, the same as a missing episode. This prevents cross-tenant resource discovery.

Client API

Receive lifecycle webhooks

EzPharma can deliver approved lifecycle events to a tenant endpoint. Configure the endpoint in the client workspace and keep its secret server-side.

HeaderValuePurpose
X-EzPharma-TimestampUnix secondsReject stale deliveries and build the signature input.
X-EzPharma-Signaturesha256=<hex>HMAC-SHA256 of the timestamp, a literal period, and the exact body.
X-EzPharma-Event-IdOpaque event IDPersist and deduplicate before applying business logic.
Verify before parsing · Node.js
import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyEzPharmaWebhook(rawBody, headers, secret, now = Date.now()) {
  const timestamp = headers['x-ezpharma-timestamp']
  const signature = headers['x-ezpharma-signature']
  if (typeof timestamp !== 'string' || !/^\d{1,12}$/.test(timestamp) ||
      Math.abs(now / 1000 - Number(timestamp)) > 300 ||
      typeof signature !== 'string' || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false
  const expected = createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody).digest()
  const provided = Buffer.from(signature.slice(7), 'hex')
  return provided.length === expected.length && timingSafeEqual(provided, expected)
}

Timestamp validation alone does not prevent duplicate effects: persist and deduplicate the event ID in durable storage. Return a 2xx only after the event is durably accepted. Process asynchronously where possible. Failed deliveries are retried and can be inspected or retried by an authorized operator.

Minimum necessary payloads

Lifecycle events exclude clinical answers unless a later, explicitly reviewed contract requires them. Do not place secrets or patient data in response bodies or logs.

Production stub framework

CRM mapping lab

The CRM framework includes a disabled provider catalog and a synthetic-only lab. HubSpot, Salesforce, Dynamics/Dataverse, Zoho CRM and Pipedrive remain in scope; no actual vendor connection, OAuth app or patient export is enabled. Protocol fixture tests do not establish vendor-account acceptance.

Client owners/admins with current membership and recent MFA can use the lab from the client workspace in an authorized synthetic workspace. It maps bounded operational intake-event metadata to durable simulators, with immutable versions, explicit backfill, safe retry, conflict quarantine and protected recovery evidence. Source clinical/payment records and consent remain unchanged.

Native session routes use /api/portal/workspaces/{tenantId}/crm: GET connection health; POST /connections with a provider to create a simulator; GET /connections/{id} for fields/versions; POST its /preview, /mappings, /state, /backfill, /retry or /reconcile for authorized management. POST /run dispatches up to 20 simulated jobs. These routes do not accept client API bearer credentials or caller-selected operating modes.

Live/sandbox workspaces receive only the disabled catalog; simulator operations accept only synthetic tenants. Mapping inputs allow known source/destination fields, a unique external ID, bounded transforms and null handling. Errors are coded: 403 authority/synthetic boundary, 404 missing connection, 409 conflict/state/schema/reconciliation, 422 invalid input, 503 service failure. Responses are private and uncached. The server-only OAuth/credential kernel and association request prototypes are fixture-tested. Real provider brokers, HTTP callback routes, change feeds, runtime wiring and actual-account reconciliation remain unfinished; vendor delivery is deferred until demand.

Provider and pharmacy preparation

Partner readiness workbench

The client workspace links to a native provider/pharmacy contract simulator. Authorized owners/admins with recent MFA can publish immutable synthetic identifier mappings, queue versioned exercises, inspect independent projections and reconcile durable acknowledgements. Live/sandbox workspaces receive disabled readiness information; simulator operations require a synthetic tenant. No clinical signature, patient data export, actual prescription, dispensing or shipment occurs.

Download the onboarding packet and versioned contract schema. The packet includes supported operations, per-operation scenarios and resource targets, authority boundaries, transport options and outstanding partner inputs. Actual partner accounts, specifications, catalogs, privacy/clinical acceptance and certification remain separate requirements. Controlled substances are excluded.

Native session API: /api/portal/workspaces/{tenantId}/partner-readiness. GET the catalog; POST /connections with domain; GET /connections/{id} for safe evidence. POST its /mappings with commandId, mapping, version, /state with action, /jobs with commandId, operation, resource, scenario, /run with an empty object, or /recovery with jobId, action. Actions are activate/pause/revoke/reconnect and reconcile/retry/skip respectively. No caller token, mode or endpoint override is accepted.

Unknown/stale/out-of-order evidence is quarantined. Timeouts require a durable receipt reread, never blind retransmission. Repeated commands reuse immutable receipts; changed retries conflict. Projections stay separate by operation and package identity. Replies are no-store/no-referrer; errors are coded 403 authority, 404 missing record, 409 state/replay/reconciliation, 422 invalid fields and 503 unavailable. The signed simulator acknowledgement verifier is internal; no public partner callback route is added. Existing reviewed partner ingress and actual credentials are required for future adapters.

Resources

Security model

Healthcare integrations fail when authorization, identity matching, and operational states are treated as shortcuts. EzPharma makes those boundaries explicit.

Credentials resolve tenants

Installation keys, user memberships, partner connections, and client API keys resolve tenant ownership. Hostnames and request parameters do not.

Server-controlled operating boundaries

Clinical access requires both the runtime switch and a current tenant grant, in addition to individual clinical authority. Synthetic tenants can use simulator connections only; sandbox tenants cannot use production connections. A live tenant may retain explicitly configured sandbox connections for testing, but cannot use simulator evidence as live certification. Requests cannot grant capabilities or override connection classification. Incompatible connections fail closed, including on worker retries. Synthetic simulator connections require separate owner provisioning and cannot certify a real partner. Clinical traffic remains gated independently.

Synthetic external simulators

Simulator transports use server-selected connections for prescription, laboratory, pharmacy, communications, payment, fulfillment and carrier exercises. They contain no live endpoint or processor credential. Normal synthetic checkout can resolve inline through its local merchant adapter without moving money; real processor payments still use their signed provider evidence. Accepted simulator delivery never signs a prescription, approves care or certifies a partner.

The internal /simulators workspace requires a current synthetic operator membership, verified email, MFA and recent TOTP step-up. Its bounded evidence and patient-code inbox use no-store and no-referrer; unknown fields, tenant selectors and endpoint/credential overrides are rejected. Patient codes retain native expiry, one-time use and account scope. Simulator migration, provisioning, release and enabled acceptance are separate stages.

Authorized clinical users can submit an existing lab order through POST /api/clinical/review-cases/:caseId/lab-orders/:labOrderId/transmit with {commandId}. Normal case authority, MFA, runtime and tenant gates apply. Delivery evidence does not establish collection, a result or patient release.

Patient email preferences (DP-028)

The native patient session scopes GET /v1/patient/portal/communication-preferences and POST /v1/patient/portal/communication-preferences. POST accepts {commandId, optionalEmailEnabled}; it returns {preferences: {optionalEmailEnabled}}, 401 without patient authority, 422 for invalid input, or 409 for conflicting command reuse. No tenant or recipient selector authorizes access. Responses use no-store and no-referrer.

Optional mail requires explicit consent. Opt-out links use an opaque fragment consumed by the preference page and submitted as {token} to POST /v1/communications/unsubscribe. The response is always generic {ok: true} for handled requests and reveals no account. It cannot opt in or expose patient records. Signed provider complaints/bounces suppress delivery; retries recheck suppression. Transactional classification stays independent. Availability requires the matching release and migration 041; client/sender acceptance remains separate.

Explicit identifier links

External resources are linked within one tenant and one connection. Missing or ambiguous links stop processing.

Append-only evidence

Source and canonical events are retained. Corrections append; they do not erase history.

Minimum necessary data

Payment providers, URLs, callbacks, logs, and audit metadata receive only approved routing and operational fields.

Integration checklist

  • Keep API keys and HMAC secrets in a managed server-side secret store.
  • Preserve exact request bytes for signature creation and verification.
  • Use synthetic data in sandbox and automated tests.
  • Redact authorization headers, session tokens, raw answers, and patient information from logs.
  • Test duplicates, stale signatures, out-of-order events, missing mappings, outages, and recovery.
  • Enable live traffic only after the specific client and partner connection is certified.
Resources

Versioning & support

The current HTTP contract is under /v1. Additive response fields may appear without a version change, so clients should ignore fields they do not recognize.

ChangePolicy
Additive field or event typeMay be introduced within v1. New event types and status mappings still require connection-level approval before use.
Breaking request or response changeIntroduced under a new version with a documented migration and overlap period.
Capability enablementProvisioned and certified independently of the API version. Implementation does not imply enablement.
Need integration help?

Email hello@ezpharma.app with your organization, integration type, and intended sandbox workflow. Do not send credentials or patient information by email.

No documentation sections match that search.

Availability probes

GET /health/ready returns a coarse ready/unavailable status (200/503), with no patient, tenant, queue or dependency detail. It checks dependency/queue posture and freshness of the protected readiness job. It grants no application access.

Platform account contact

GET /api/platform/contact returns the configured platform account-support email only. Patient support remains tenant-resolved and the operational incident recipient stays private.

Patient support routing

Patient support and branded email Reply-To come from the authorized client’s support setting. Reserved support_email and client_name template bindings cannot be overridden by event variables. Missing client support blocks patient email processing; platform incident contacts are separate.