# 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`). Hub webhook routes are documented in [§8 Webhooks (Hub API)](#8-8-working-with-webhooks). 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. 1. Getting started

### 1.1 High-level flow (registration → screening → consult → prescription → order)

The app’s provider-portal flow is:

1. **Tenant & auth** – Resolve tenant ID and partner app ID ([Fetch Tenant ID and Partner App ID](#fetch-tenant-id-and-partner-app-id)); ensure all requests use the same tenant and Bearer token.
2. **Patient** – Ensure the patient exists and has **`account.id`** (for `customer.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. **Practitioner** – Ensure the practitioner exists and you have `practitionerId` for consults, prescriptions, and order context as needed. **Note:** Practitioner registration requires a valid NPI verifiable from NPPES NPI Registry.
5. **Product / variant** – Resolve `productVariantId` from the catalog if not already on the prescription. Product Variant is the actual medication that will be ordered.
6. **Screening** – Complete the questionnaire session and obtain **screeningSessionId** ([§4 Patient Screening](#4-4-patient-screening)).
7. **Consult** (when your tenant requires it) – Create and drive a consult tied to that screening session, patient, practitioner, and product ([§5 Consult](#5-5-consult)). Skip if you prescribe directly after screening.
8. **Prescription** – Create (or find) a prescription with `productId` and `productVariantId` via **POST /clinical/v1/prescriptions/create**, or use **GET /clinical/v1/prescriptions/search** for an existing record ([§6 Step 2](#step-2-get-or-create-a-prescription)).
9. **Order import** – Build `OrderImportRequest` and call `POST /commerce/v1/orders/import` ([§7 Creating Orders](#7-7-creating-orders)).

For **outbound webhooks** (register your HTTPS URL, signing secret, test and retry deliveries), see [§8 Webhooks (Hub API)](#8-8-working-with-webhooks). For a compact **endpoint index** across services, see [§9 API Reference](#9-api-reference).

## 2. 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)
  - **Consult** (`/consult/v1/*`) – `api://haas.consult/haas.api.read`, `api://haas.consult/haas.api.write` (as required by each operation in the spec; see [§5 Consult](#5-5-consult))
  - **Hub** (`/hub/v1/*`) – `api://haas.hub/haas.api.read`, `api://haas.hub/haas.api.write` for webhook subscriptions, deliveries, and related operations (see [§8](#8-8-working-with-webhooks)).
- **Tenant context** – **X-Tenant-Id** header is required on Hub subscription and delivery calls (and most other tenant-scoped routes). You obtain it from `GET /identity/v1/tenants/me` (see [Fetch Tenant ID and Partner App ID](#fetch-tenant-id-and-partner-app-id)). `GET /hub/v1/webhooks/event-types/search` has no tenant header in the bundled OpenAPI—confirm with your deployment if the gateway differs.

### Fetch Tenant ID and Partner App ID

**Endpoint:** `GET /identity/v1/tenants/me` (`getMyTenants`)  
**Headers:** `Authorization: Bearer <access_token>`

**Response type:** [MyTenantsResponse](#mytenantsresponse) — **`partnerApp`** may be `null` when the caller has no linked partner application.

**What to use:**

| Need | Field |
| ---- | ----- |
| **X-Tenant-Id** on tenant-scoped routes | **partnerApp.tenants[].tenantId** for the clinic/tenant your user or app is bound to (often the first row, or match by name/role in multi-tenant apps). |
| **source.partnerAppId** on order import | **partnerApp.id** |
| Partner identity | **partnerApp.partner** ([MyTenantsPartnerResponse](#mytenantspartnerresponse)) — `id`, `name`, `role` |
| Linked pharmacy tenants | **partnerApp.tenantProviders** — same shape as **tenants** ([MyTenantsTenantResponse](#mytenantstenantresponse)); use **role** (e.g. `Pharmacy`) and **tenantId** when resolving **pharmacyId** for prescriptions or order display. |

**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",
      "role": "partner"
    },
    "tenants": [
      {
        "tenantId": "01933a7e-6a1b-7123-8000-000000000003",
        "name": "Acme Clinic",
        "role": "provider",
        "attributes": []
      }
    ],
    "tenantProviders": [
      {
        "tenantId": "0193ace2-7048-7692-9f57-91e59b94b40a",
        "name": "Example Pharmacy",
        "role": "Pharmacy",
        "attributes": []
      }
    ],
    "attributes": []
  }
}
```

## 3. 3. Patient Registration

### Prerequisites — registration

Complete **[§2 Access & Authentication](#2-2-access-authentication)** — Bearer token, **Identity** read/write scopes, and **X-Tenant-Id** from [Fetch Tenant ID and Partner App ID](#fetch-tenant-id-and-partner-app-id).

Register or resolve the patient in Identity so you have a **patientId** (UUID) for screening (`POST /screening/v1/sessions/create`), 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        | **`"Male"`** or **`"Female"`** — plain text. |
| `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`; optional `label` (e.g. `Shipping Address`).     |
| `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).         |


The sample below sets **`gender`** to **`"Female"`** (use **`"Male"`** for male patients).

**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",
    "label": "Shipping Address"
  },
  "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"
    }
  }
}
```

#### Lookup by id or email (`GET /identity/v1/patients/search`)

When you need to **find or verify** a patient by **id**, **email**, or **accountId** (instead of or after [resolve-by-email](#3-3-patient-registration)) above, use search.

**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.account.id** → `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 **`account.id`**, 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",
  "account": {
    "id": "01933a7e-8c3d-7567-8000-000000000011",
    "username": "jane.doe@example.com",
    "displayName": "Jane Doe",
    "etag": "W/\"acct-etag\""
  },
  "firstName": "Jane",
  "lastName": "Doe",
  "dateOfBirth": "1990-05-15",
  "height": 65,
  "weight": 140,
  "isOnGlp": false,
  "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
  }
}
```

## 4. 4. Patient Screening

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.

### Prerequisites — screening

Complete these before [Step 1: Select medication](#step-1-select-medication):

- **[§3 Patient Registration](#3-3-patient-registration)** — [Prerequisites — registration](#prerequisites-registration), resolve-by-email, optional [Lookup by id or email (`GET /identity/v1/patients/search`)](#lookup-by-id-or-email-get-identityv1patientssearch); you need **patientId** for `CreateSessionRequest`.
- **Screening routes** — Confirm **Screening** scopes on your Bearer token ([§2 Access & Authentication](#2-2-access-authentication)) for `/screening/v1/*`.

---

### Step 1: 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.                                              |
| `limit`, `next`, `sortBy`, `sortOrder`                    | 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/{id}/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/create`.

**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 2: Create screening session

**Endpoint:** `POST /screening/v1/sessions/create` (`operationId`: `createSession`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`, `Content-Type: application/json`

**Body:** `CreateSessionRequest` — all fields are required per the OpenAPI schema:


| Field              | Description                                                                                                                                     |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `channel`          | Channel identifier (e.g. `web`) per your integration contract.                                                                                  |
| `patientId`        | From [§3 Patient Registration](#3-3-patient-registration); optional lookup via [Lookup by id or email](#lookup-by-id-or-email-get-identityv1patientssearch). |
| `questionnaireId`  | From Step 2 above.                                                                                                                              |
| `productId`        | Catalog product UUID.                                                                                                                           |
| `productVariantId` | Catalog product variant UUID.                                                                                                                   |


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

After `POST /screening/v1/sessions/create` 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. prescriptions tied to `screeningSessionId` when your deployment supports that) can proceed.

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

#### 3.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.

#### 3.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.

#### 3.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.

#### 3.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** — **SubmitSessionResponse** — may include **status**, **completedAt**, and **recommendedProductVariants** (used by clinical flows to suggest variants). Use the same **sessionId** as **screeningSessionId** on **POST /consult/v1/consults/create** ([§5 Step 2](#step-2-create-a-consult)) when your flow includes a consult. For prescribing, pass the consult **id** as **consultId** on **POST /clinical/v1/prescriptions/create** ([§6 Step 2](#step-2-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. 5. Consult

Many tenant programs run a **consult** (provider visit or chat thread) **after** screening and **before** prescribing. Consult records tie together **patient**, **practitioner**, **screening session**, and **catalog product / variant** via **POST /consult/v1/consults/create** (**CreateConsultRequest**). Skip this section if your flow prescribes directly from screening without a separate consult step.

### Prerequisites — consult

- **[§4 Prerequisites — screening](#prerequisites-screening)** — Completed screening; you need **screeningSessionId** from [§4 Step 3](#step-3-submit-questionnaire-answers-and-complete-the-session).
- **[§3 Patient Registration](#3-3-patient-registration)** — **patientId**.
- **[§2 Access & Authentication](#2-2-access-authentication)** — Bearer token with **Consult** scopes (`api://haas.consult/haas.api.read`, `api://haas.consult/haas.api.write`).
- **Practitioner** — **practitionerId** for **CreateConsultRequest** (resolve with Identity search/onboard; see [§6 Prerequisites — prescriptions](#prerequisites-prescriptions) under **Ensure Practitioner exists**).
- **Product / variant** — **selectedProductId** and **selectedProductVariantId** aligned with screening / catalog ([§4 Step 1](#step-1-select-medication)).

### Step 1: List consults (optional)

**Endpoint:** `GET /consult/v1/consults/search` (`operationId`: `searchConsults`)  
**Headers:** `Authorization: Bearer <token>`, **`X-Tenant-Id`** (required)

**Query (typical):** `patientId`, `practitionerId`, `status`, `screeningSessionId`, `selectedProductId`, `selectedProductVariantId`, `limit`, `next`, `sortBy`, `sortOrder`

**Response:** **200** — **PagedResponseOfGetConsultSummaryResponse** — use to find or resume consults for a patient or session context.

**Sample response (`200`)** — shape of **data[0]** when listing:

```json
{
  "data": [
    {
      "id": "01933a7e-a001-7c01-8000-000000000040",
      "status": "active",
      "tenantId": "01933a7e-6a1b-7123-8000-000000000003",
      "patientId": "01933a7e-7b2c-7456-8000-000000000010",
      "practitionerId": "01933a7e-ae5f-7789-8000-000000000021",
      "screeningSessionId": "01933a7e-d182-7a23-8000-000000000030",
      "selectedProductId": "01933a7e-bf60-7801-8000-000000000022",
      "selectedProductVariantId": "01933a7e-c071-7912-8000-000000000023",
      "channel": "web",
      "startedUtc": "2025-03-04T15:00:00Z"
    }
  ],
  "metadata": {
    "limit": "10",
    "nextCursor": "",
    "hasMore": false
  }
}
```

*(Metadata keys may match **PageMetadata** in your client; confirm against the spec.)*

### Step 2: Create a consult

**Endpoint:** `POST /consult/v1/consults/create` (`createConsult`)  
**Headers:** `Authorization: Bearer <token>`, **`X-Tenant-Id`**, `Content-Type: application/json`

**Body:** **CreateConsultRequest** — required per OpenAPI: **patientId**, **practitionerId**, **topic**, **channel**, **screeningSessionId**, **selectedProductId**, **selectedProductVariantId**.

**Sample request (POST /consult/v1/consults/create):**

```json
{
  "patientId": "01933a7e-7b2c-7456-8000-000000000010",
  "practitionerId": "01933a7e-ae5f-7789-8000-000000000021",
  "topic": "weight-loss-intake",
  "channel": "web",
  "screeningSessionId": "01933a7e-d182-7a23-8000-000000000030",
  "selectedProductId": "01933a7e-bf60-7801-8000-000000000022",
  "selectedProductVariantId": "01933a7e-c071-7912-8000-000000000023"
}
```

**Response:** **200** — **CreateConsultResponse** — includes **id** (consult uuid). Use this id on GET/messages routes below.

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

```json
{
  "id": "01933a7e-a001-7c01-8000-000000000040",
  "providerTenantId": "01933a7e-6a1b-7123-8000-000000000003",
  "createdAtUtc": "2025-03-04T15:05:00Z",
  "updatedAtUtc": "2025-03-04T15:05:00Z"
}
```

### Step 3: Get consult detail

**Endpoint:** `GET /consult/v1/consults/{id}/get` (`getConsult`)  
**Headers:** `Authorization: Bearer <token>`, **`X-Tenant-Id`**; optional **If-None-Match**

**Response:** **200** — **GetConsultResponse** — **status**, **screeningSessionId**, **patientId**, **practitionerId**, selected product ids, **conversationRef**, **participants**, **chats**, lifecycle timestamps, **etag**.

**Sample response (`200`)** — **GetConsultResponse** for path `{id}` (same consult **id** as create response):

```json
{
  "id": "01933a7e-a001-7c01-8000-000000000040",
  "status": "active",
  "tenantId": "01933a7e-6a1b-7123-8000-000000000003",
  "patientId": "01933a7e-7b2c-7456-8000-000000000010",
  "practitionerId": "01933a7e-ae5f-7789-8000-000000000021",
  "screeningSessionId": "01933a7e-d182-7a23-8000-000000000030",
  "selectedProductId": "01933a7e-bf60-7801-8000-000000000022",
  "selectedProductVariantId": "01933a7e-c071-7912-8000-000000000023",
  "channel": "web",
  "startedUtc": "2025-03-04T15:05:00Z",
  "conversationRef": "",
  "participants": [],
  "chats": [],
  "etag": "W/\"consult-etag\""
}
```

### Step 4: Messages and integrations

| Operation | Method | Path | Notes |
| --------- | ------ | ---- | ----- |
| Search messages | GET | `/consult/v1/consults/{id}/messages/search` | `searchConsultMessages`. Paged **ConsultMessageResponse**. **X-Tenant-Id** required. |
| Add participant | POST | `/consult/v1/consults/{id}/participants/add` | `addConsultParticipant`. Body **AddParticipantRequest** (`practitionerId`, `displayName`). **201** — **AddParticipantResponse**. Registers a practitioner on the consult chat; **patientId** on the row is taken from the consult. |
| Post message | POST | `/consult/v1/consults/{id}/messages/post` | `postConsultMessage`. Body **PostMessageRequest** (`content`, optional `participantId`, `isMedia`). **participantId** is the consult participant **row** id from **ConsultParticipantDto**, not the identity patient/practitioner uuid. |
| Update message delivery | POST | `/consult/v1/consults/{id}/messages/{messageId}/update-delivery` | `updateConsultMessageDelivery`. Body **UpdateMessageDeliveryRequest** (`deliveredUtc`, `readUtc`). **200** — **UpdateMessageDeliveryResponse**. |
| Post CS message | POST | `/consult/v1/consults/{id}/cs-messages` | `postCsMessage`. Body **PostCsMessageRequest**. Consult **write** scope. |

Beluga inbound webhooks (`POST /consult/v1/consult/message`, `/consult/v1/consult/cs-message`, `/consult/v1/consult/close`, `/consult/v1/consult/cancel`) are **not** in the unified partner OpenAPI; they are server-to-server integration routes on the consult service.

### Step 5: Close or cancel consult (provider / tenant API)

| Operation | Method | Path | Notes |
| --------- | ------ | ---- | ----- |
| Close | POST | `/consult/v1/consults/{id}/close` | `closeConsult`. Path **id** = consult UUID; **X-Tenant-Id** required. No body. **200** — **CloseConsultResponse**. |
| Cancel | POST | `/consult/v1/consults/{id}/cancel` | `cancelConsult`. Path **id** = consult UUID; **X-Tenant-Id** required. No body. **200** — **CancelConsultResponse**. |

**Close** marks the consult ended (e.g. provider portal after prescription create). **Cancel** records cancellation with **canceledAtUtc** and **canceledBy**.

When your workflow is ready to prescribe, continue to [§6 Creating Prescriptions](#6-6-creating-prescriptions).

## 6. 6. Creating Prescriptions

### Prerequisites — prescriptions

Build on [§4 Prerequisites — screening](#prerequisites-screening) (patient, screening session workflow, Screening scopes). If your program runs a provider consult after screening, complete [§5 Prerequisites — consult](#prerequisites-consult) first. For Identity patient fields used on orders, follow [§3 Patient Registration](#3-3-patient-registration) and [Lookup by id or email (`GET /identity/v1/patients/search`)](#lookup-by-id-or-email-get-identityv1patientssearch) when needed.

This section adds **clinic** and **practitioner** prerequisites not covered above:


#### 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](#10-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`.

---

#### Ensure Practitioner exists

You need **practitioner.id** as **practitionerId** in [CreatePrescriptionCommandRequest](#createprescriptioncommandrequest) when creating prescriptions ([Step 2](#step-2-get-or-create-a-prescription)); it is optional in the OpenAPI schema but typically required for your workflow.

##### 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](#10-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](#10-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 1: 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 2: Get or create a prescription

Clinical routes in **CareAtlas-Unified-API-oas3-v0.1.json** include **GET /clinical/v1/prescriptions/search**, **POST /clinical/v1/prescriptions/create**, **GET /clinical/v1/prescriptions/{id}/validate**, and **PATCH /clinical/v1/prescriptions/{id}/update**. For provider prescribing after a consult, use **create** with **consultId** from [§5 Step 2](#step-2-create-a-consult) (and **pharmacyId**, **medsPrescribed**, practitioner fields per your workflow).

#### Search existing prescriptions

**Endpoint:** `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 prescriptions (provider flow)

**Endpoint:** `POST /clinical/v1/prescriptions/create` (`operationId`: `createPrescriptionCommand`)  
**Headers:** `X-Tenant-Id`, `Authorization`, `Content-Type: application/json`

**Body:** [CreatePrescriptionCommandRequest](#createprescriptioncommandrequest); each element of **medsPrescribed** is a [MedPrescribedInternal](#medprescribedinternal). See [Object reference](#10-object-reference) for fields.

**Response:** **201 Created** — JSON **array** of **PrescriptionResponse** (one entry per item in **medsPrescribed**).

**Sample request:**

```json
{
  "consultId": "01933a7e-a001-7c01-8000-000000000040",
  "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 (`201`):** Same shape as each element in the search **data** array below (array of **PrescriptionResponse**).

#### Validate a prescription

**Endpoint:** `GET /clinical/v1/prescriptions/{id}/validate` (`operationId`: `validatePrescription`)  
**Headers:** `X-Tenant-Id`, `Authorization`

**Response:** **200** — **ValidatePrescriptionResponse** (see [ValidatePrescriptionResponse](#validateprescriptionresponse)): includes **isValid** (boolean).

#### Update a prescription

**Endpoint:** `PATCH /clinical/v1/prescriptions/{id}/update` (`operationId`: `updatePrescriptionCommand`)  
**Headers:** `X-Tenant-Id`, `Authorization`, `Content-Type: application/json`

**Body:** [UpdatePrescriptionCommandRequest](#updateprescriptioncommandrequest) — all properties optional in the bundled schema; use for notes, approval metadata, or payment fields per your process.

**Response:** **200** — **PrescriptionResponse**.

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

### Step 3: Map prescription fields for order import

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 1)
- **quantityAuthorized** → `lines[].qty` (e.g. `quantityAuthorized.value` or default 1)

---

## 7. 7. Creating Orders

### Prerequisites — orders

Build on [§6 Prerequisites — prescriptions](#prerequisites-prescriptions) (clinic, practitioner, product variant, and prescription). Confirm [§2 Access & Authentication](#2-2-access-authentication) grants **Commerce** scopes for `POST /commerce/v1/orders/import` (same Bearer token and **X-Tenant-Id** as earlier steps).

Complete [§6 Step 3](#step-3-map-prescription-fields-for-order-import) so you have the prescription and patient field mapping before assembling the payload. Full request shape and the import call are in [Build OrderImportRequest and call Import Order](#build-orderimportrequest-and-call-import-order) below.

### Build OrderImportRequest and call Import Order

Build the request body and call the import endpoint. **Body schema:** [OrderImportRequest](#orderimportrequest) — see [Object reference](#10-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 [§6 Step 1](#step-1-resolve-product-variant-medication))
- **prescription.quantityAuthorized** → `lines[].qty` (e.g. `.value` or default 1)
- **patient.account.id** → `customer.accountId` ([§3](#3-3-patient-registration) / [lookup](#lookup-by-id-or-email-get-identityv1patientssearch))
- **patient.address** → `shippingAddress` and `billingAddress` (full [Address](#address) objects)
- **clinic** → [§6 Prerequisites — prescriptions](#prerequisites-prescriptions) (**Ensure Clinic exists**); **source.partnerAppId** from [Fetch Tenant ID and Partner App ID](#fetch-tenant-id-and-partner-app-id)

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\""
}
```

## 8. 8. Working with Webhooks

The **Hub** service manages **outbound webhooks**: CareAtlas POSTs event payloads to **your** HTTPS URL when something happens (per event type). You do **not** implement CRUD against arbitrary domain tables here—you configure **subscriptions**, **verify** delivery to your endpoint, and **operate** retries and secrets. All Hub webhook routes use the same API base URL as the rest of the unified API; paths published in `CareAtlas-Unified-API-oas3-v0.1.json` appear in the [§9 API Reference](#9-api-reference) table.

The bundled spec includes subscription **create**, **patch update**, **disable**, plus **search**, **get** (`…/get`), **deliveries**, **test**, **retry**, and **rotate-secret**.

**OpenAPI:** Examples include `createSubscriptionCommand`, `updateSubscriptionCommand`, `disableSubscriptionCommand`, `listEventTypesQuery`, `listSubscriptionsQuery`, `getSubscriptionQuery`, `listSubscriptionsForEventType`, `testWebhook`, `getDeliveryQuery`, `retryDelivery`, `rotateSecret` — verify names in the spec.

### Getting started

#### Available event types (reference)

**Display name** (left) is for readability; the **name** string (right) matches event-type identifiers. The authoritative list—including `payloadSchema`—comes from **GET /hub/v1/webhooks/event-types/search** ([List event types](#list-event-types)).

| Display name | `name` |
| ------------ | ------ |
| Consult Created | `consult.created` |
| Consult Updated | `consult.updated` |
| Invoice Created | `invoice.created` |
| Invoice Updated | `invoice.updated` |
| Order Created | `order.created` |
| Order Updated | `order.updated` |
| Patient Created | `patient.created` |
| Patient Updated | `patient.updated` |
| Payment Created | `payment.created` |
| Payment Updated | `payment.updated` |
| Practitioner Created | `practitioner.created` |
| Practitioner Updated | `practitioner.updated` |
| Prescription Created | `prescription.created` |
| Prescription Updated | `prescription.updated` |
| Screening Response Created | `screening.response.created` |
| Screening Response Updated | `screening.response.updated` |
| Screening Session Created | `screening.session.created` |
| Screening Session Updated | `screening.session.updated` |

#### Your inbound endpoint

Configure your server to accept **HTTPS POST** from CareAtlas, respond with **2xx** quickly, and verify signatures using the **signingSecret** from **[Create subscription](#create-a-subscription)** (`201`) or **[Rotate signing secret](#rotate-signing-secret)**. Exact headers and body envelope follow your deployment’s webhook contract (payload shapes are described per `EventTypeResponse.payloadSchema` where provided).

#### Authentication and headers

- **Bearer token** must include Hub scopes: `api://haas.hub/haas.api.read` (event types and subscription reads) and `api://haas.hub/haas.api.write` (create, update, and disable subscription; test delivery; retry delivery; rotate secret).
- **X-Tenant-Id** – Required on routes that declare it (e.g. **POST /hub/v1/webhooks/subscriptions/create**, **GET /hub/v1/webhooks/subscriptions/search**, **GET /hub/v1/webhooks/subscriptions/{id}/deliveries**). **GET /hub/v1/webhooks/event-types/search** has no tenant header in the bundled OpenAPI.

#### List event types

**Endpoint:** `GET /hub/v1/webhooks/event-types/search` (`listEventTypesQuery`)  
**Headers:** `Authorization: Bearer <token>` (Hub **read** scope).

**Response**

```text
HTTP 200 — JSON array of EventTypeResponse.
Fields per item: name, displayName, description, sourceService, payloadSchema.
Use each `name` string in the `eventTypes` array when creating or updating a subscription.
```

### Subscriptions

#### Create a subscription

**Endpoint:** `POST /hub/v1/webhooks/subscriptions/create` (`createSubscriptionCommand`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`, `Content-Type: application/json`

**Body:** `CreateSubscriptionRequest` — required: **url**, **eventTypes** (array of event **name** strings from [List event types](#list-event-types)), **description**, **expiresAtUtc**. Optional: **notificationEmails** (comma-separated or deployment-specific format for alert recipients).

**Response:** **201 Created** — `CreateSubscriptionResponse` includes **id**, **url**, **eventTypes**, **signingSecret**, **expiresAtUtc**, **notificationEmails**, **etag**, **isActive**, **partnerAppId**, etc. Store **signingSecret** securely; it is used to verify inbound webhook requests (also returned only at **rotate-secret** thereafter).

#### Search subscriptions

**Endpoint:** `GET /hub/v1/webhooks/subscriptions/search` (`listSubscriptionsQuery`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`

**Query (typical):** `limit`, `next`, `sortBy`, `sortOrder` (per OpenAPI).

#### Get one subscription

**Endpoint:** `GET /hub/v1/webhooks/subscriptions/{id}/get` (`getSubscriptionQuery`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`; optional `If-None-Match`

#### Search subscriptions for an event type

**Endpoint:** `GET /hub/v1/webhooks/event-types/{name}/subscriptions/search` (`listSubscriptionsForEventType`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`

Path **{name}** is the event type **name** (e.g. `order.created`).

#### Update a subscription

**Endpoint:** `PATCH /hub/v1/webhooks/subscriptions/{id}/update` (`updateSubscriptionCommand`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`, `Content-Type: application/json`

**Body:** JSON body per the OpenAPI **PATCH** operation (the bundled file may reference a placeholder schema name; your generated client maps it to the subscription update type). May include **notificationEmails** when updating alert recipients. **200** returns **UpdateSubscriptionResponse** (`id`, `url`, `eventTypes`, **notificationEmails**, **expiresAtUtc**, **etag**, etc.—no **signingSecret**). Responses may include **412** / **409** for concurrency or conflict—follow **etag** / conditional request rules in the spec for your client.

#### Disable a subscription

**Endpoint:** `POST /hub/v1/webhooks/subscriptions/{id}/disable` (`disableSubscriptionCommand`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`

**Response:** **204 No Content** — stops deliveries for that subscription (per API semantics; confirm behavior with your deployment).

#### Test delivery

**Endpoint:** `POST /hub/v1/webhooks/subscriptions/{id}/test`  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`

Triggers a test delivery to the subscription URL. **Response:** `202 Accepted`. Use after [creating](#create-a-subscription) (or otherwise obtaining) a subscription to confirm connectivity before production traffic.

#### Rotate signing secret

**Endpoint:** `POST /hub/v1/webhooks/subscriptions/{id}/rotate-secret`  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`

Returns a new secret (response shape per `RotateSecretResponse` in the OpenAPI spec). Update your verifier to accept **both** old and new secrets during rotation, then retire the old secret.

### Deliveries

#### List deliveries for a subscription

**Endpoint:** `GET /hub/v1/webhooks/subscriptions/{id}/deliveries`  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`

**Query (typical):** `limit`, `next`, `status`, `from`, `to`, `sortBy`, `sortOrder` (per OpenAPI).

**Response**

```text
HTTP 200 — paged list of DeliveryResponse (per spec). Use for observability and debugging.
```

#### Get one delivery

**Endpoint:** `GET /hub/v1/webhooks/deliveries/{id}/get` (`getDeliveryQuery`)  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>` (per spec)

Use this to inspect payload metadata, status, and errors for a single delivery attempt.

#### Retry a failed delivery

**Endpoint:** `POST /hub/v1/webhooks/deliveries/{id}/retry`  
**Headers:** `Authorization: Bearer <token>`, `X-Tenant-Id: <tenantId>`

**Response:** `202 Accepted` — Hub queues a retry. Combine with the deliveries list to recover from transient 5xx responses on your server.

## 9. API Reference

Subsections follow the service path prefix on the unified API base URL: **/identity/v1**, **/catalog/v1**, **/screening/v1**, **/consult/v1**, **/clinical/v1**, **/commerce/v1**, **/hub/v1**.

### 1. Identity (`/identity/v1`)

| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Get tenant + partner app     | GET    | `/identity/v1/tenants/me`                  | **MyTenantsResponse** — `partnerApp.id`, **tenants[]**, **tenantProviders[]**, **partner** ([MyTenantsPartnerResponse](#mytenantspartnerresponse)).                                                                                                                    |
| 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.                                                                                          |

### 2. Catalog (`/catalog/v1`)

| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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`.        |

### 3. Screening (`/screening/v1`)

| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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/create`            | Body: CreateSessionRequest; headers: X-Tenant-Id. `operationId`: createSession.                                                                                  |
| 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 SubmitSessionResponse.                                                                   |
| Update screening session     | POST   | `/screening/v1/sessions/{id}/update`       | Body: UpdateSessionRequest (`productVariantId`, `expiresAtUtc`). `updateSession`. Change variant/expiry on in-progress session.                                  |
| Record session payment       | POST   | `/screening/v1/sessions/{id}/record-payment` | Body: RecordPaymentRequest (`paymentIntentId`, `amountCents`). `recordPayment`. Sets **Authorized** payment on session.                                          |
| Update payment status        | POST   | `/screening/v1/sessions/{id}/update-payment-status` | Body: UpdatePaymentStatusRequest (`status`: Captured, Failed, Refunded). `updatePaymentStatus`.                                                          |
| Cancel screening session     | POST   | `/screening/v1/sessions/{id}/cancel`      | Body: CancelSessionRequest (`reason`). **200** — SessionResponse. `cancelSession`.                                                                              |

### 4. Consult (`/consult/v1`)

| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Search consults              | GET    | `/consult/v1/consults/search`              | Query: `patientId`, `practitionerId`, `screeningSessionId`, `status`, `selectedProductId`, `selectedProductVariantId`, `limit`, `next`, etc. **X-Tenant-Id** required. `searchConsults`. |
| Create consult               | POST   | `/consult/v1/consults/create`              | Body: CreateConsultRequest. **200** — CreateConsultResponse. `createConsult`. Consult **write** scope.                                                          |
| Get consult                  | GET    | `/consult/v1/consults/{id}/get`            | **GetConsultResponse**. `getConsult`. Optional **If-None-Match**. **X-Tenant-Id** required.                                                                       |
| Search consult messages      | GET    | `/consult/v1/consults/{id}/messages/search` | Paged **ConsultMessageResponse**. `searchConsultMessages`.                                                                                                      |
| Add consult participant      | POST   | `/consult/v1/consults/{id}/participants/add` | Body: **AddParticipantRequest** (`practitionerId`, `displayName`). **201** — **AddParticipantResponse**. `addConsultParticipant`. **X-Tenant-Id** required.   |
| Post consult message         | POST   | `/consult/v1/consults/{id}/messages/post`  | Body: PostMessageRequest (`content`, `participantId`, `isMedia`). `postConsultMessage`. **participantId** = consult participant row id. **X-Tenant-Id** required. |
| Update consult message delivery | POST | `/consult/v1/consults/{id}/messages/{messageId}/update-delivery` | Body: UpdateMessageDeliveryRequest (`deliveredUtc`, `readUtc`). **200** — UpdateMessageDeliveryResponse. `updateConsultMessageDelivery`. **X-Tenant-Id** required. |
| Post CS message (consult)    | POST   | `/consult/v1/consults/{id}/cs-messages`    | Body: PostCsMessageRequest. `postCsMessage`. **X-Tenant-Id** required.                                                                                           |
| Close consult                | POST   | `/consult/v1/consults/{id}/close`          | Path consult **id**; no body. **200** — **CloseConsultResponse**. `closeConsult`. Tenant/provider command (not Beluga webhook).                                   |
| Cancel consult               | POST   | `/consult/v1/consults/{id}/cancel`         | Path consult **id**; no body. **200** — **CancelConsultResponse**. `cancelConsult`. Tenant/provider command (not Beluga webhook).                                 |

### 5. Clinical (`/clinical/v1`)

| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Search prescriptions         | GET    | `/clinical/v1/prescriptions/search`        | Query: `id`, `patientId`, `patientEmail`, `limit`, `next`, etc. Requires X-Tenant-Id.                                                                            |
| Create prescriptions         | POST   | `/clinical/v1/prescriptions/create`        | Body: CreatePrescriptionRequest (`consultId`, `medsPrescribed`, `pharmacyId`, …). **201** — array of PrescriptionResponse. `createPrescriptionCommand`. Requires clinical **write** scope. |
| Validate prescription        | GET    | `/clinical/v1/prescriptions/{id}/validate` | Returns ValidatePrescriptionResponse (`isValid`). `validatePrescription`.                                                                                       |
| Update prescription          | PATCH  | `/clinical/v1/prescriptions/{id}/update`   | Body: UpdatePrescriptionCommandRequest. **200** — PrescriptionResponse. `updatePrescriptionCommand`.                                                             |
| Cancel prescription          | POST   | `/clinical/v1/prescriptions/{id}/cancel`   | Body: CancelPrescriptionRequest. **200** — PrescriptionResponse. `cancelPrescription`.                                                                          |

### 6. Commerce (`/commerce/v1`)

| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Submit payment               | POST   | `/commerce/v1/payments/submit`             | Body: SubmitPaymentRequest (`entityName`, `entityId`, `orderId`, `provider`, `intentId`, `status`, `amount`, `currencyISO`). **200** — SubmitPaymentResponse. `submitPayment`. |
| 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}/get`             | Full order details. `getOrderQuery`. Optional `If-None-Match`. **X-Tenant-Id** required.                                                                        |
| Link orders                  | POST   | `/commerce/v1/orders/link`                 | Body: LinkOrdersRequest (`masterOrderId`, `childOrderIds`). **200** — LinkOrdersResponse. `linkOrders`.                                                         |
| Update order shipping        | POST   | `/commerce/v1/orders/{id}/update-shipping` | Body: UpdateOrderShippingRequest. **200** — UpdateOrderShippingResponse. `updateOrderShipping`.                                                                |
| Cancel order                 | POST   | `/commerce/v1/orders/{id}/cancel`          | Body: CancelOrderRequest. **200** — CancelOrderResponse. `cancelOrder`.                                                                                         |

### 7. Hub (`/hub/v1`)

| Operation                    | Method | Path                                       | Notes                                                                                                                                                            |
| ---------------------------- | ------ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| List webhook event types     | GET    | `/hub/v1/webhooks/event-types/search`      | Hub **read** scope. `listEventTypesQuery`. No **X-Tenant-Id** in bundled spec.                                                                                    |
| Create webhook subscription  | POST   | `/hub/v1/webhooks/subscriptions/create`   | Body: CreateSubscriptionRequest (`url`, `eventTypes`, `description`, `expiresAtUtc`; optional **notificationEmails**). **201** — CreateSubscriptionResponse (includes **signingSecret**, **notificationEmails**). `createSubscriptionCommand`. |
| Search webhook subscriptions | GET    | `/hub/v1/webhooks/subscriptions/search`    | Query: `limit`, `next`, `sortBy`, `sortOrder`. **X-Tenant-Id** required. `listSubscriptionsQuery`.                                                               |
| Get webhook subscription     | GET    | `/hub/v1/webhooks/subscriptions/{id}/get`   | Optional `If-None-Match`. **X-Tenant-Id** required. `getSubscriptionQuery`.                                                                                         |
| Update webhook subscription  | PATCH  | `/hub/v1/webhooks/subscriptions/{id}/update` | Body per OpenAPI **PATCH**. **200** — UpdateSubscriptionResponse. `updateSubscriptionCommand`.                                                               |
| Disable webhook subscription | POST   | `/hub/v1/webhooks/subscriptions/{id}/disable` | **204**. `disableSubscriptionCommand`. **X-Tenant-Id** required.                                                                                         |
| Search subs by event type    | GET    | `/hub/v1/webhooks/event-types/{name}/subscriptions/search` | Path `name` = event type id (e.g. `order.created`). **X-Tenant-Id** required. `listSubscriptionsForEventType`.                                      |
| Test webhook subscription    | POST   | `/hub/v1/webhooks/subscriptions/{id}/test` | Triggers test delivery (**202**). **X-Tenant-Id** required.                                                                                                       |
| List deliveries (subscription) | GET | `/hub/v1/webhooks/subscriptions/{id}/deliveries` | Paged delivery history; query `status`, `from`, `to`, etc.                                                                                                   |
| Get delivery by id           | GET    | `/hub/v1/webhooks/deliveries/{id}/get`     | Single delivery record. `getDeliveryQuery`.                                                                                                                      |
| Retry delivery               | POST   | `/hub/v1/webhooks/deliveries/{id}/retry`   | Queues retry (**202**). **X-Tenant-Id** required.                                                                                                                 |
| Rotate subscription secret   | POST   | `/hub/v1/webhooks/subscriptions/{id}/rotate-secret` | Returns new signing secret. **X-Tenant-Id** required.                                                                                                      |

All non-catalog endpoints require **Authorization: Bearer **** and ****X-Tenant-Id** unless the operation explicitly omits tenant scope (e.g. `GET /hub/v1/webhooks/event-types/search` per bundled spec).


## 10. Object reference

Types are grouped by the same service prefixes as [§9 API Reference](#9-api-reference): **Shared**, **Identity** (`/identity/v1`), **Catalog** (`/catalog/v1`), **Screening** (`/screening/v1`), **Consult** (`/consult/v1`), **Clinical** (`/clinical/v1`), **Commerce** (`/commerce/v1`), **Hub** (`/hub/v1`). **Shared types** are reused across services. For fields omitted below, see `CareAtlas-Unified-API-oas3-v0.1.json`.

### Shared types

### Address

Used on commerce **OrderImportRequest**, practitioner onboard, and (as **AddressInfo**) on **PatientResolveRequest**. Patient **update** uses **PatientAddressInfo** (same required fields; optional **label**).


| 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).      |
| label        | string | Optional on **AddressInfo**, **PatientAddressInfo**, and **PractitionerAddressInfo** (e.g. `"Shipping Address"` on resolve-by-email). |


---

### Identity (`/identity/v1`)

#### MyTenantsResponse

**200** from **GET /identity/v1/tenants/me**.


| Field       | Type                                      | Description                          |
| ----------- | ----------------------------------------- | ------------------------------------ |
| partnerApp  | [MyTenantsPartnerAppResponse](#mytenantspartnerappresponse) \| object | Partner application context; may be absent. |

#### MyTenantsPartnerAppResponse


| Field            | Type                                              | Description                                                                 |
| ---------------- | ------------------------------------------------- | --------------------------------------------------------------------------- |
| id               | string (UUID)                                     | Partner application id — use as **source.partnerAppId** on order import.   |
| appId            | string                                            | Application id (e.g. Cognito app client / portal key).                      |
| name             | string                                            | Display name of the partner application.                                    |
| partner          | [MyTenantsPartnerResponse](#mytenantspartnerresponse) | Owning partner.                                                             |
| tenants          | [MyTenantsTenantResponse](#mytenantstenantresponse)[] | Clinic/provider tenants linked to this app.                              |
| tenantProviders  | [MyTenantsTenantResponse](#mytenantstenantresponse)[] | Provider/pharmacy tenants (e.g. **role** `Pharmacy`) for routing and UI. |
| attributes       | [MyTenantsAttributeResponse](#mytenantsattributeresponse)[] | Partner-app-level attributes (see OpenAPI).                    |

#### MyTenantsPartnerResponse

Nested under **MyTenantsPartnerAppResponse.partner**.


| Field | Type   | Description        |
| ----- | ------ | ------------------ |
| id    | string (UUID) | Partner id.  |
| name  | string | Partner name.      |
| role  | string | Partner role name. |

#### MyTenantsTenantResponse

Each element of **tenants** or **tenantProviders**.


| Field       | Type                                              | Description                                      |
| ----------- | ------------------------------------------------- | ------------------------------------------------ |
| tenantId    | string (UUID)                                     | Use as **X-Tenant-Id** when this row is selected. |
| name        | string                                            | Tenant display name.                             |
| role        | string                                            | Role name (e.g. `provider`, `Pharmacy`).         |
| attributes  | [MyTenantsAttributeResponse](#mytenantsattributeresponse)[] | Attributes on the tenant row (see OpenAPI). |

#### MyTenantsAttributeResponse

Name/value pair used on **MyTenantsTenantResponse** and **MyTenantsPartnerAppResponse**.


| Field | Type   | Description   |
| ----- | ------ | ------------- |
| name  | string | Attribute name. |
| value | string | Attribute value. |

---

### PatientResponse

Returned by patient search, resolve, and get-patient flows. Tenant context is **`X-Tenant-Id`** on the request, not a field on this object.


| Field          | Type                              | Description                                        |
| -------------- | --------------------------------- | -------------------------------------------------- |
| id             | string (UUID)                     | `customer.patientId`.                              |
| account        | PatientAccountResponse            | Nested account; use **`account.id`** as `customer.accountId`. |
| firstName      | string                            |                                                    |
| lastName       | string                            |                                                    |
| dateOfBirth    | string                            |                                                    |
| height         | number                            | Height in inches (Identity column).                |
| weight         | number                            | Weight in lbs.                                     |
| gender         | string                            | **`"Male"`** or **`"Female"`** (per deployment).   |
| email          | string                            | Optional for billing.                              |
| phoneNumber    | string                            |                                                    |
| address        | [Address](#address)               | Required for shipping/billing if not from payment. |
| etag           | string                            | Concurrency token when supported.                  |

**Removed from unified `PatientResponse`:** top-level **`tenantId`** and **`accountId`** — use **`account.id`** instead of **`accountId`**.


---

### 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`.

---

### 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                       |                                                              |


---

### 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.                                                   |
| attributes                 | array              | Tenant role attribute values (e.g. address, contact details). |


Use with address/attribute data to build [ClinicInfo](#clinicinfo) for order import.

---

### Catalog (`/catalog/v1`)

Search and product payloads use **GetProductResponse** (informally called **ProductResponse** in this guide). Variants carry **productVariantId** and **uomId** for prescriptions and order lines.

#### GetProductResponse

Returned from **GET /catalog/v1/products/search** and **GET /catalog/v1/products/{id}/find**.


| Field       | Type                                  | Description                                                        |
| ----------- | ------------------------------------- | ------------------------------------------------------------------ |
| id          | string (UUID)                         | **productId**.                                                      |
| name, sku, brand, description | strings                      | Display and catalog metadata.                                      |
| variants    | [ProductVariantInfo](#productvariantinfo)[] | Use for variant id and **uomId**.                               |
| catalog     | object (`ProductCatalogInfo`)        | Nested catalog id/name/supplier.                                   |
| etag        | string                                | For conditional requests.                                          |

#### ProductVariantInfo

Each variant maps to a prescribe-able SKU and order line.


| Field       | Type                      | Description                                              |
| ----------- | ------------------------- | -------------------------------------------------------- |
| id          | string (UUID)             | **productVariantId** for prescriptions and **LineInfo**. |
| uomId       | string (UUID)             | Required on commerce **LineInfo** when importing orders. |
| name        | string                    | e.g. strength label.                                      |
| variantSku  | string                    | SKU.                                                      |
| attributes  | array (`AttributeResponse`) | Tenant-defined attributes.                             |
| etag        | string                    |                                                          |

---

### Screening (`/screening/v1`)

#### CreateSessionRequest

Body for **POST /screening/v1/sessions/create**.


| Field              | Type          | Description                         |
| ------------------ | ------------- | ----------------------------------- |
| channel            | string        | e.g. `web`.                         |
| patientId          | string (UUID) | From Identity.                      |
| questionnaireId    | string (UUID) | From active questionnaires list. |
| productId          | string (UUID) | Catalog product.                  |
| productVariantId   | string (UUID) | Catalog variant.                  |

#### CreateSessionResponse

**201** response from session create; **id** is the screening session id for **/next**, **/answer**, **/submit**, etc.


| Field              | Type          | Description           |
| ------------------ | ------------- | --------------------- |
| id                 | string (UUID) | Session id.           |
| tenantId           | string (UUID) |                       |
| patientId          | string (UUID) |                       |
| questionnaireId    | string (UUID) |                       |
| productId          | string (UUID) |                       |
| productVariantId   | string (UUID) |                       |
| startedAt          | date-time     |                       |
| expiresAtUtc       | date-time     |                       |
| etag               | string        |                       |

#### ActiveQuestionnaireResponse

Rows from **GET /screening/v1/questionnaires/active** (each element describes an available questionnaire).


| Field           | Type          | Description                                  |
| --------------- | ------------- | -------------------------------------------- |
| id              | string (UUID) | Use as **questionnaireId** in **CreateSessionRequest**. |
| name, version   | string        |                                              |
| treatmentId     | string (UUID) | Care-path binding.                         |
| statusId        | string (UUID) |                                              |
| effectiveFromUtc, effectiveToUtc | date-time | Validity window.              |

#### SessionSubmitRequest

Body for **POST /screening/v1/sessions/{id}/submit**.


| Field         | Type    | Description        |
| ------------- | ------- | ------------------ |
| validateOnly  | boolean | Optional dry-run.  |

#### SubmitSessionResponse

**200** body after submit; use **recommendedProductVariants** when your clinical flow suggests variants.


| Field                       | Type          | Description                                |
| --------------------------- | ------------- | ------------------------------------------ |
| status                      | string        | Session outcome.                           |
| sessionId                   | string (UUID) |                                            |
| questionnaireId             | string (UUID) |                                            |
| initiallySelectedProductId  | string (UUID) |                                            |
| initiallySelectedProductVariantId | string (UUID) |                                    |
| completedAt                 | date-time     |                                            |
| recommendedProductVariants    | array         | `RecommendedProductVariantInfo` per spec.  |

#### AnswerBundleRequest

Body for **POST /screening/v1/sessions/{id}/answer**.


| Field    | Type             | Description                                      |
| -------- | ---------------- | ------------------------------------------------ |
| answers  | AnswerItemInfo[] | Each item: **linkId**, **answer**, optional **evidenceRef**. |

---

### Consult (`/consult/v1`)

#### CreateConsultRequest

Body for **POST /consult/v1/consults/create** (`createConsult`).


| Field                  | Type          | Description                                        |
| ---------------------- | ------------- | -------------------------------------------------- |
| patientId              | string (UUID) | From Identity.                                     |
| practitionerId       | string (UUID) | From Identity practitioner search/onboard.         |
| topic                  | string        | Consult topic label.                               |
| channel                | string        | e.g. `web`.                                        |
| screeningSessionId     | string (UUID) | From screening after submit ([§4](#4-4-patient-screening)). |
| selectedProductId      | string (UUID) | Catalog **productId**.                             |
| selectedProductVariantId | string (UUID) | Catalog **productVariantId**.                      |

#### CreateConsultResponse

**200** from create consult; use **id** as consult id on GET/messages routes.


| Field           | Type          | Description        |
| --------------- | ------------- | ------------------ |
| id              | string (UUID) | Consult id.        |
| providerTenantId | string (UUID) |                    |
| createdAtUtc    | date-time     |                    |
| updatedAtUtc    | date-time     |                    |

#### GetConsultResponse

Returned by **GET /consult/v1/consults/{id}/get**.


| Field                   | Type          | Description                          |
| ----------------------- | ------------- | ------------------------------------ |
| id                      | string (UUID) |                                      |
| status                  | string        | Workflow status.                     |
| tenantId                | string (UUID) |                                      |
| patientId               | string (UUID) |                                      |
| practitionerId          | string (UUID) |                                      |
| screeningSessionId      | string (UUID) |                                      |
| selectedProductId       | string (UUID) |                                      |
| selectedProductVariantId  | string (UUID) |                                      |
| channel                 | string        |                                      |
| startedUtc, endedUtc    | date-time     | Lifecycle.                           |
| conversationRef         | string        | External conversation link when set. |
| participants            | array         | **ConsultParticipantDto** (see below). |
| chats                   | array         | `ConsultChatDto` per spec.           |
| etag                    | string        |                                      |

#### ConsultParticipantDto

Each item in **GetConsultResponse.participants** (and **AddParticipantResponse** after **POST …/participants/add**).


| Field            | Type          | Description                                                                 |
| ---------------- | ------------- | --------------------------------------------------------------------------- |
| id               | string (UUID) | Consult participant **row** id; use as **PostMessageRequest.participantId**. |
| tenantId         | string (UUID) |                                                                             |
| patientId        | string (UUID) | Identity patient on the consult (from the consult record).                  |
| practitionerId   | string (UUID) | Identity practitioner registered on this participant row.                   |
| displayName      | string        | Display label for the participant.                                          |
| joinedUtc        | date-time     |                                                                             |
| leftUtc          | date-time     | Optional. When the participant left the thread.                             |
| etag             | string        |                                                                             |

The unified partner API does **not** expose **roleId** on this type. Role-based participant management (e.g. admin picklists) uses separate admin CRUD routes under `/consult/v1/consults/{id}/participants`, not **participants/add**.

#### AddParticipantRequest

Body for **POST /consult/v1/consults/{id}/participants/add**.


| Field            | Type          | Description                                                          |
| ---------------- | ------------- | -------------------------------------------------------------------- |
| practitionerId   | string (UUID) | **Required.** Practitioner to add to the consult chat.               |
| displayName      | string        | **Required.** Shown name for this participant.                       |

The consult’s **patientId** is assigned on the created row by the API; it is not sent in the request.

#### AddParticipantResponse

**201** from **POST /consult/v1/consults/{id}/participants/add**.


| Field            | Type          | Description                    |
| ---------------- | ------------- | ------------------------------ |
| id               | string (UUID) | New participant row id.        |
| consultId        | string (UUID) |                                |
| patientId        | string (UUID) | Consult patient.               |
| practitionerId   | string (UUID) | Practitioner from the request. |
| displayName      | string        |                                |
| joinedUtc        | date-time     |                                |
| createdAtUtc     | date-time     |                                |
| updatedAtUtc     | date-time     |                                |

#### CloseConsultResponse

**200** from **POST /consult/v1/consults/{id}/close**.


| Field        | Type          | Description     |
| ------------ | ------------- | --------------- |
| id           | string (UUID) | Consult id.     |
| endedUtc     | date-time     | When closed.    |
| createdAtUtc | date-time     |                 |
| updatedAtUtc | date-time     |                 |

#### CancelConsultResponse

**200** from **POST /consult/v1/consults/{id}/cancel**.


| Field          | Type          | Description        |
| -------------- | ------------- | ------------------ |
| id             | string (UUID) | Consult id.        |
| canceledAtUtc  | date-time     |                    |
| canceledBy     | string        | Who canceled.      |
| createdAtUtc   | date-time     |                    |
| updatedAtUtc   | date-time     |                    |

#### PostMessageRequest

Body for **POST /consult/v1/consults/{id}/messages/post**.


| Field          | Type    | Description                                              |
| -------------- | ------- | -------------------------------------------------------- |
| content        | string  | **Required.** Message text.                              |
| participantId  | string (UUID) | Optional. **ConsultParticipantDto.id** (participant row), not identity patient/practitioner uuid. Omit when no participant row exists; the API still accepts the message. |
| isMedia        | boolean | Optional.                                                |

#### UpdateMessageDeliveryRequest

Body for **POST /consult/v1/consults/{id}/messages/{messageId}/update-delivery**.


| Field         | Type      | Description                                      |
| ------------- | --------- | ------------------------------------------------ |
| deliveredUtc  | date-time | When the message was delivered to the recipient. |
| readUtc       | date-time | When the message was read.                       |

Path **{messageId}** is the consult message id (from **ConsultMessageResponse** / search messages).

#### UpdateMessageDeliveryResponse

**200** from **POST /consult/v1/consults/{id}/messages/{messageId}/update-delivery**.


| Field         | Type          | Description        |
| ------------- | ------------- | ------------------ |
| id            | string (UUID) | Message id.        |
| consultId     | string (UUID) |                    |
| tenantId      | string (UUID) |                    |
| deliveredUtc  | date-time     |                    |
| readUtc       | date-time     |                    |
| etag          | string        |                    |

---

### Clinical (`/clinical/v1`)

### ValidatePrescriptionResponse

Returned by `GET /clinical/v1/prescriptions/{id}/validate`.


| Field    | Type    | Description                      |
| -------- | ------- | -------------------------------- |
| isValid  | boolean | Whether the prescription passes validation per the API. |


---

### CreatePrescriptionCommandRequest

Request body for `POST /clinical/v1/prescriptions/create` (OpenAPI: **CreatePrescriptionRequest**).


| Field                 | Type                                              | Notes                                                                 |
| --------------------- | ------------------------------------------------- | --------------------------------------------------------------------- |
| consultId             | string (UUID)                                   | Consult id from [§5 Step 2](#step-2-create-a-consult). Required for provider prescribing flows. |
| medsPrescribed        | [MedPrescribedInternal](#medprescribedinternal)[] | **Required.** One row per prescribed line.                        |
| pharmacyId            | string                                            | **Required.** Pharmacy identifier (UUID in typical deployments).     |
| pharmacyNote          | string                                            | Optional.                                                             |
| practitionerId        | string (UUID)                                   | Optional in schema; usually set from [§6 Prerequisites — prescriptions](#prerequisites-prescriptions) ([Ensure Practitioner exists](#ensure-practitioner-exists)). |
| practitionerFirstName | string                                            | Optional.                                                             |
| practitionerLastName  | string                                            | Optional.                                                             |
| practitionerNpi       | string                                            | Optional.                                                             |
| practitionerPhone     | string                                            | Optional.                                                             |
| practitionerAddress   | string                                            | Optional.                                                             |
| practitionerCity      | string                                            | Optional.                                                             |
| practitionerState     | string                                            | Optional.                                                             |
| practitionerZip       | string                                            | Optional.                                                             |
| approvedAtUtc         | string (date-time)                              | Optional.                                                             |
| approvedBy            | string                                            | Optional.                                                             |
| paymentAmount         | object (double format per spec)                   | Optional.                                                             |
| paymentLink           | string                                            | Optional.                                                             |
| paymentLinkSentAt     | string (date-time)                              | Optional.                                                             |
| comments              | string                                            | Optional.                                                             |

---

### SubmitPaymentRequest

Body for **POST /commerce/v1/payments/submit** (`submitPayment`). Records a payment against an order entity in Commerce.


| Field       | Type   | Description                                      |
| ----------- | ------ | ------------------------------------------------ |
| entityName  | string | Entity type label (per your commerce integration). |
| entityId    | string | Entity UUID.                                     |
| orderId     | string | Order UUID.                                      |
| provider    | string | Payment provider (e.g. `stripe`).                |
| intentId    | string | Provider payment intent id.                      |
| status      | string | Payment status string.                           |
| amount      | number | Amount (OpenAPI may export as object/double).    |
| currencyISO | string | ISO currency code (e.g. `USD`).                  |

#### SubmitPaymentResponse

**200** from submit payment — includes **id**, **tenantId**, **intentId**, **status**, **amount**, **etag**, and related fields mirroring the request.

---

### MedPrescribedInternal

One element of **medsPrescribed** in [CreatePrescriptionCommandRequest](#createprescriptioncommandrequest).


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


---

### UpdatePrescriptionCommandRequest

Request body for `PATCH /clinical/v1/prescriptions/{id}/update`. All fields are optional in the bundled schema.


| Field             | Type                    | Description        |
| ----------------- | ----------------------- | ------------------ |
| approvedAtUtc     | string (date-time)      | Optional.          |
| approvedBy        | string                  | Optional.          |
| pharmacyNote      | string                  | Optional.          |
| comments          | string                  | Optional.          |
| paymentAmount     | object (double format) | Optional.          |
| paymentLink       | string                  | Optional.          |
| paymentLinkSentAt | string (date-time)      | Optional.          |


---

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


---

### Commerce (`/commerce/v1`)

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

---

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


---

### 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              |                              |


---

### 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"`).

---

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

---

### Hub (`/hub/v1`)

Webhook subscription and delivery types for [§8 Working with Webhooks](#8-8-working-with-webhooks). Requests use **Hub** Cognito scopes (`api://haas.hub/haas.api.read` / `…write`).

#### CreateSubscriptionRequest

Body for **POST /hub/v1/webhooks/subscriptions/create**.


| Field               | Type       | Required | Description                          |
| ------------------- | ---------- | -------- | ------------------------------------ |
| url                 | string     | yes      | HTTPS endpoint for deliveries.       |
| eventTypes          | string[]   | yes      | e.g. `order.created` (per event catalog). |
| description         | string     | yes      | Human-readable label.                |
| expiresAtUtc        | date-time  | yes      | Subscription expiry (ISO-8601 UTC).  |
| notificationEmails  | string     | no       | Optional alert recipients for subscription events. |

#### CreateSubscriptionResponse

**201** — treat **signingSecret** as sensitive; persist it to verify signatures on inbound webhooks.


| Field               | Type          | Description                    |
| ------------------- | ------------- | ------------------------------ |
| id                  | string (UUID) | Subscription id.             |
| partnerAppId        | string (UUID) |                                |
| url                 | string        | Callback URL.                  |
| eventTypes          | string[]      |                                |
| isActive            | boolean       |                                |
| description         | string        |                                |
| expiresAtUtc        | date-time     |                                |
| notificationEmails  | string        | Alert recipients when configured. |
| etag                | string        |                                |
| signingSecret       | string        | HMAC / verification secret.    |

#### UpdateSubscriptionResponse

**200** from **PATCH /hub/v1/webhooks/subscriptions/{id}/update** — same fields as create response except **signingSecret** (use [rotate-secret](#rotate-signing-secret) to obtain a new secret).


| Field               | Type          | Description                    |
| ------------------- | ------------- | ------------------------------ |
| id                  | string (UUID) |                                |
| partnerAppId        | string (UUID) |                                |
| url                 | string        |                                |
| eventTypes          | string[]      |                                |
| isActive            | boolean       |                                |
| description         | string        |                                |
| expiresAtUtc        | date-time     |                                |
| notificationEmails  | string        |                                |
| etag                | string        |                                |

#### GetDeliveryResponse

Returned by **GET /hub/v1/webhooks/deliveries/{id}/get** for observability and debugging.


| Field           | Type          | Description                                      |
| --------------- | ------------- | ------------------------------------------------ |
| id              | string (UUID) | Delivery attempt id.                             |
| subscriptionId  | string (UUID) |                                                  |
| sourceMessageId | string (UUID) | Hub message id.                                  |
| eventType       | string        |                                                  |
| status          | string        | Delivery pipeline status.                        |
| statusCode      | integer       | HTTP status from your endpoint (if applicable).   |
| error           | string        | Failure detail.                                  |
| attempt         | integer       | Retry count.                                     |
| nextRetryAtUtc  | date-time     | Scheduled retry.                                 |
| traceId         | string        | Correlate with Hub logs.                         |
| etag            | string        |                                                  |

## 11. 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 [Build OrderImportRequest and call Import Order](#build-orderimportrequest-and-call-import-order) and the sample request there.