CareAtlas Integration Guide
Overview
This guide describes how to implement order submission using only the HaaS APIs, with no dependency on the provider-portal UI. It is intended for developers building headless or custom integrations (e.g. external provider portals, scripts, or server-to-server flows) that need to create orders in the same way as this app.
API contract: HTTP paths below match public/specs/CareAtlas-Unified-API-oas3-v0.1.json (CareAtlas Unified API). Combine them with the API base URL (e.g. staging https://qa.api.thecareatlas.com). To re-check that guide paths still exist in that spec, run python3 scripts/verify-careatlas-guide-paths.py from the repo root.
Goal: From a provider/tenant context, get to a successfully submitted order (prescription-based flow.
1. Getting started
1.1 High-level flow (registration → screening → prescription → order)
The app’s provider-portal flow is:
- Tenant & auth – Resolve tenant ID and partner app ID; ensure all requests use the same tenant and Bearer token.
- Patient – Ensure the patient exists and has an
accountIdand a valid address (required for order import). - Clinic – Ensure the clinic exists (or create/link) and you have clinic details (name, address, email, phone) required for
OrderImportRequest.clinic(ClinicInfo). - API Reference – Use the endpoint summary in §6 for paths, methods, and notes across Identity, Screening, Clinical, Catalog, and Commerce.
- Practitioner – Ensure the practitioner exists and you have
practitionerIdfor creating prescriptions and for order context as needed. Note: Practitioner registeration requires a valid NPI verifiable from NPPES NPI Registry. - Product / variant – Resolve
productVariantIdfrom the catalog if not already on the prescription. Product Variant is the actual medication that will be ordered. - Prescription – Create (or use an existing) prescription for that patient with
productIdandproductVariantId. - Order import – Build
OrderImportRequestand callPOST /commerce/v1/orders/import.
2. Access & Authentication
-
API base URL – Staging:
https://qa.api.thecareatlas.comProduction:https://api.thecareatlas.com -
Authentication – Once you receive your App Client credentials from the CareAtlas team, you can request auth token from AWS Cognito:
https://qa.app-auth.thecareatlas.comusing the credentials. You need a valid Bearer access token with the following scopes per API:- Identity (
/identity/v1/*on the API base URL) –api://haas.identity/haas.api.read,api://haas.identity/haas.api.write - Catalog (
/catalog/v1/*) –api://haas.catalog/haas.api.read,api://haas.catalog/haas.api.write - Clinical (
/clinical/v1/*) –api://haas.clinical/haas.api.read,api://haas.clinical/haas.api.write - Commerce (
/commerce/v1/*) –api://haas.commerce/haas.api.read,api://haas.commerce/haas.api.write - Screening (
/screening/v1/*) –api://haas.screening/haas.api.read,api://haas.screening/haas.api.write(as required by each operation in the spec)
- Identity (
-
Tenant context –
X-Tenant-Idheader is required in every request. You obtain it fromGET /identity/v1/tenants/me(see Step 1).
3. Patient registration
Register or resolve the patient in Identity so you have a patientId (UUID) for screening (POST /screening/v1/sessions), prescriptions, and orders.
Endpoint: POST /identity/v1/patients/resolve-by-email
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>, Content-Type: application/json
The API resolves an existing patient by email in the tenant or creates one when none exists. Use patient.id from the response as patientId in later steps.
Request body: PatientResolveRequest
| Field | Type | Notes |
|---|---|---|
email | string | Patient email (lookup key). |
firstName, lastName | string | Legal name. |
dateOfBirth | string (date) | ISO date, e.g. 1990-05-15. |
gender | string | Gender code or reference id per Identity / tenant. |
height, weight | object | Height and weight payload per API contract (shape may be numeric or structured). |
isOnGlp | boolean | Whether the patient is on GLP therapy. |
stateCode | string | US state or region code. |
address | Address | Full Address — addressLine1, city, state, zipCode, phone, etc. |
partnerPatientKey | string | Stable external key (often same as email). |
phoneNumber | string | Contact phone. |
stageId | string (uuid) | Funnel/stage id when your tenant requires it. |
referrerId | string | Referrer id when your tenant requires it. |
externalId | string | External/reference identifier from your system (often a generated UUID). |
Sample request (POST /identity/v1/patients/resolve-by-email):
{
"email": "jane.doe@example.com",
"firstName": "Jane",
"lastName": "Doe",
"dateOfBirth": "1990-05-15",
"gender": "female",
"height": {},
"weight": {},
"isOnGlp": false,
"stateCode": "CA",
"address": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
},
"partnerPatientKey": "jane.doe@example.com",
"phoneNumber": "5551234567",
"stageId": "01933a7e-5f2a-7000-8000-000000000020",
"referrerId": "01933a7e-5f2a-7000-8000-000000000021",
"externalId": "01933a7e-5f2a-7000-8000-000000000022"
}
Response: PatientResolveResponse — the nested patient object is a PatientResponse.
| Field | Type | Notes |
|---|---|---|
found | boolean | true if a patient already existed for this email. |
created | boolean | true if a new patient was created. |
patient | PatientResponse | Full patient record; use patient.id as patientId. |
Sample response:
{
"found": false,
"created": true,
"patient": {
"id": "01933a7e-7b2c-7456-8000-000000000010",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"accountId": "01933a7e-8c3d-7567-8000-000000000011",
"firstName": "Jane",
"lastName": "Doe",
"dateOfBirth": "1990-05-15",
"height": {},
"weight": {},
"isOnGlp": false,
"phone": "5551234567",
"gender": "female",
"state": "CA",
"stateName": "California",
"smsVerified": false,
"email": "jane.doe@example.com",
"emailVerified": true,
"phoneNumber": "5551234567",
"phoneNumberVerified": false,
"etag": "W/\"abc123\"",
"address": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
}
}
}
4. Start screening session
Before prescribing, many flows require the patient to complete a screening questionnaire tied to a medication (catalog product + variant). A typical headless sequence matches the provider portal: choose the product/variant and questionnaire, then start a screening session. Subsequent calls (answer questions, consent, submit) use the session id returned here.
Step 1: Ensure Patient exists
Endpoint: GET /identity/v1/patients/search
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>
Query parameters: Pass the patient’s id (and/or email, accountId, etc. per spec) to retrieve a paged list. When filtering by unique id, use the matching row from data.
Response: PagedResponseOfPatientResponse — each element in data is a PatientResponse.
For order import you need:
patient.id→customer.patientIdpatient.accountId→customer.accountId(required; resolve from profile/session if not on patient in your flow)patient.address→ must be a full Address object with at least:addressLine1,addressLine2,city,state,zipCode,phone
If the patient has no address or no accountId, create or update the patient via the appropriate Identity APIs before calling order import.
Sample response (GET /identity/v1/patients/search?id=01933a7e-7b2c-7456-8000-000000000010&limit=1): Example shape for data[0]:
{
"data": [
{
"id": "01933a7e-7b2c-7456-8000-000000000010",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"accountId": "01933a7e-8c3d-7567-8000-000000000011",
"firstName": "Jane",
"lastName": "Doe",
"dateOfBirth": "1990-05-15",
"height": {},
"weight": {},
"isOnGlp": false,
"phone": "5551234567",
"gender": "female",
"state": "CA",
"stateName": "California",
"smsVerified": false,
"email": "jane.doe@example.com",
"emailVerified": true,
"phoneNumber": "5551234567",
"phoneNumberVerified": false,
"etag": "W/\"abc123\"",
"address": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
}
}
],
"metadata": {
"limit": {},
"nextCursor": "",
"hasMore": false
}
}
Step 2: Select medication
A screening session is bound to a catalog product (productId), a product variant (productVariantId), and a questionnaire (questionnaireId). Resolve the product first (to pick a variant), then load active questionnaires and pick questionnaireId for your care path (use the bundle endpoint when you need full question/option payloads for the UI).
2.1 Get product and variant
Source: GET /catalog/v1/products/search (searchProducts) and optionally GET /catalog/v1/products/{id}/find (findProductById). Prefer search—many deployments only expose /products/search; /find can be absent on the gateway even when it appears in the spec.
Search products (recommended)
Endpoint: GET /catalog/v1/products/search
Headers: Authorization: Bearer <token> (catalog may use a separate client or token); optional If-None-Match
Query parameters (all optional; combine per your catalog)
| Name | Description |
|---|---|
variantId | Filter by product variant UUID (useful when you already know the variant). |
sku, variantSkus, name, brand | Product / variant text filters. |
catalogId, categoryId, pharmacy | Scope to catalog or category. |
excludeProvider, limit, next, sortBy, sortOrder | Exclusion, pagination, sorting. |
Request: No body.
Response: 200 — PagedResponseOfProductResponse. Use data[].id as productId. Choose productVariantId from data[].variants[] (match the variant you need).
Sample response (GET /catalog/v1/products/search?variantId=01933a7e-7b2c-7456-8000-000000000031&limit=5): Example shape for data[0]:
{
"data": [
{
"id": "01933a7e-7b2c-7456-8000-000000000030",
"sku": "COMP-GLP-001",
"name": "Sample GLP-1 medication",
"description": "Example catalog product",
"brand": "CareAtlas",
"isBundle": false,
"effectiveStartUtc": "2024-01-01T00:00:00Z",
"effectiveEndUtc": "2099-12-31T23:59:59Z",
"catalog": {
"id": "01933a7e-7b2c-7456-8000-000000000040",
"name": "Provider catalog",
"etag": "W/\"c1\"",
"createdAtUtc": "2024-01-01T00:00:00Z",
"createdBy": "system",
"updatedAtUtc": "2024-01-01T00:00:00Z",
"updatedBy": "system"
},
"attributes": [],
"variants": [
{
"id": "01933a7e-7b2c-7456-8000-000000000031",
"variantSku": "V0-30mg",
"name": "30 day supply",
"uomId": "01933a7e-7b2c-7456-8000-000000000050",
"attributes": [],
"etag": "W/\"v1\""
}
],
"categories": [],
"etag": "W/\"p1\"",
"createdAtUtc": "2024-01-01T00:00:00Z",
"createdBy": "system",
"updatedAtUtc": "2024-01-01T00:00:00Z",
"updatedBy": "system"
}
],
"metadata": {
"limit": {},
"nextCursor": "",
"hasMore": false
}
}
Get product by id (optional)
Endpoint: GET /catalog/v1/products/{id}/find — operationId: findProductById in the same OpenAPI file (path key /catalog/v1/products/{id}/find). Returns a single ProductResponse (same inner shape as data[] from search). If this route is not routed by your API gateway, use Search products with variantId or name / sku instead.
2.2 Fetch Available Questionnaires
The unified API exposes GET /screening/v1/questionnaires/active (not a separate paged /questionnaires list). Use it to discover questionnaireId. For rendering or validating the flow, load the full bundle with GET /screening/v1/questionnaires/{id}/bundle.
List active questionnaires
Endpoint: GET /screening/v1/questionnaires/active
Headers: Authorization: Bearer <token>; optional If-None-Match
Query parameters
| Name | Description |
|---|---|
name | Filter by questionnaire name / slug. |
version | Questionnaire version string. |
Request: No body.
Response: 200 — JSON array of QuestionnaireResponse. Use [].id as questionnaireId for POST /screening/v1/sessions.
Sample response (GET /screening/v1/questionnaires/active?name=branded):
[
{
"id": "01933a7e-7b2c-7456-8000-000000000060",
"name": "branded-medication-screening",
"version": "1",
"description": "Screening questionnaire for branded medication path",
"statusId": "01933a7e-7b2c-7456-8000-000000000061",
"treatmentId": "01933a7e-7b2c-7456-8000-000000000062",
"effectiveFromUtc": "2024-01-01T00:00:00Z",
"effectiveToUtc": "2099-12-31T23:59:59Z",
"questions": [],
"etag": "W/\"q1\"",
"createdAtUtc": "2024-01-01T00:00:00Z",
"createdBy": "system",
"updatedAtUtc": "2024-01-01T00:00:00Z",
"updatedBy": "system"
}
]
Get questionnaire bundle (optional)
Endpoint: GET /screening/v1/questionnaires/{id}/bundle
Headers: Authorization: Bearer <token>; optional If-None-Match
Path parameters
| Name | Description |
|---|---|
id | questionnaireId from the active list. |
Query parameters
| Name | Description |
|---|---|
expand | Optional; use per your client (e.g. related entities) when supported. |
Response: 200 — QuestionnaireBundleResponse (questions, answer options, and related fields per spec—use this to drive the screening UI after you create a session).
Note: Carry forward
productId,productVariantId(from 2.1), andquestionnaireId(from 2.2) into Step 3.
Step 3: Create screening session
Endpoint: POST /screening/v1/sessions
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>, Content-Type: application/json
Body: SessionStart — all fields are required:
| Field | Description |
|---|---|
channel | Channel identifier (e.g. web) per your integration contract. |
patientId | From Patient registration (§3) or confirmed in Step 1: Ensure Patient exists above. |
questionnaireId | From Step 2 above. |
practitionerId | Send any GUID. This will be deprecated in the future. |
productId | Catalog product UUID. |
productVariantId | Catalog product variant UUID. |
Response: 201 with SessionCreateResponse, including id (session id). Use that id to load the questionnaire bundle, post answers (POST /screening/v1/sessions/{id}/answer, POST /screening/v1/sessions/{id}/next, etc.), record consent if required, and POST /screening/v1/sessions/{id}/submit when the questionnaire is complete.
Notes:
409may indicate an existing session for the same patient/questionnaire — follow your product rules (resume vs. new session).- Confirm with CareAtlas that your client’s Bearer token includes Screening / CRUD write scopes for
/screening/v1/*routes (gateway requirements may differ from Identity or Clinical alone).
5. Prescriptions & Orders
Step 1: Obtain Tenant ID & Partner App ID
Endpoint: GET /identity/v1/tenants/me
Headers: Authorization: Bearer <access_token>
- Use
partnerApp.tenants[0].tenantId(or the tenant your app is configured for) asX-Tenant-Idon all subsequent requests. - Use
partnerApp.idassource.partnerAppIdin every Order Import request.
Sample response:
{
"partnerApp": {
"id": "01933a7e-5f2a-7000-8000-000000000001",
"appId": "provider-portal",
"name": "Provider Portal",
"partner": {
"id": "01933a7e-5f2a-7000-8000-000000000002",
"name": "Acme Health"
},
"tenants": [
{
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"name": "Acme Clinic",
"role": "provider"
}
],
"providers": []
}
}
Step 2: Ensure Patient exists
Endpoint: GET /identity/v1/patients/search
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>
Query parameters: Pass the patient’s id (and/or email, accountId, etc. per spec) to retrieve a paged list. When filtering by unique id, use the matching row from data.
Response: PagedResponseOfPatientResponse — each element in data is a PatientResponse.
For order import you need:
patient.id→customer.patientIdpatient.accountId→customer.accountId(required; resolve from profile/session if not on patient in your flow)patient.address→ must be a full Address object with at least:addressLine1,addressLine2,city,state,zipCode,phone
If the patient has no address or no accountId, create or update the patient via the appropriate Identity APIs before calling order import.
Sample response (GET /identity/v1/patients/search?id=01933a7e-7b2c-7456-8000-000000000010&limit=1): Example shape for data[0]:
{
"data": [
{
"id": "01933a7e-7b2c-7456-8000-000000000010",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"accountId": "01933a7e-8c3d-7567-8000-000000000011",
"firstName": "Jane",
"lastName": "Doe",
"dateOfBirth": "1990-05-15",
"height": {},
"weight": {},
"isOnGlp": false,
"phone": "5551234567",
"gender": "female",
"state": "CA",
"stateName": "California",
"smsVerified": false,
"email": "jane.doe@example.com",
"emailVerified": true,
"phoneNumber": "5551234567",
"phoneNumberVerified": false,
"etag": "W/\"abc123\"",
"address": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
}
}
],
"metadata": {
"limit": {},
"nextCursor": "",
"hasMore": false
}
}
Step 3: Ensure Clinic exists
Order import requires clinic (ClinicInfo) on every OrderImportRequest—clinic name, full address, email, and phone. Use the clinic APIs to find an existing clinic or create/link one, then map the result to ClinicInfo for the order.
Search clinics
Endpoint: GET /identity/v1/tenants/clinics/search
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>
Query parameters: name, id, npi, externalId, limit, after (optional). Use these to find a clinic by name, tenant id, NPI, or external id.
Response: Paged list of tenants (clinics). Each item is a TenantResponse-like object with id, name, partnerId, parentTenantId, role, and attributes. Use the tenant(s) to build or resolve ClinicInfo for the order (name, address, email, phone—from tenant attributes or your own data).
Sample response (GET /identity/v1/tenants/clinics/search): Returns a paged list; each element in data has id, name, partnerId, parentTenantId, role, etag, and related fields. Map to ClinicInfo using the tenant’s name and attribute/address data as required by your backend.
Onboard clinic
Endpoint: POST /identity/v1/tenants/clinics/onboard
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>, Content-Type: application/json
Body: OnboardClinicRequest — partnerId, parentTenantId, name, and attributes (e.g. name, npi, addressId, email, phone, description). See Object reference for schema details.
Response: 201 with TenantResponse. Use the returned tenant (and any address resolved from attributes.addressId) to build ClinicInfo for order import.
Sample request (POST /identity/v1/tenants/clinics/onboard):
{
"partnerId": "01933a7e-5f2a-7000-8000-000000000002",
"parentTenantId": "01933a7e-6a1b-7123-8000-000000000003",
"name": "Acme Clinic",
"attributes": {
"name": "Acme Clinic",
"npi": "1234567890",
"addressId": "01933a7e-ad00-7000-8000-000000000010",
"email": "contact@acmeclinic.example.com",
"phone": "5559876543",
"description": ""
}
}
Sample response (POST /identity/v1/tenants/clinics/onboard): Returns the created/linked tenant with id, name, partnerId, parentTenantId, role, etag, and timestamps. Use this tenant and your address data to populate OrderImportRequest.clinic (ClinicInfo).
What you need for OrderImportRequest:
clinic– ClinicInfo:clinicName,clinicAddressLine1,clinicAddressLine2,clinicCity,clinicState,clinicZip,clinicEmail,clinicPhoneNumber. Populate from the clinic/tenant returned by search or onboard, and from the resolved address when usingattributes.addressId.
Step 4: Ensure Practitioner exists
You need practitioner.id as practitionerId in CreatePrescriptionInternal (Step 5).
Fetching existing practitioner
If you already have a practitioner ID (e.g. from your system or a previous onboard response), call GET /identity/v1/practitioners/search with query id=<practitionerId> (and/or npi, email, etc. per spec).
Headers: Authorization: Bearer <token>; optional If-None-Match
Response: PagedResponseOfPractitionerResponse — use the matching PractitionerResponse from data (see PractitionerResponse).
Sample response (GET /identity/v1/practitioners/search?id=01933a7e-ae5f-7789-8000-000000000021&limit=1): Example shape for data[0]:
{
"data": [
{
"id": "01933a7e-ae5f-7789-8000-000000000021",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"accountId": "01933a7e-aa5e-7788-8000-000000000020",
"npi": "1234567890",
"firstName": "Maria",
"middleName": "",
"lastName": "Smith",
"suffix": "",
"displayName": "Maria Smith",
"providerTypeId": "provider-type-uuid",
"statusId": "active",
"inactivated": false,
"email": "maria.smith@clinic.example.com",
"emailVerified": true,
"phoneNumber": "5559876543",
"phoneNumberVerified": false,
"address": {
"addressLine1": "456 Clinic Way",
"addressLine2": "",
"city": "San Francisco",
"state": "CA",
"zipCode": "94103",
"phone": "5559876543"
},
"etag": "W/\"practitioner-etag\""
}
],
"metadata": {
"limit": {},
"nextCursor": "",
"hasMore": false
}
}
Creating new practitioner
If the practitioner does not exist, use POST /identity/v1/practitioners/onboard with PractitionerOnboardRequest (see Object reference for schema). The API returns an existing practitioner when one matches (e.g. by NPI/tenant), or creates one. Use the returned practitioner.id as practitionerId in prescription and order flows.
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>, Content-Type: application/json
Body: PractitionerOnboardRequest — see Object reference for schema.
Sample request (POST /identity/v1/practitioners/onboard):
{
"accountId": "01933a7e-aa5e-7788-8000-000000000020",
"npi": "1234567890",
"firstName": "Maria",
"middleName": "",
"lastName": "Smith",
"suffix": "",
"displayName": "Maria Smith",
"providerTypeId": "01933a7e-pt00-7000-8000-000000000001",
"statusId": "01933a7e-st00-7000-8000-000000000002",
"address": {
"addressLine1": "456 Clinic Way",
"addressLine2": "",
"city": "San Francisco",
"state": "CA",
"zipCode": "94103",
"phone": "5559876543"
},
"createIfMissing": true
}
Sample response (POST /identity/v1/practitioners/onboard):
{
"found": false,
"created": true,
"practitioner": {
"id": "01933a7e-ae5f-7789-8000-000000000021",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"accountId": "01933a7e-aa5e-7788-8000-000000000020",
"npi": "1234567890",
"firstName": "Maria",
"middleName": "",
"lastName": "Smith",
"suffix": "",
"displayName": "Maria Smith",
"providerTypeId": "01933a7e-pt00-7000-8000-000000000001",
"statusId": "01933a7e-st00-7000-8000-000000000002",
"inactivated": false,
"email": "",
"emailVerified": false,
"phoneNumber": "5559876543",
"phoneNumberVerified": false,
"address": {
"addressLine1": "456 Clinic Way",
"addressLine2": "",
"city": "San Francisco",
"state": "CA",
"zipCode": "94103",
"phone": "5559876543"
},
"etag": "W/\"practitioner-etag\""
}
}
Step 5: Get or create a prescription
Get existing: GET /clinical/v1/prescriptions/search with query id=<prescriptionId> (and/or patientId, patientEmail, etc. per spec).
Headers: X-Tenant-Id, Authorization
Response: PagedResponseOfPrescriptionResponse — use the matching PrescriptionResponse from data.
Create (provider flow): POST /clinical/v1/internal/prescriptions
Headers: X-Tenant-Id, Authorization, Content-Type: application/json
Body: CreatePrescriptionInternal; each item in medsPrescribed is MedPrescribedInternal. See Object reference for schemas.
Sample request (POST /clinical/v1/internal/prescriptions):
{
"screeningSessionId": "01933a7e-d182-7a23-8000-000000000030",
"medsPrescribed": [
{
"productId": "01933a7e-bf60-7801-8000-000000000022",
"productVariantId": "01933a7e-c071-7912-8000-000000000023",
"strength": "10mg",
"frequency": "once daily",
"route": "oral"
}
],
"pharmacyId": "0193ace2-7048-7692-9f57-91e59b94b40a",
"pharmacyNote": "Bill to patient, ship to patient",
"practitionerId": "01933a7e-ae5f-7789-8000-000000000021"
}
Sample response (GET /clinical/v1/prescriptions/search?id=01933a7e-9d4e-7678-8000-000000000020&limit=1): Example shape for data[0]:
{
"data": [
{
"id": "01933a7e-9d4e-7678-8000-000000000020",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"patientId": "01933a7e-7b2c-7456-8000-000000000010",
"practitionerId": "01933a7e-ae5f-7789-8000-000000000021",
"productId": "01933a7e-bf60-7801-8000-000000000022",
"productVariantId": "01933a7e-c071-7912-8000-000000000023",
"medicationName": "Example Medication 10mg",
"quantityAuthorized": { "value": 1 },
"approvedAtUtc": "2025-03-01T14:00:00Z",
"pharmacyId": "0193ace2-7048-7692-9f57-91e59b94b40a"
}
],
"metadata": {
"limit": {},
"nextCursor": "",
"hasMore": false
}
}
Sample response (POST /clinical/v1/internal/prescriptions): Returns an array of PrescriptionResponse (same fields as each item in the search data array above), one per item in medsPrescribed.
[
{
"id": "01933a7e-9d4e-7678-8000-000000000020",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"patientId": "01933a7e-7b2c-7456-8000-000000000010",
"practitionerId": "01933a7e-ae5f-7789-8000-000000000021",
"productId": "01933a7e-bf60-7801-8000-000000000022",
"productVariantId": "01933a7e-c071-7912-8000-000000000023",
"medicationName": "Example Medication 10mg",
"quantityAuthorized": { "value": 1 },
"approvedAtUtc": "2025-03-01T14:00:00Z",
"pharmacyId": "0193ace2-7048-7692-9f57-91e59b94b40a"
}
]
From the prescription you need for the order:
id→lines[].matchedPrescriptionIdpatientId→customer.patientIdproductId→lines[].productIdproductVariantId→lines[].productVariantId(or resolve from product in Step 6)quantityAuthorized→lines[].qty(e.g.quantityAuthorized.valueor default 1)
Step 6: Resolve Product Variant (Medication)
Endpoint: GET /catalog/v1/products/search (e.g. query variantId from the prescription, or name / sku) — see §4 Step 2.1. Optional: GET /catalog/v1/products/{id}/find when your gateway exposes it (same ProductResponse shape).
Headers: Authorization (catalog may use a different token in some environments); optional If-None-Match
Response: ProductResponse (from data[] or from /find) — use the product’s variants to get productVariantId (from prescription or e.g. variants[0].id).
The Import Order API also requires uomId (unit of measure) on each line. This app does not use UOM for any business logic; it only sends it because the API requires it. The app gets uomId from the variant when present, otherwise uses a fixed default. For headless implementations: send the variant’s uomId if available, or a known default UUID your backend accepts.
Sample response (single ProductResponse — e.g. one data row from search, or body of /find when available):
{
"id": "01933a7e-bf60-7801-8000-000000000022",
"name": "Example Medication",
"etag": "W/\"product-etag\"",
"variants": [
{
"id": "01933a7e-c071-7912-8000-000000000023",
"name": "10mg",
"variantSku": "MED-10MG",
"uomId": "e0b02579-ddbd-4d58-8ed1-8df498178e1d",
"product": {
"id": "01933a7e-bf60-7801-8000-000000000022",
"name": "Example Medication"
}
}
]
}
(Schema may vary; the important fields for order import are variants[].id (productVariantId) and variants[].uomId.)
Step 7: Build OrderImportRequest and call Import Order
Build the request body and call the import endpoint. Body schema: OrderImportRequest — see Object reference for full schema and nested types (CustomerInfo, ClinicInfo, LineInfo, Address, TotalsInfo, PaymentInfo, SourceInfo).
Mapping from prescription to order: Use the data from earlier steps to fill the payload:
prescription.id→lines[].matchedPrescriptionIdprescription.patientId→customer.patientIdprescription.productId→lines[].productIdprescription.productVariantId→lines[].productVariantId(or from product in Step 6)prescription.quantityAuthorized→lines[].qty(e.g..valueor default 1)patient.accountId→customer.accountId(from Step 2)patient.address→shippingAddressandbillingAddress(full Address objects)clinic→ from Step 3;source.partnerAppIdfrom Step 1
Numeric fields like qty, unitPrice, lineTotal, and totals may be strings in the API; see the sample request below and OrderImportRequest.
Endpoint: POST /commerce/v1/orders/import
Headers: Authorization: Bearer <token>, X-Tenant-Id: <tenantId>, Content-Type: application/json. Optional: Idempotency-Key: <key> to avoid duplicate orders on retries.
Sample request (POST /commerce/v1/orders/import):
{
"externalOrderId": "ORDER-1730123456789",
"customer": {
"patientId": "01933a7e-7b2c-7456-8000-000000000010",
"accountId": "01933a7e-8c3d-7567-8000-000000000011"
},
"clinic": {
"clinicName": "Acme Clinic",
"clinicAddressLine1": "456 Clinic Way",
"clinicAddressLine2": "",
"clinicCity": "San Francisco",
"clinicState": "CA",
"clinicZip": "94103",
"clinicEmail": "clinic@acme.example.com",
"clinicPhoneNumber": "5559876543"
},
"currencyISO": "USD",
"pricing": {
"priceBookVersion": "1.0",
"components": []
},
"lines": [
{
"externalLineId": "LINE-1730123456789",
"productId": "01933a7e-bf60-7801-8000-000000000022",
"productVariantId": "01933a7e-c071-7912-8000-000000000023",
"uomId": "e0b02579-ddbd-4d58-8ed1-8df498178e1d",
"qty": "1",
"unitPrice": "29.99",
"lineTotal": "29.99",
"priceComponents": [],
"requiresPrescription": true,
"matchedPrescriptionId": "01933a7e-9d4e-7678-8000-000000000020"
}
],
"promos": [],
"totals": {
"subtotal": "29.99",
"taxTotal": "0",
"shippingTotal": "0",
"grandTotal": "29.99"
},
"shippingAddress": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
},
"billingAddress": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
},
"payment": {},
"validatedAtUtc": "2025-03-04T12:00:00.000Z",
"source": {
"partnerAppId": "01933a7e-5f2a-7000-8000-000000000001",
"channel": "web",
"region": "US"
},
"notes": "Order for prescription 01933a7e-9d4e-7678-8000-000000000020"
}
Sample response (201 Created):
{
"id": "01933a7e-e293-7b34-8000-000000000040",
"orderNumber": "ORD-10042",
"tenantId": "01933a7e-6a1b-7123-8000-000000000003",
"patientId": "01933a7e-7b2c-7456-8000-000000000010",
"status": "Submitted",
"currencyISO": "USD",
"lines": [
{
"id": "01933a7e-f3a4-7c45-8000-000000000041",
"productId": "01933a7e-bf60-7801-8000-000000000022",
"productVariantId": "01933a7e-c071-7912-8000-000000000023",
"sku": "MED-10MG",
"uomId": "e0b02579-ddbd-4d58-8ed1-8df498178e1d",
"qty": { "value": 1 },
"unitPrice": { "value": 29.99 },
"lineTotal": { "value": 29.99 },
"prescriptionId": "01933a7e-9d4e-7678-8000-000000000020"
}
],
"subtotal": "29.99",
"taxTotal": "0",
"shippingTotal": "0",
"grandTotal": "29.99",
"shippingAddress": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
},
"billingAddress": {
"addressLine1": "123 Main St",
"addressLine2": "Apt 4",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"phone": "5551234567"
},
"etag": "W/\"order-etag\""
}
6. API Reference
| Operation | Method | Path | Notes |
|---|---|---|---|
| Get tenant + partner app | GET | /identity/v1/tenants/me | Returns partnerApp.id and tenants[].tenantId. |
| Search clinics | GET | /identity/v1/tenants/clinics/search | Query: name, id, npi, externalId, limit, after. Requires X-Tenant-Id. |
| Search practitioners | GET | /identity/v1/practitioners/search | Query: id, npi, email, limit, next, etc. |
| Onboard clinic | POST | /identity/v1/tenants/clinics/onboard | Body: OnboardClinicRequest. Creates or links a clinic tenant. |
| Search patients | GET | /identity/v1/patients/search | Query: id, email, accountId, limit, next, etc. Requires X-Tenant-Id. |
| Resolve patient by email | POST | /identity/v1/patients/resolve-by-email | Body: PatientResolveRequest; returns existing or newly created patient. |
| List active questionnaires | GET | /screening/v1/questionnaires/active | Query: name, version. Returns array of QuestionnaireResponse. |
| Get questionnaire bundle | GET | /screening/v1/questionnaires/{id}/bundle | Query: expand. Returns QuestionnaireBundleResponse (questions, options, etc.). |
| Create screening session | POST | /screening/v1/sessions | Body: SessionStart; headers: X-Tenant-Id. |
| Search prescriptions | GET | /clinical/v1/prescriptions/search | Query: id, patientId, patientEmail, limit, next, etc. Requires X-Tenant-Id. |
| Create prescriptions | POST | /clinical/v1/internal/prescriptions | Body: CreatePrescriptionInternal. |
| Search products | GET | /catalog/v1/products/search | Query: variantId, sku, name, brand, catalogId, limit, next, etc. Paged ProductResponse list. |
| Get product by id (optional) | GET | /catalog/v1/products/{id}/find | In unified OpenAPI as findProductById; some gateways omit this route—use Search products if 404. Single ProductResponse. |
| Import order | POST | /commerce/v1/orders/import | Body: OrderImportRequest; headers: X-Tenant-Id, optional Idempotency-Key. |
| Search orders | GET | /commerce/v1/orders/search | Query: patientId, orderNumber, limit, etc. |
| Get order by id | GET | /commerce/v1/orders/{id} | Full order details. |
All non-catalog endpoints require Authorization: Bearer <token> and X-Tenant-Id.
7. Object reference
Address
| Field | Type | Description |
|---|---|---|
| addressLine1 | string | Required. |
| addressLine2 | string | Required (can be empty string). |
| city | string | Required. |
| state | string | Required. |
| zipCode | string | Required. |
| phone | string | Required (e.g. 10 digits). |
ClinicInfo
Required by the API schema for OrderImportRequest.
| Field | Type | Description |
|---|---|---|
| clinicName | string | |
| clinicAddressLine1 | string | |
| clinicAddressLine2 | string | |
| clinicCity | string | |
| clinicState | string | |
| clinicZip | string | |
| clinicEmail | string | |
| clinicPhoneNumber | string |
If your backend accepts orders without clinic, you may still need to send a minimal object; confirm with the API or backend team.
CreatePrescriptionInternal
Request body for POST /clinical/v1/internal/prescriptions:
| Field | Type | Description |
|---|---|---|
| screeningSessionId | string (UUID) | Required. From a screening session. |
| medsPrescribed | MedPrescribedInternal[] | Required. Array of prescribed meds (see below). |
| pharmacyId | string (UUID) | Required. Pharmacy UUID. |
| pharmacyNote | string | Optional. e.g. "Bill to patient, ship to patient". |
| practitionerId | string (UUID) | Optional. Practitioner UUID (from Step 4). |
| practitionerFirstName | string | Optional. |
| practitionerLastName | string | Optional. |
| practitionerNpi | string | Optional. |
| practitionerPhone | string | Optional. |
| practitionerAddress | string | Optional. |
| practitionerCity | string | Optional. |
| practitionerState | string | Optional. |
| practitionerZip | string | Optional. |
| paymentAmount | number | Optional. |
| paymentLink | string | Optional. e.g. Stripe payment link. |
| paymentLinkSentAt | string (date-time) | Optional. |
CustomerInfo
| Field | Type | Description |
|---|---|---|
| patientId | string | Patient UUID. |
| accountId | string | Account UUID (from patient or profile/session). |
LineInfo
| Field | Type | Description |
|---|---|---|
| externalLineId | string | Unique line id (e.g. LINE-<ts> or same as externalOrderId for single line). |
| productId | string | Product UUID. |
| productVariantId | string | Product variant UUID. |
| uomId | string | Required by API. Unit of measure UUID. This app does not use it for logic; it sends the variant's uomId or a default. |
| qty | Record<string, any> or string | Quantity (API may expect string). |
| unitPrice | Record<string, any> or string | Unit price. |
| lineTotal | Record<string, any> or string | Line total. |
| priceComponents | array | e.g. []. |
| requiresPrescription | boolean | Typically true for prescription-based orders. |
| matchedPrescriptionId | string | Prescription UUID. |
MedPrescribedInternal
One entry in medsPrescribed (CreatePrescriptionInternal):
| Field | Type | Description |
|---|---|---|
| productId | string (UUID) | Required. Product UUID. |
| productVariantId | string (UUID) | Required. Product variant UUID. |
| strength | string | Required. e.g. "10mg". |
| frequency | string | Required. e.g. "once daily". |
| route | string | Required. e.g. "oral". |
OnboardClinicRequest
Request body for POST /identity/v1/tenants/clinics/onboard:
| Field | Type | Description |
|---|---|---|
| partnerId | string (UUID) | Partner UUID. |
| parentTenantId | string (UUID) | Parent tenant UUID. |
| name | string | Clinic/tenant name. |
| attributes | OnboardClinicAttributesRequest | Required. See below. |
attributes: name, npi, addressId (UUID), email, phone, description.
OrderImportRequest
Full request body for POST /commerce/v1/orders/import. The API schema marks these as required: externalOrderId, customer, clinic, currencyISO, pricing, lines, promos, totals, shippingAddress, billingAddress, payment, validatedAtUtc, source, notes.
| Field | Type | Description |
|---|---|---|
| externalOrderId | string | Unique external order id (e.g. ORDER-<ts> or Stripe payment intent id). |
| customer | CustomerInfo | Patient and account identifiers. |
| clinic | ClinicInfo | Required by schema. Clinic name, full address, email, phone. |
| currencyISO | string | e.g. "USD". |
| pricing | object / PricingInfo | e.g. { priceBookVersion: "1.0", components: [] }. |
| lines | LineInfo[] | Order lines; each can reference a prescription via matchedPrescriptionId. |
| promos | string[] | Promo codes; use [] if none. |
| totals | TotalsInfo | subtotal, taxTotal, shippingTotal, grandTotal. |
| shippingAddress | Address | Full shipping address. |
| billingAddress | Address | object | Full billing address. |
| payment | PaymentInfo | object | Payment id, external reference, status; can be empty for unpaid. |
| validatedAtUtc | string | ISO 8601 date-time. |
| source | SourceInfo | object | partnerAppId, channel, region. |
| notes | string | Free text. |
OrderResponse
Returned by POST /commerce/v1/orders/import:
| Field | Type | Description |
|---|---|---|
| id | string | Order UUID. |
| orderNumber | string | Human-readable order number. |
| status | string | Order status. |
| patientId | string | |
| lines | OrderLineResponse[] | Order lines. |
| subtotal, taxTotal, shippingTotal, grandTotal | various | |
| payment | object | |
| shippingAddress, billingAddress | object |
PatientResponse
Relevant for order import:
| Field | Type | Description |
|---|---|---|
| id | string | customer.patientId. |
| accountId | string | Often used as customer.accountId. |
| address | Address | object | Required for shipping/billing if not from payment. |
| string | Optional for billing. |
PaymentInfo
| Field | Type | Description |
|---|---|---|
| paymentId | string | Internal payment UUID. |
| externalReference | string | e.g. Stripe session id. |
| status | string | e.g. "paid", "pending". |
Extended payment payload (as used after Stripe) may include intentId, provider (e.g. "Stripe").
PractitionerOnboardRequest
Request body for POST /identity/v1/practitioners/onboard (create or find practitioner by NPI):
| Field | Type | Description |
|---|---|---|
| accountId | string (UUID) | Account UUID for the practitioner (from your identity/tenant context). |
| npi | string | National Provider Identifier (10 digits). |
| firstName | string | Required. |
| middleName | string | Required; use "" if none. |
| lastName | string | Required. |
| suffix | string | Required; use "" if none. |
| displayName | string | e.g. "Dr. Maria Smith". |
| providerTypeId | string (UUID) | Provider type from tenant config (e.g. physician, NP). |
| statusId | string (UUID) | Status from tenant config (e.g. active). |
| address | Address | Full address (addressLine1, addressLine2, city, state, zipCode, phone). |
| createIfMissing | boolean | Optional; default true. If true, creates the practitioner when not found. |
PractitionerResponse
Returned by GET /identity/v1/practitioners/search (each item in data) and in POST .../practitioners/onboard response:
| Field | Type | Description |
|---|---|---|
| id | string | Practitioner UUID; use as practitionerId in prescriptions. |
| tenantId | string | Tenant UUID. |
| accountId | string | Account UUID. |
| npi | string | National Provider Identifier. |
| firstName | string | |
| middleName | string | |
| lastName | string | |
| suffix | string | |
| displayName | string | |
| providerTypeId | string | |
| statusId | string | |
| inactivated | boolean | |
| string | ||
| emailVerified | boolean | |
| phoneNumber | string | |
| phoneNumberVerified | boolean | |
| address | Address | object | Full address. |
| etag | string |
PrescriptionResponse
Relevant fields for building the order:
| Field | Type | Description |
|---|---|---|
| id | string | Used as matchedPrescriptionId. |
| patientId | string | Used as customer.patientId. |
| productId | string | Used as lines[].productId. |
| productVariantId | string | Used as lines[].productVariantId (or resolve from product). |
| quantityAuthorized | object | Used for lines[].qty (e.g. .value or default 1). |
SourceInfo
| Field | Type | Description |
|---|---|---|
| partnerAppId | string | From GET /identity/v1/tenants/me → partnerApp.id. |
| channel | string | e.g. "web". |
| region | string | e.g. "US". |
TotalsInfo
| Field | Type | Description |
|---|---|---|
| subtotal | Record<string, any> or string | Subtotal amount. |
| taxTotal | Record<string, any> or string | Tax total. |
| shippingTotal | Record<string, any> or string | Shipping total. |
| grandTotal | Record<string, any> or string | Grand total. |
In this app, totals are often sent as strings (e.g. "0", "12.99") for decimal precision.
TenantResponse
Returned by POST /identity/v1/tenants/clinics/onboard and in clinic search results:
| Field | Type | Description |
|---|---|---|
| id | string | Tenant (clinic) UUID. |
| partnerId | string | Partner UUID. |
| parentTenantId | string | Parent tenant UUID. |
| name | string | Clinic/tenant name. |
| role | TenantRoleResponse | Tenant role. |
| etag | string | For conditional updates. |
| createdAtUtc, updatedAtUtc | string | Timestamps. |
| atributes | array | Tenant role attribute values (e.g. address, contact details). |
Use with address/attribute data to build ClinicInfo for order import.
8. Troubleshooting
- 401 Unauthorized – Check Cognito token and scope (
api://haas.commerce/haas.api.writefor import). - 403 Forbidden – Verify X-Tenant-Id and that the token is allowed for that tenant.
- 400 Bad Request – Validate OrderImportRequest: required fields (including
clinic), address fields (addressLine2 must be present, can be""), and numeric/string formats for qty, unitPrice, lineTotal, totals. - Missing accountId – Ensure patient has
accountIdor resolve it via your identity/profile API before calling order import. - Patient does not have address – Order import requires full shipping and billing addresses; create or update the patient’s address via Identity APIs first.
- Duplicate orders – Use Idempotency-Key (e.g. prescription id + payment id) on
POST /commerce/v1/orders/importwhen retrying or handling webhooks.
For payload examples and mapping from prescription to order, see Step 7 and the sample request there.