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