Download .md

    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:

    1. Tenant & auth – Resolve tenant ID and partner app ID; ensure all requests use the same tenant and Bearer token.
    2. Patient – Ensure the patient exists and has an accountId and a valid address (required for order import).
    3. Clinic – Ensure the clinic exists (or create/link) and you have clinic details (name, address, email, phone) required for OrderImportRequest.clinic (ClinicInfo).
    4. API Reference – Use the endpoint summary in §6 for paths, methods, and notes across Identity, Screening, Clinical, Catalog, and Commerce.
    5. Practitioner – Ensure the practitioner exists and you have practitionerId for creating prescriptions and for order context as needed. Note: Practitioner registeration requires a valid NPI verifiable from NPPES NPI Registry.
    6. Product / variant – Resolve productVariantId from the catalog if not already on the prescription. Product Variant is the actual medication that will be ordered.
    7. Prescription – Create (or use an existing) prescription for that patient with productId and productVariantId.
    8. Order import – Build OrderImportRequest and call POST /commerce/v1/orders/import.

    2. Access & Authentication

    • API base URLStaging: https://qa.api.thecareatlas.com Production: 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.com using 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)
    • Tenant contextX-Tenant-Id header is required in every request. You obtain it from GET /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

    FieldTypeNotes
    emailstringPatient email (lookup key).
    firstName, lastNamestringLegal name.
    dateOfBirthstring (date)ISO date, e.g. 1990-05-15.
    genderstringGender code or reference id per Identity / tenant.
    height, weightobjectHeight and weight payload per API contract (shape may be numeric or structured).
    isOnGlpbooleanWhether the patient is on GLP therapy.
    stateCodestringUS state or region code.
    addressAddressFull AddressaddressLine1, city, state, zipCode, phone, etc.
    partnerPatientKeystringStable external key (often same as email).
    phoneNumberstringContact phone.
    stageIdstring (uuid)Funnel/stage id when your tenant requires it.
    referrerIdstringReferrer id when your tenant requires it.
    externalIdstringExternal/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.

    FieldTypeNotes
    foundbooleantrue if a patient already existed for this email.
    createdbooleantrue if a new patient was created.
    patientPatientResponseFull 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.idcustomer.patientId
    • patient.accountIdcustomer.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)

    NameDescription
    variantIdFilter by product variant UUID (useful when you already know the variant).
    sku, variantSkus, name, brandProduct / variant text filters.
    catalogId, categoryId, pharmacyScope to catalog or category.
    excludeProvider, limit, next, sortBy, sortOrderExclusion, pagination, sorting.

    Request: No body.

    Response: 200PagedResponseOfProductResponse. 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}/findoperationId: 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

    NameDescription
    nameFilter by questionnaire name / slug.
    versionQuestionnaire 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

    NameDescription
    idquestionnaireId from the active list.

    Query parameters

    NameDescription
    expandOptional; use per your client (e.g. related entities) when supported.

    Response: 200QuestionnaireBundleResponse (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), and questionnaireId (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:

    FieldDescription
    channelChannel identifier (e.g. web) per your integration contract.
    patientIdFrom Patient registration (§3) or confirmed in Step 1: Ensure Patient exists above.
    questionnaireIdFrom Step 2 above.
    practitionerIdSend any GUID. This will be deprecated in the future.
    productIdCatalog product UUID.
    productVariantIdCatalog 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:

    • 409 may 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) as X-Tenant-Id on all subsequent requests.
    • Use partnerApp.id as source.partnerAppId in 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.idcustomer.patientId
    • patient.accountIdcustomer.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: OnboardClinicRequestpartnerId, 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:

    • clinicClinicInfo: clinicName, clinicAddressLine1, clinicAddressLine2, clinicCity, clinicState, clinicZip, clinicEmail, clinicPhoneNumber. Populate from the clinic/tenant returned by search or onboard, and from the resolved address when using attributes.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:

    • idlines[].matchedPrescriptionId
    • patientIdcustomer.patientId
    • productIdlines[].productId
    • productVariantIdlines[].productVariantId (or resolve from product in Step 6)
    • quantityAuthorizedlines[].qty (e.g. quantityAuthorized.value or 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.idlines[].matchedPrescriptionId
    • prescription.patientIdcustomer.patientId
    • prescription.productIdlines[].productId
    • prescription.productVariantIdlines[].productVariantId (or from product in Step 6)
    • prescription.quantityAuthorizedlines[].qty (e.g. .value or default 1)
    • patient.accountIdcustomer.accountId (from Step 2)
    • patient.addressshippingAddress and billingAddress (full Address objects)
    • clinic → from Step 3; source.partnerAppId from 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

    OperationMethodPathNotes
    Get tenant + partner appGET/identity/v1/tenants/meReturns partnerApp.id and tenants[].tenantId.
    Search clinicsGET/identity/v1/tenants/clinics/searchQuery: name, id, npi, externalId, limit, after. Requires X-Tenant-Id.
    Search practitionersGET/identity/v1/practitioners/searchQuery: id, npi, email, limit, next, etc.
    Onboard clinicPOST/identity/v1/tenants/clinics/onboardBody: OnboardClinicRequest. Creates or links a clinic tenant.
    Search patientsGET/identity/v1/patients/searchQuery: id, email, accountId, limit, next, etc. Requires X-Tenant-Id.
    Resolve patient by emailPOST/identity/v1/patients/resolve-by-emailBody: PatientResolveRequest; returns existing or newly created patient.
    List active questionnairesGET/screening/v1/questionnaires/activeQuery: name, version. Returns array of QuestionnaireResponse.
    Get questionnaire bundleGET/screening/v1/questionnaires/{id}/bundleQuery: expand. Returns QuestionnaireBundleResponse (questions, options, etc.).
    Create screening sessionPOST/screening/v1/sessionsBody: SessionStart; headers: X-Tenant-Id.
    Search prescriptionsGET/clinical/v1/prescriptions/searchQuery: id, patientId, patientEmail, limit, next, etc. Requires X-Tenant-Id.
    Create prescriptionsPOST/clinical/v1/internal/prescriptionsBody: CreatePrescriptionInternal.
    Search productsGET/catalog/v1/products/searchQuery: variantId, sku, name, brand, catalogId, limit, next, etc. Paged ProductResponse list.
    Get product by id (optional)GET/catalog/v1/products/{id}/findIn unified OpenAPI as findProductById; some gateways omit this route—use Search products if 404. Single ProductResponse.
    Import orderPOST/commerce/v1/orders/importBody: OrderImportRequest; headers: X-Tenant-Id, optional Idempotency-Key.
    Search ordersGET/commerce/v1/orders/searchQuery: patientId, orderNumber, limit, etc.
    Get order by idGET/commerce/v1/orders/{id}Full order details.

    All non-catalog endpoints require Authorization: Bearer <token> and X-Tenant-Id.


    7. Object reference

    Address

    FieldTypeDescription
    addressLine1stringRequired.
    addressLine2stringRequired (can be empty string).
    citystringRequired.
    statestringRequired.
    zipCodestringRequired.
    phonestringRequired (e.g. 10 digits).

    ClinicInfo

    Required by the API schema for OrderImportRequest.

    FieldTypeDescription
    clinicNamestring
    clinicAddressLine1string
    clinicAddressLine2string
    clinicCitystring
    clinicStatestring
    clinicZipstring
    clinicEmailstring
    clinicPhoneNumberstring

    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:

    FieldTypeDescription
    screeningSessionIdstring (UUID)Required. From a screening session.
    medsPrescribedMedPrescribedInternal[]Required. Array of prescribed meds (see below).
    pharmacyIdstring (UUID)Required. Pharmacy UUID.
    pharmacyNotestringOptional. e.g. "Bill to patient, ship to patient".
    practitionerIdstring (UUID)Optional. Practitioner UUID (from Step 4).
    practitionerFirstNamestringOptional.
    practitionerLastNamestringOptional.
    practitionerNpistringOptional.
    practitionerPhonestringOptional.
    practitionerAddressstringOptional.
    practitionerCitystringOptional.
    practitionerStatestringOptional.
    practitionerZipstringOptional.
    paymentAmountnumberOptional.
    paymentLinkstringOptional. e.g. Stripe payment link.
    paymentLinkSentAtstring (date-time)Optional.

    CustomerInfo

    FieldTypeDescription
    patientIdstringPatient UUID.
    accountIdstringAccount UUID (from patient or profile/session).

    LineInfo

    FieldTypeDescription
    externalLineIdstringUnique line id (e.g. LINE-<ts> or same as externalOrderId for single line).
    productIdstringProduct UUID.
    productVariantIdstringProduct variant UUID.
    uomIdstringRequired by API. Unit of measure UUID. This app does not use it for logic; it sends the variant's uomId or a default.
    qtyRecord<string, any> or stringQuantity (API may expect string).
    unitPriceRecord<string, any> or stringUnit price.
    lineTotalRecord<string, any> or stringLine total.
    priceComponentsarraye.g. [].
    requiresPrescriptionbooleanTypically true for prescription-based orders.
    matchedPrescriptionIdstringPrescription UUID.

    MedPrescribedInternal

    One entry in medsPrescribed (CreatePrescriptionInternal):

    FieldTypeDescription
    productIdstring (UUID)Required. Product UUID.
    productVariantIdstring (UUID)Required. Product variant UUID.
    strengthstringRequired. e.g. "10mg".
    frequencystringRequired. e.g. "once daily".
    routestringRequired. e.g. "oral".

    OnboardClinicRequest

    Request body for POST /identity/v1/tenants/clinics/onboard:

    FieldTypeDescription
    partnerIdstring (UUID)Partner UUID.
    parentTenantIdstring (UUID)Parent tenant UUID.
    namestringClinic/tenant name.
    attributesOnboardClinicAttributesRequestRequired. 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.

    FieldTypeDescription
    externalOrderIdstringUnique external order id (e.g. ORDER-<ts> or Stripe payment intent id).
    customerCustomerInfoPatient and account identifiers.
    clinicClinicInfoRequired by schema. Clinic name, full address, email, phone.
    currencyISOstringe.g. "USD".
    pricingobject / PricingInfoe.g. { priceBookVersion: "1.0", components: [] }.
    linesLineInfo[]Order lines; each can reference a prescription via matchedPrescriptionId.
    promosstring[]Promo codes; use [] if none.
    totalsTotalsInfosubtotal, taxTotal, shippingTotal, grandTotal.
    shippingAddressAddressFull shipping address.
    billingAddressAddress | objectFull billing address.
    paymentPaymentInfo | objectPayment id, external reference, status; can be empty for unpaid.
    validatedAtUtcstringISO 8601 date-time.
    sourceSourceInfo | objectpartnerAppId, channel, region.
    notesstringFree text.

    OrderResponse

    Returned by POST /commerce/v1/orders/import:

    FieldTypeDescription
    idstringOrder UUID.
    orderNumberstringHuman-readable order number.
    statusstringOrder status.
    patientIdstring
    linesOrderLineResponse[]Order lines.
    subtotal, taxTotal, shippingTotal, grandTotalvarious
    paymentobject
    shippingAddress, billingAddressobject

    PatientResponse

    Relevant for order import:

    FieldTypeDescription
    idstringcustomer.patientId.
    accountIdstringOften used as customer.accountId.
    addressAddress | objectRequired for shipping/billing if not from payment.
    emailstringOptional for billing.

    PaymentInfo

    FieldTypeDescription
    paymentIdstringInternal payment UUID.
    externalReferencestringe.g. Stripe session id.
    statusstringe.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):

    FieldTypeDescription
    accountIdstring (UUID)Account UUID for the practitioner (from your identity/tenant context).
    npistringNational Provider Identifier (10 digits).
    firstNamestringRequired.
    middleNamestringRequired; use "" if none.
    lastNamestringRequired.
    suffixstringRequired; use "" if none.
    displayNamestringe.g. "Dr. Maria Smith".
    providerTypeIdstring (UUID)Provider type from tenant config (e.g. physician, NP).
    statusIdstring (UUID)Status from tenant config (e.g. active).
    addressAddressFull address (addressLine1, addressLine2, city, state, zipCode, phone).
    createIfMissingbooleanOptional; 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:

    FieldTypeDescription
    idstringPractitioner UUID; use as practitionerId in prescriptions.
    tenantIdstringTenant UUID.
    accountIdstringAccount UUID.
    npistringNational Provider Identifier.
    firstNamestring
    middleNamestring
    lastNamestring
    suffixstring
    displayNamestring
    providerTypeIdstring
    statusIdstring
    inactivatedboolean
    emailstring
    emailVerifiedboolean
    phoneNumberstring
    phoneNumberVerifiedboolean
    addressAddress | objectFull address.
    etagstring

    PrescriptionResponse

    Relevant fields for building the order:

    FieldTypeDescription
    idstringUsed as matchedPrescriptionId.
    patientIdstringUsed as customer.patientId.
    productIdstringUsed as lines[].productId.
    productVariantIdstringUsed as lines[].productVariantId (or resolve from product).
    quantityAuthorizedobjectUsed for lines[].qty (e.g. .value or default 1).

    SourceInfo

    FieldTypeDescription
    partnerAppIdstringFrom GET /identity/v1/tenants/mepartnerApp.id.
    channelstringe.g. "web".
    regionstringe.g. "US".

    TotalsInfo

    FieldTypeDescription
    subtotalRecord<string, any> or stringSubtotal amount.
    taxTotalRecord<string, any> or stringTax total.
    shippingTotalRecord<string, any> or stringShipping total.
    grandTotalRecord<string, any> or stringGrand 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:

    FieldTypeDescription
    idstringTenant (clinic) UUID.
    partnerIdstringPartner UUID.
    parentTenantIdstringParent tenant UUID.
    namestringClinic/tenant name.
    roleTenantRoleResponseTenant role.
    etagstringFor conditional updates.
    createdAtUtc, updatedAtUtcstringTimestamps.
    atributesarrayTenant 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.write for 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 accountId or 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/import when retrying or handling webhooks.

    For payload examples and mapping from prescription to order, see Step 7 and the sample request there.