# 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](#6-api-reference) 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 URL** – 
**Staging:** `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](#step-1-obtain-tenant-id-and-partner-app-id)).

## 3. Patient registration

Register or resolve the patient in Identity so you have a `**patientId**` (UUID) for screening (`POST /screening/v1/sessions`), prescriptions, and orders.

**Endpoint:** `POST /identity/v1/patients/resolve-by-email`  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`, `Content-Type: application/json`

The API **resolves** an existing patient by email in the tenant or **creates** one when none exists. Use `**patient.id`** from the response as `**patientId**` in later steps.

**Request body:** `PatientResolveRequest`


| Field                   | Type          | Notes                                                                            |
| ----------------------- | ------------- | -------------------------------------------------------------------------------- |
| `email`                 | string        | Patient email (lookup key).                                                      |
| `firstName`, `lastName` | string        | Legal name.                                                                      |
| `dateOfBirth`           | string (date) | ISO date, e.g. `1990-05-15`.                                                     |
| `gender`                | string        | Gender code or reference id per Identity / tenant.                               |
| `height`, `weight`      | object        | Height and weight payload per API contract (shape may be numeric or structured). |
| `isOnGlp`               | boolean       | Whether the patient is on GLP therapy.                                           |
| `stateCode`             | string        | US state or region code.                                                         |
| `address`               | Address       | Full **Address** — `addressLine1`, `city`, `state`, `zipCode`, `phone`, etc.     |
| `partnerPatientKey`     | string        | Stable external key (often same as email).                                       |
| `phoneNumber`           | string        | Contact phone.                                                                   |
| `stageId`               | string (uuid) | Funnel/stage id when your tenant requires it.                                    |
| `referrerId`            | string        | Referrer id when your tenant requires it.                                        |
| `externalId`            | string        | External/reference identifier from your system (often a generated UUID).         |


**Sample request (POST /identity/v1/patients/resolve-by-email):**

