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](../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 context**X-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.id**customer.patientId
    • **patient.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 Search catalogs

    Source: **GET /catalog/v1/catalogs/search** (searchCatalogs). Lists catalogs available to the tenant (each catalog groups products). Use this when you need **catalogId** to scope Search products (§2.2) or to show a catalog picker in the UI.

    Endpoint: GET /catalog/v1/catalogs/search
    Headers: **Authorization: Bearer <token>**; **X-Tenant-Id** (required — current clinic/tenant UUID); optional **If-None-Match**

    Query parameters (all optional unless noted)

    NameDescription
    idFilter by catalog UUID.
    nameFilter by catalog name (matching behavior depends on deployment).
    includeCategoriesWhen supported, include category rows on each catalog (string flag per OpenAPI — confirm with your client).
    limit, nextPagination; **next** is the cursor from **metadata** on the previous page.
    sortBy, sortOrderSorting.

    (Query parameters are optional; omit **If-None-Match** on first call.)

    Sample response (200)**PagedResponseOfCatalogSearchResponse**:

    { "data": [ { "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", "categories": [ { "id": "01933a7e-8c3d-8567-8000-000000000041", "name": "Weight management", "etag": "W/\"cat1\"" } ] } ], "metadata": { "limit": {}, "nextCursor": "", "hasMore": false } }

    Use **data[].id** as **catalogId** when calling **GET /catalog/v1/products/search**. If **includeCategories** is omitted or unsupported, **categories** may be an empty array.

    Errors: **401**, **403**, **404** (per spec). **304** when **If-None-Match** matches.

    Note: Skip this step if **catalogId** is already known (config, or from **product.catalog.id** on a product returned by search).


    2.2 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. Catalog calls require **X-Tenant-Id** on the request headers.

    Search products (recommended)

    Endpoint: GET /catalog/v1/products/search
    Headers: Authorization: Bearer <token> (catalog may use a separate client or token); **X-Tenant-Id** (tenant UUID); 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: **200** — **PagedResponseOfProductResponse**. Use **data[].id** as **productId**. Choose **productVariantId** from **data[].variants[]** (match the variant you need).

    Sample request

    GET /catalog/v1/products/search?variantId=01933a7e-7b2c-7456-8000-000000000031&limit=5&catalogId=01933a7e-7b2c-7456-8000-000000000040 HTTP/1.1 Authorization: Bearer <access_token> X-Tenant-Id: 01933a7e-6a1b-7123-8000-000000000003

    (The catalogId query parameter is optional; use it to scope to a catalog from §2.1.)

    Sample response (200) — full page body (example **GET /catalog/v1/products/search?variantId=01933a7e-7b2c-7456-8000-000000000031&limit=5**):

    { "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). Headers: **Authorization; **X-Tenant-Id** (required). 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.

    Request: No body.

    Sample request

    GET /catalog/v1/products/01933a7e-7b2c-7456-8000-000000000030/find HTTP/1.1 Authorization: Bearer <access_token> X-Tenant-Id: 01933a7e-6a1b-7123-8000-000000000003

    Sample response (200) — one **ProductResponse** (not wrapped in **data**); same fields as a single element of **data[]** in Search products:

    { "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" }

    2.3 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
    id**questionnaireId** from the active list.

    Query parameters

    NameDescription
    expandOptional; 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.2), and **questionnaireId (from 2.3) 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 value as {id} in Step 4 to post answers and complete the session.

    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).

    Step 4: Submit questionnaire answers and complete the session

    After POST /screening/v1/sessions returns a session id, drive the questionnaire with the Screening API: optionally fetch batches of questions, send answers, record consent when your flow requires it, then submit the session so downstream steps (e.g. internal prescriptions with screeningSessionId) can proceed.

    Headers (all calls below): Authorization: Bearer <token>, X-Tenant-Id: <tenantId>, and Content-Type: application/json on POST bodies.

    4.1 Get the next question batch (optional)

    Endpoint: GET /screening/v1/sessions/{id}/next
    Path: {id} = session id from Step 3.

    Query parameters: optional limit (string per client), optional If-None-Match.

    Response: **200** — **QuestionnaireNextResponse**:

    FieldNotes
    sessionIdSame session id.
    questionsArray of questions for this batch (shape per QuestionnaireNextQuestionResponse).
    totalRemainingServer hint for remaining work (shape per deployment).
    isLastBatchWhen true, no further **/next** batches are expected for this pass.

    Use this in paged UIs: call **/next**, render questions, collect answers, then POST **/answer** (§4.2). Repeat until isLastBatch is true or you have collected every answer your bundle requires.

    Alternative: If you already loaded **GET /screening/v1/questionnaires/{id}/bundle** (§2.3), you can build the full form without calling **/next**, then send one or more **/answer** requests with every linkId the bundle defines.

    4.2 Post answers

    Endpoint: POST /screening/v1/sessions/{id}/answer
    Body: **AnswerBundleRequest**{ "answers": [ ... ] }

    Each **AnswerItem** typically includes:

    FieldDescription
    linkIdQuestion link id from the questionnaire bundle or **/next** payload (stable key for the question).
    answerValue: string, number, boolean, or structured object per question type (single choice, multi-select, text, etc.).
    evidenceRefOptional. For file-upload questions, reference returned after uploading the file to your storage flow (per tenant/API contract).

    Sample request (POST /screening/v1/sessions/{id}/answer):

    { "answers": [ { "linkId": "q-height", "answer": 70 }, { "linkId": "q-current-meds", "answer": "None" }, { "linkId": "q-conditions", "answer": ["condition-a", "condition-b"] } ] }

    Response: **200** — **AnswerBundleResponse** (per spec). You may call **/answer** multiple times (e.g. per batch) before submitting.

    Some tenants require consent before final submit. If your bundle or product rules say consent is needed:

    Endpoint: POST /screening/v1/sessions/{id}/consent
    Body: **CreateConsentRequest** — e.g. scope, termsVersion, grantedAt / expiresAt (ISO date-times) per OpenAPI.

    Skip this step if your integration does not use session-level consent.

    4.4 Submit the session

    When all required answers are saved, complete the session:

    Endpoint: POST /screening/v1/sessions/{id}/submit
    Body: **SessionSubmitRequest**

    FieldDescription
    validateOnlyfalse to finalize; true to validate without completing (if supported).

    Sample request:

    { "validateOnly": false }

    Response: **200** — **SessionSubmitResponse** — may include **status**, **completedAt**, and **recommendedProductVariants** (used by clinical flows to suggest variants). Use the same sessionId as **screeningSessionId** when creating internal prescriptions (Step 5).

    Operational notes

    • Order of operations (typical): create session → (**/next** + **/answer)* → optional **/consent**/submit**. Exact branching depends on questionnaire configuration.
    • Idempotency: Re-posting overlapping answers may be rejected or merged per backend rules; prefer one bundle per batch or a single final bundle before submit.
    • Errors: **400** validation (missing required answers), **404** unknown session id — confirm session id and tenant.

    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.id**customer.patientId
    • **patient.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: 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:

    • **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 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:

    • **id**lines[].matchedPrescriptionId
    • **patientId**customer.patientId
    • **productId**lines[].productId
    • **productVariantId**lines[].productVariantId (or resolve from product in Step 6)
    • **quantityAuthorized**lines[].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.2. Optional: **GET /catalog/v1/products/{id}/find** when your gateway exposes it (same **ProductResponse** shape; send **X-Tenant-Id**).

    Headers: Authorization (catalog may use a different token in some environments); **X-Tenant-Id**; 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[].matchedPrescriptionId
    • **prescription.patientId**customer.patientId
    • **prescription.productId**lines[].productId
    • **prescription.productVariantId**lines[].productVariantId (or from product in Step 6)
    • **prescription.quantityAuthorized**lines[].qty (e.g. .value or default 1)
    • **patient.accountId**customer.accountId (from Step 2)
    • **patient.address**shippingAddress 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.
    Next questionnaire batchGET/screening/v1/sessions/{id}/nextPath: session id. Optional query limit. Returns QuestionnaireNextResponse.
    Submit session answersPOST/screening/v1/sessions/{id}/answerBody: AnswerBundleRequest (answers: AnswerItem[]). Headers: X-Tenant-Id.
    Record session consentPOST/screening/v1/sessions/{id}/consentBody: CreateConsentRequest. When tenant flow requires consent before submit.
    Submit screening sessionPOST/screening/v1/sessions/{id}/submitBody: SessionSubmitRequest (validateOnly). Completes session; returns SessionSubmitResponse.
    Search prescriptionsGET/clinical/v1/prescriptions/searchQuery: id, patientId, patientEmail, limit, next, etc. Requires X-Tenant-Id.
    Create prescriptionsPOST/clinical/v1/internal/prescriptionsBody: CreatePrescriptionInternal.
    Search catalogsGET/catalog/v1/catalogs/searchQuery: id, name, includeCategories, limit, next, sortBy, sortOrder. Header X-Tenant-Id required. **PagedResponseOfCatalogSearchResponse**.
    Search productsGET/catalog/v1/products/searchQuery: variantId, sku, name, brand, catalogId, limit, next, etc. Header X-Tenant-Id required. Paged ProductResponse list.
    Get product by id (optional)GET/catalog/v1/products/{id}/findOpenAPI findProductById. Header X-Tenant-Id required. 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 **** 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.
    billingAddressAddressobject
    paymentPaymentInfoobject
    validatedAtUtcstringISO 8601 date-time.
    sourceSourceInfoobject
    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.
    addressAddressobject
    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
    addressAddressobject
    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.