```json
{
  "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](#patientresponse).


| Field     | Type            | Notes                                                         |
| --------- | --------------- | ------------------------------------------------------------- |
| `found`   | boolean         | `true` if a patient already existed for this email.           |
| `created` | boolean         | `true` if a new patient was created.                          |
| `patient` | PatientResponse | Full patient record; use `**patient.id**` as `**patientId**`. |


**Sample response:**

```json
{
  "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](#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]`**:

```json
{
  "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)


| Name                  | Description                                                                                                 |
| --------------------- | ----------------------------------------------------------------------------------------------------------- |
| `id`                  | Filter by catalog UUID.                                                                                     |
| `name`                | Filter by catalog name (matching behavior depends on deployment).                                           |
| `includeCategories`   | When supported, include category rows on each catalog (string flag per OpenAPI — confirm with your client). |
| `limit`, `next`       | Pagination; `**next`** is the cursor from `**metadata**` on the previous page.                              |
| `sortBy`, `sortOrder` | Sorting.                                                                                                    |


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

**Sample response (`200`)** — `**PagedResponseOfCatalogSearchResponse`**:

```json
{
  "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)


| Name                                                      | Description                                                                |
| --------------------------------------------------------- | -------------------------------------------------------------------------- |
| `variantId`                                               | Filter by product variant UUID (useful when you already know the variant). |
| `sku`, `variantSkus`, `name`, `brand`                     | Product / variant text filters.                                            |
| `catalogId`, `categoryId`, `pharmacy`                     | Scope to catalog or category.                                              |
| `excludeProvider`, `limit`, `next`, `sortBy`, `sortOrder` | Exclusion, pagination, sorting.                                            |


**Request:** No body.

**Response:** `**200`** — `**PagedResponseOfProductResponse**`. Use `**data[].id**` as `**productId**`. Choose `**productVariantId**` from `**data[].variants[]**` (match the variant you need).

**Sample request**

```http
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`**):

```json
{
  "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**

```http
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:

```json
{
  "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**


| Name      | Description                          |
| --------- | ------------------------------------ |
| `name`    | Filter by questionnaire name / slug. |
| `version` | Questionnaire version string.        |


**Request:** No body.

**Response:** `**200`** — JSON **array** of `**QuestionnaireResponse`**. Use `**[].id**` as `**questionnaireId**` for `POST /screening/v1/sessions`.

**Sample response (GET /screening/v1/questionnaires/active?name=branded):**

```json
[
  {
    "id": "01933a7e-7b2c-7456-8000-000000000060",
    "name": "branded-medication-screening",
    "version": "1",
    "description": "Screening questionnaire for branded medication path",
    "statusId": "01933a7e-7b2c-7456-8000-000000000061",
    "treatmentId": "01933a7e-7b2c-7456-8000-000000000062",
    "effectiveFromUtc": "2024-01-01T00:00:00Z",
    "effectiveToUtc": "2099-12-31T23:59:59Z",
    "questions": [],
    "etag": "W/\"q1\"",
    "createdAtUtc": "2024-01-01T00:00:00Z",
    "createdBy": "system",
    "updatedAtUtc": "2024-01-01T00:00:00Z",
    "updatedBy": "system"
  }
]
```

##### Get questionnaire bundle (optional)

**Endpoint:** `GET /screening/v1/questionnaires/{id}/bundle`  
**Headers:** `Authorization: Bearer <token>`; optional `If-None-Match`

**Path parameters**


| Name | Description                                 |
| ---- | ------------------------------------------- |
| `id` | `**questionnaireId`** from the active list. |


**Query parameters**


| Name     | Description                                                           |
| -------- | --------------------------------------------------------------------- |
| `expand` | Optional; use per your client (e.g. related entities) when supported. |


**Response:** `**200`** — `**QuestionnaireBundleResponse**` (questions, answer options, and related fields per spec—use this to drive the screening UI after you create a session).

> **Note:** Carry forward `**productId`**, `**productVariantId**` (from **2.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:


| Field              | Description                                                                                                                                     |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel`          | Channel identifier (e.g. `web`) per your integration contract.                                                                                  |
| `patientId`        | From [Patient registration](#3-patient-registration) (§3) or confirmed in [Step 1: Ensure Patient exists](#step-1-ensure-patient-exists) above. |
| `questionnaireId`  | From Step 2 above.                                                                                                                              |
| `practitionerId`   | Send any GUID. This will be deprecated in the future.                                                                                           |
| `productId`        | Catalog product UUID.                                                                                                                           |
| `productVariantId` | Catalog product variant UUID.                                                                                                                   |


**Response:** `**201`** with `**SessionCreateResponse**`, including `**id**` (session id). Use that value as `{id}` in [Step 4](#step-4-submit-questionnaire-answers-and-complete-the-session) 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**`:

| Field            | Notes                                                                 |
| ---------------- | --------------------------------------------------------------------- |
| `sessionId`      | Same session id.                                                      |
| `questions`      | Array of questions for this batch (shape per `QuestionnaireNextQuestionResponse`). |
| `totalRemaining` | Server hint for remaining work (shape per deployment).               |
| `isLastBatch`    | When `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:

| Field          | Description                                                                 |
| -------------- | --------------------------------------------------------------------------- |
| `linkId`       | Question link id from the questionnaire bundle or `**/next**` payload (stable key for the question). |
| `answer`       | Value: string, number, boolean, or structured object per question type (single choice, multi-select, text, etc.). |
| `evidenceRef`  | Optional. 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`):**

```json
{
  "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.

#### 4.3 Record consent (when required)

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**`

| Field           | Description                                      |
| --------------- | ------------------------------------------------ |
| `validateOnly` | `false` to finalize; `true` to validate without completing (if supported). |

**Sample request:**

```json
{
  "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](#step-5-get-or-create-a-prescription)).

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

```json
{
  "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](#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]`**:

```json
{
  "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](#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](#tenantresponse)-like object with `id`, `name`, `partnerId`, `parentTenantId`, `role`, and attributes. Use the tenant(s) to build or resolve [ClinicInfo](#clinicinfo) for the order (name, address, email, phone—from tenant attributes or your own data).

**Sample response (GET /identity/v1/tenants/clinics/search):** Returns a paged list; each element in `data` has `id`, `name`, `partnerId`, `parentTenantId`, `role`, `etag`, and related fields. Map to ClinicInfo using the tenant’s name and attribute/address data as required by your backend.

#### Onboard clinic

**Endpoint:** `POST /identity/v1/tenants/clinics/onboard`  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`, `Content-Type: application/json`

**Body:** [OnboardClinicRequest](#onboardclinicrequest) — `partnerId`, `parentTenantId`, `name`, and `attributes` (e.g. `name`, `npi`, `addressId`, `email`, `phone`, `description`). See [Object reference](#7-object-reference) for schema details.

**Response:** 201 with [TenantResponse](#tenantresponse). Use the returned tenant (and any address resolved from `attributes.addressId`) to build [ClinicInfo](#clinicinfo) for order import.

**Sample request (POST /identity/v1/tenants/clinics/onboard):**

```json
{
  "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](#clinicinfo)).

**What you need for OrderImportRequest:**

- `**clinic`** – [ClinicInfo](#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](#practitionerresponse)).

**Sample response (GET /identity/v1/practitioners/search?id=01933a7e-ae5f-7789-8000-000000000021&limit=1):** Example shape for `**data[0]`**:

```json
{
  "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](#practitioneronboardrequest) (see [Object reference](#7-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](#practitioneronboardrequest) — see [Object reference](#7-object-reference) for schema.

**Sample request (POST /identity/v1/practitioners/onboard):**

```json
{
  "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):**

```json
{
  "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](#createprescriptioninternal); each item in `medsPrescribed` is [MedPrescribedInternal](#medprescribedinternal). See [Object reference](#7-object-reference) for schemas.

**Sample request (POST /clinical/v1/internal/prescriptions):**

```json
{
  "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]`**:

```json
{
  "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`.

```json
[
  {
    "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](#22-get-product-and-variant). **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):

```json
{
  "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](#orderimportrequest) — see [Object reference](#7-object-reference) for full schema and nested types ([CustomerInfo](#customerinfo), [ClinicInfo](#clinicinfo), [LineInfo](#lineinfo), [Address](#address), [TotalsInfo](#totalsinfo), [PaymentInfo](#paymentinfo), [SourceInfo](#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](#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](#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):**

```json
{
  "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):**

```json
{
  "id": "01933a7e-e293-7b34-8000-000000000040",
  "orderNumber": "ORD-10042",
  "tenantId": "01933a7e-6a1b-7123-8000-000000000003",
  "patientId": "01933a7e-7b2c-7456-8000-000000000010",
  "status": "Submitted",
  "currencyISO": "USD",
  "lines": [
    {
      "id": "01933a7e-f3a4-7c45-8000-000000000041",
      "productId": "01933a7e-bf60-7801-8000-000000000022",
      "productVariantId": "01933a7e-c071-7912-8000-000000000023",
      "sku": "MED-10MG",
      "uomId": "e0b02579-ddbd-4d58-8ed1-8df498178e1d",
      "qty": { "value": 1 },
      "unitPrice": { "value": 29.99 },
      "lineTotal": { "value": 29.99 },
      "prescriptionId": "01933a7e-9d4e-7678-8000-000000000020"
    }
  ],
  "subtotal": "29.99",
  "taxTotal": "0",
  "shippingTotal": "0",
  "grandTotal": "29.99",
  "shippingAddress": {
    "addressLine1": "123 Main St",
    "addressLine2": "Apt 4",
    "city": "San Francisco",
    "state": "CA",
    "zipCode": "94102",
    "phone": "5551234567"
  },
  "billingAddress": {
    "addressLine1": "123 Main St",
    "addressLine2": "Apt 4",
    "city": "San Francisco",
    "state": "CA",
    "zipCode": "94102",
    "phone": "5551234567"
  },
  "etag": "W/\"order-etag\""
}
```

---

## 6. API Reference


| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Get tenant + partner app     | GET    | `/identity/v1/tenants/me`                  | Returns partnerApp.id and tenants[].tenantId.                                                                                                                    |
| Search clinics               | GET    | `/identity/v1/tenants/clinics/search`      | Query: name, id, npi, externalId, limit, after. Requires X-Tenant-Id.                                                                                            |
| Search practitioners         | GET    | `/identity/v1/practitioners/search`        | Query: `id`, `npi`, `email`, `limit`, `next`, etc.                                                                                                               |
| Onboard clinic               | POST   | `/identity/v1/tenants/clinics/onboard`     | Body: OnboardClinicRequest. Creates or links a clinic tenant.                                                                                                    |
| Search patients              | GET    | `/identity/v1/patients/search`             | Query: `id`, `email`, `accountId`, `limit`, `next`, etc. Requires X-Tenant-Id.                                                                                   |
| Resolve patient by email     | POST   | `/identity/v1/patients/resolve-by-email`   | Body: PatientResolveRequest; returns existing or newly created patient.                                                                                          |
| List active questionnaires   | GET    | `/screening/v1/questionnaires/active`      | Query: `name`, `version`. Returns array of QuestionnaireResponse.                                                                                                |
| Get questionnaire bundle     | GET    | `/screening/v1/questionnaires/{id}/bundle` | Query: `expand`. Returns QuestionnaireBundleResponse (questions, options, etc.).                                                                                 |
| Create screening session     | POST   | `/screening/v1/sessions`                   | Body: SessionStart; headers: X-Tenant-Id.                                                                                                                        |
| Next questionnaire batch     | GET    | `/screening/v1/sessions/{id}/next`        | Path: session id. Optional query `limit`. Returns QuestionnaireNextResponse.                                                                                      |
| Submit session answers       | POST   | `/screening/v1/sessions/{id}/answer`       | Body: AnswerBundleRequest (`answers`: AnswerItem[]). Headers: X-Tenant-Id.                                                                                       |
| Record session consent       | POST   | `/screening/v1/sessions/{id}/consent`      | Body: CreateConsentRequest. When tenant flow requires consent before submit.                                                                                    |
| Submit screening session     | POST   | `/screening/v1/sessions/{id}/submit`       | Body: SessionSubmitRequest (`validateOnly`). Completes session; returns SessionSubmitResponse.                                                                   |
| Search prescriptions         | GET    | `/clinical/v1/prescriptions/search`        | Query: `id`, `patientId`, `patientEmail`, `limit`, `next`, etc. Requires X-Tenant-Id.                                                                            |
| Create prescriptions         | POST   | `/clinical/v1/internal/prescriptions`      | Body: CreatePrescriptionInternal.                                                                                                                                |
| Search catalogs              | GET    | `/catalog/v1/catalogs/search`              | Query: `id`, `name`, `includeCategories`, `limit`, `next`, `sortBy`, `sortOrder`. **Header `X-Tenant-Id` required.** `**PagedResponseOfCatalogSearchResponse`**. |
| Search products              | GET    | `/catalog/v1/products/search`              | Query: `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}/find`           | **OpenAPI** `findProductById`. **Header `X-Tenant-Id` required.** Some gateways omit this route—use **Search products** if 404. Single `ProductResponse`.        |
| Import order                 | POST   | `/commerce/v1/orders/import`               | Body: OrderImportRequest; headers: X-Tenant-Id, optional Idempotency-Key.                                                                                        |
| Search orders                | GET    | `/commerce/v1/orders/search`               | Query: patientId, orderNumber, limit, etc.                                                                                                                       |
| Get order by id              | GET    | `/commerce/v1/orders/{id}`                 | Full order details.                                                                                                                                              |


All non-catalog endpoints require **Authorization: Bearer **** and ****X-Tenant-Id**.

---

## 7. Object reference

### Address


| Field        | Type   | Description                     |
| ------------ | ------ | ------------------------------- |
| addressLine1 | string | Required.                       |
| addressLine2 | string | Required (can be empty string). |
| city         | string | Required.                       |
| state        | string | Required.                       |
| zipCode      | string | Required.                       |
| phone        | string | Required (e.g. 10 digits).      |


---

### ClinicInfo

**Required by the API schema** for OrderImportRequest.


| Field              | Type   | Description |
| ------------------ | ------ | ----------- |
| clinicName         | string |             |
| clinicAddressLine1 | string |             |
| clinicAddressLine2 | string |             |
| clinicCity         | string |             |
| clinicState        | string |             |
| clinicZip          | string |             |
| clinicEmail        | string |             |
| clinicPhoneNumber  | string |             |


If your backend accepts orders without `clinic`, you may still need to send a minimal object; confirm with the API or backend team.

---

### CreatePrescriptionInternal

Request body for `POST /clinical/v1/internal/prescriptions`:


| Field                 | Type                                              | Description                                        |
| --------------------- | ------------------------------------------------- | -------------------------------------------------- |
| screeningSessionId    | string (UUID)                                     | Required. From a screening session.                |
| medsPrescribed        | [MedPrescribedInternal](#medprescribedinternal)[] | Required. Array of prescribed meds (see below).    |
| pharmacyId            | string (UUID)                                     | Required. Pharmacy UUID.                           |
| pharmacyNote          | string                                            | Optional. e.g. "Bill to patient, ship to patient". |
| practitionerId        | string (UUID)                                     | Optional. Practitioner UUID (from Step 4).         |
| practitionerFirstName | string                                            | Optional.                                          |
| practitionerLastName  | string                                            | Optional.                                          |
| practitionerNpi       | string                                            | Optional.                                          |
| practitionerPhone     | string                                            | Optional.                                          |
| practitionerAddress   | string                                            | Optional.                                          |
| practitionerCity      | string                                            | Optional.                                          |
| practitionerState     | string                                            | Optional.                                          |
| practitionerZip       | string                                            | Optional.                                          |
| paymentAmount         | number                                            | Optional.                                          |
| paymentLink           | string                                            | Optional. e.g. Stripe payment link.                |
| paymentLinkSentAt     | string (date-time)                                | Optional.                                          |


---

### CustomerInfo


| Field     | Type   | Description                                     |
| --------- | ------ | ----------------------------------------------- |
| patientId | string | Patient UUID.                                   |
| accountId | string | Account UUID (from patient or profile/session). |


---

### LineInfo


| Field                 | Type                          | Description                                                                                                               |
| --------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| externalLineId        | string                        | Unique line id (e.g. `LINE-<ts>` or same as externalOrderId for single line).                                             |
| productId             | string                        | Product UUID.                                                                                                             |
| productVariantId      | string                        | Product variant UUID.                                                                                                     |
| uomId                 | string                        | **Required by API.** Unit of measure UUID. This app does not use it for logic; it sends the variant's uomId or a default. |
| qty                   | Record<string, any> or string | Quantity (API may expect string).                                                                                         |
| unitPrice             | Record<string, any> or string | Unit price.                                                                                                               |
| lineTotal             | Record<string, any> or string | Line total.                                                                                                               |
| priceComponents       | array                         | e.g. `[]`.                                                                                                                |
| requiresPrescription  | boolean                       | Typically `true` for prescription-based orders.                                                                           |
| matchedPrescriptionId | string                        | Prescription UUID.                                                                                                        |


---

### MedPrescribedInternal

One entry in `medsPrescribed` ([CreatePrescriptionInternal](#createprescriptioninternal)):


| Field            | Type          | Description                     |
| ---------------- | ------------- | ------------------------------- |
| productId        | string (UUID) | Required. Product UUID.         |
| productVariantId | string (UUID) | Required. Product variant UUID. |
| strength         | string        | Required. e.g. `"10mg"`.        |
| frequency        | string        | Required. e.g. `"once daily"`.  |
| route            | string        | Required. e.g. `"oral"`.        |


---

### OnboardClinicRequest

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


| Field          | Type                           | Description          |
| -------------- | ------------------------------ | -------------------- |
| partnerId      | string (UUID)                  | Partner UUID.        |
| parentTenantId | string (UUID)                  | Parent tenant UUID.  |
| name           | string                         | Clinic/tenant name.  |
| attributes     | OnboardClinicAttributesRequest | Required. See below. |


**attributes:** `name`, `npi`, `addressId` (UUID), `email`, `phone`, `description`.

---

### OrderImportRequest

Full request body for `POST /commerce/v1/orders/import`. The API schema marks these as required: `externalOrderId`, `customer`, `clinic`, `currencyISO`, `pricing`, `lines`, `promos`, `totals`, `shippingAddress`, `billingAddress`, `payment`, `validatedAtUtc`, `source`, `notes`.


| Field           | Type                                 | Description                                                                 |
| --------------- | ------------------------------------ | --------------------------------------------------------------------------- |
| externalOrderId | string                               | Unique external order id (e.g. `ORDER-<ts>` or Stripe payment intent id).   |
| customer        | [CustomerInfo](#customerinfo)        | Patient and account identifiers.                                            |
| clinic          | [ClinicInfo](#clinicinfo)            | **Required by schema.** Clinic name, full address, email, phone.            |
| currencyISO     | string                               | e.g. `"USD"`.                                                               |
| pricing         | object / PricingInfo                 | e.g. `{ priceBookVersion: "1.0", components: [] }`.                         |
| lines           | [LineInfo](#lineinfo)[]              | Order lines; each can reference a prescription via `matchedPrescriptionId`. |
| promos          | string[]                             | Promo codes; use `[]` if none.                                              |
| totals          | [TotalsInfo](#totalsinfo)            | subtotal, taxTotal, shippingTotal, grandTotal.                              |
| shippingAddress | [Address](#address)                  | Full shipping address.                                                      |
| billingAddress  | [Address](#address) | object         | Full billing address.                                                       |
| payment         | [PaymentInfo](#paymentinfo) | object | Payment id, external reference, status; can be empty for unpaid.            |
| validatedAtUtc  | string                               | ISO 8601 date-time.                                                         |
| source          | [SourceInfo](#sourceinfo) | object   | partnerAppId, channel, region.                                              |
| notes           | string                               | Free text.                                                                  |


---

### OrderResponse

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


| Field                                         | Type                | Description                  |
| --------------------------------------------- | ------------------- | ---------------------------- |
| id                                            | string              | Order UUID.                  |
| orderNumber                                   | string              | Human-readable order number. |
| status                                        | string              | Order status.                |
| patientId                                     | string              |                              |
| lines                                         | OrderLineResponse[] | Order lines.                 |
| subtotal, taxTotal, shippingTotal, grandTotal | various             |                              |
| payment                                       | object              |                              |
| shippingAddress, billingAddress               | object              |                              |


---

### PatientResponse

Relevant for order import:


| Field     | Type                         | Description                                        |
| --------- | ---------------------------- | -------------------------------------------------- |
| id        | string                       | `customer.patientId`.                              |
| accountId | string                       | Often used as `customer.accountId`.                |
| address   | [Address](#address) | object | Required for shipping/billing if not from payment. |
| email     | string                       | Optional for billing.                              |


---

### PaymentInfo


| Field             | Type   | Description                 |
| ----------------- | ------ | --------------------------- |
| paymentId         | string | Internal payment UUID.      |
| externalReference | string | e.g. Stripe session id.     |
| status            | string | e.g. `"paid"`, `"pending"`. |


Extended payment payload (as used after Stripe) may include `intentId`, `provider` (e.g. `"Stripe"`).

---

### PractitionerOnboardRequest

Request body for `POST /identity/v1/practitioners/onboard` (create or find practitioner by NPI):


| Field           | Type                | Description                                                                   |
| --------------- | ------------------- | ----------------------------------------------------------------------------- |
| accountId       | string (UUID)       | Account UUID for the practitioner (from your identity/tenant context).        |
| npi             | string              | National Provider Identifier (10 digits).                                     |
| firstName       | string              | Required.                                                                     |
| middleName      | string              | Required; use `""` if none.                                                   |
| lastName        | string              | Required.                                                                     |
| suffix          | string              | Required; use `""` if none.                                                   |
| displayName     | string              | e.g. `"Dr. Maria Smith"`.                                                     |
| providerTypeId  | string (UUID)       | Provider type from tenant config (e.g. physician, NP).                        |
| statusId        | string (UUID)       | Status from tenant config (e.g. active).                                      |
| address         | [Address](#address) | Full address (addressLine1, addressLine2, city, state, zipCode, phone).       |
| createIfMissing | boolean             | Optional; default `true`. If `true`, creates the practitioner when not found. |


---

### PractitionerResponse

Returned by `GET /identity/v1/practitioners/search` (each item in `**data`**) and in `POST .../practitioners/onboard` response:


| Field               | Type                         | Description                                                  |
| ------------------- | ---------------------------- | ------------------------------------------------------------ |
| id                  | string                       | Practitioner UUID; use as `practitionerId` in prescriptions. |
| tenantId            | string                       | Tenant UUID.                                                 |
| accountId           | string                       | Account UUID.                                                |
| npi                 | string                       | National Provider Identifier.                                |
| firstName           | string                       |                                                              |
| middleName          | string                       |                                                              |
| lastName            | string                       |                                                              |
| suffix              | string                       |                                                              |
| displayName         | string                       |                                                              |
| providerTypeId      | string                       |                                                              |
| statusId            | string                       |                                                              |
| inactivated         | boolean                      |                                                              |
| email               | string                       |                                                              |
| emailVerified       | boolean                      |                                                              |
| phoneNumber         | string                       |                                                              |
| phoneNumberVerified | boolean                      |                                                              |
| address             | [Address](#address) | object | Full address.                                                |
| etag                | string                       |                                                              |


---

### PrescriptionResponse

Relevant fields for building the order:


| Field              | Type   | Description                                                   |
| ------------------ | ------ | ------------------------------------------------------------- |
| id                 | string | Used as `matchedPrescriptionId`.                              |
| patientId          | string | Used as `customer.patientId`.                                 |
| productId          | string | Used as `lines[].productId`.                                  |
| productVariantId   | string | Used as `lines[].productVariantId` (or resolve from product). |
| quantityAuthorized | object | Used for `lines[].qty` (e.g. `.value` or default 1).          |


---

### SourceInfo


| Field        | Type   | Description                                           |
| ------------ | ------ | ----------------------------------------------------- |
| partnerAppId | string | From `GET /identity/v1/tenants/me` → `partnerApp.id`. |
| channel      | string | e.g. `"web"`.                                         |
| region       | string | e.g. `"US"`.                                          |


---

### TotalsInfo


| Field         | Type                          | Description      |
| ------------- | ----------------------------- | ---------------- |
| subtotal      | Record<string, any> or string | Subtotal amount. |
| taxTotal      | Record<string, any> or string | Tax total.       |
| shippingTotal | Record<string, any> or string | Shipping total.  |
| grandTotal    | Record<string, any> or string | Grand total.     |


In this app, totals are often sent as **strings** (e.g. `"0"`, `"12.99"`) for decimal precision.

---

### TenantResponse

Returned by `POST /identity/v1/tenants/clinics/onboard` and in clinic search results:


| Field                      | Type               | Description                                                   |
| -------------------------- | ------------------ | ------------------------------------------------------------- |
| id                         | string             | Tenant (clinic) UUID.                                         |
| partnerId                  | string             | Partner UUID.                                                 |
| parentTenantId             | string             | Parent tenant UUID.                                           |
| name                       | string             | Clinic/tenant name.                                           |
| role                       | TenantRoleResponse | Tenant role.                                                  |
| etag                       | string             | For conditional updates.                                      |
| createdAtUtc, updatedAtUtc | string             | Timestamps.                                                   |
| atributes                  | array              | Tenant role attribute values (e.g. address, contact details). |


Use with address/attribute data to build [ClinicInfo](#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](#step-7-build-orderimportrequest-and-call-import-order) and the sample request there.