@healthcloudai/hc-safe-cdx 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,16 +1,10 @@
1
1
  # Healthcheck Safe CDX Connector
2
2
 
3
- Safe CDX connector provides a thin TypeScript wrapper around Safe CDX API routes.
3
+ Safe CDX supports the diagnostic test workflow performed by an authenticated patient. It provides the SDK methods used to retrieve available test profiles, resolve a selected test by GTIN, prepare and upload test images, process test results, and complete the test flow.
4
4
 
5
- The connector follows the same shared-client pattern used by other Health Cloud connector modules:
5
+ `HCSafeCDXClient` uses the authenticated patient context provided by `HCLoginClient`. The client derives its own Safe CDX service URL from the configured environment, sends authenticated requests to Safe CDX routes, and wraps request payloads internally where the API expects the standard `Data` envelope.
6
6
 
7
- - `HttpClient` handles HTTP transport.
8
- - `HCLoginClient` provides the authenticated request headers.
9
- - `HCSafeCDXClient` builds Safe CDX URLs, wraps request payloads, and exposes one method per Safe CDX route.
10
-
11
- Safe CDX has its own API host. The connector uses `HCLoginClient` for authentication only; it does not use `loginClient.getBaseUrl()` for Safe CDX routes.
12
-
13
- Image upload is handled separately because the upload target is a pre-signed S3 URL, not a Safe CDX API route.
7
+ Image upload is handled separately because the upload target is a pre-signed storage URL rather than a Safe CDX API route.
14
8
 
15
9
  ## Installation
16
10
 
@@ -26,588 +20,1642 @@ import { HCLoginClient } from "@healthcloudai/hc-login-connector";
26
20
  import { HCSafeCDXClient } from "@healthcloudai/hc-safe-cdx";
27
21
  ```
28
22
 
29
- ## Basic Setup
23
+ ## Setup
30
24
 
31
- ```ts
32
- const http = new FetchClient();
25
+ Create the shared HTTP client, authenticate the patient through `HCLoginClient`, and then create the Safe CDX client with the authenticated login instance.
33
26
 
34
- const loginClient = new HCLoginClient(http);
27
+ ```ts
28
+ const httpClient = new FetchClient();
29
+ const loginClient = new HCLoginClient(httpClient);
35
30
 
36
- // Configure and authenticate the login client first.
37
- // Exact login/configuration calls depend on the login connector setup used by the app.
31
+ loginClient.configure("healthcheck", "dev");
32
+ await loginClient.login(email, password);
38
33
 
39
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
40
- environment: "dev",
41
- defaultLanguage: "en",
42
- defaultImageType: "jpg",
43
- });
34
+ const safeCdx = new HCSafeCDXClient(httpClient, loginClient);
44
35
  ```
45
36
 
46
- The connector does not perform login. The consuming application should authenticate through the Health Cloud login connector first. Safe CDX then reuses the authenticated headers from `loginClient.getAuthHeader()`.
37
+ The Safe CDX connector does not perform login and does not require a separate `setAccessToken(...)` step. Authenticated headers are read from the existing `HCLoginClient` instance.
47
38
 
48
- There is no `setAccessToken()` step in this version of the client.
39
+ ## Client Configuration
49
40
 
50
- ## Dependency Notes
41
+ ### Service URL
51
42
 
52
- Because `HCSafeCDXClient` accepts `HCLoginClient` in the constructor, `@healthcloudai/hc-login-connector` should be included as a package dependency.
43
+ Safe CDX uses its own service base URL and does not reuse `loginClient.getBaseUrl()`. The client reads the configured environment through `loginClient.getEnvironment()` and resolves the Safe CDX host internally.
53
44
 
54
- Example package configuration:
55
45
 
56
- ```json
57
- {
58
- "dependencies": {
59
- "@healthcloudai/hc-http": "^0.x.x",
60
- "@healthcloudai/hc-login-connector": "^0.x.x"
61
- }
46
+ ### Optional API Key
47
+
48
+ When an environment requires an API key header, configure it on the Safe CDX client:
49
+
50
+ ```ts
51
+ safeCdx.setApiKey("x-api-key", apiKeyValue);
52
+ ```
53
+
54
+ The configured API key header is added to Safe CDX API route requests together with the patient authentication headers.
55
+
56
+ ## Request Contract
57
+
58
+ SDK methods receive only the values needed by the consuming application. The consumer does not manually provide the backend `Data` wrapper, tenant context, patient identity values, or authentication headers.
59
+
60
+ For POST requests, `HCSafeCDXClient` constructs the API request envelope internally:
61
+
62
+ ```ts
63
+ interface APIRequest<T> {
64
+ Data: T;
62
65
  }
63
66
  ```
64
67
 
65
- In a workspace setup, use the same dependency versioning strategy already used by the other connector packages.
68
+ For example:
66
69
 
67
- Example:
70
+ ```ts
71
+ await safeCdx.submitAnswers({
72
+ UserTestResultId: userTestResultId,
73
+ Result: [
74
+ {
75
+ Analyte: "Purchase",
76
+ ReportedValue: "drug store",
77
+ StoredValue: "drug store",
78
+ Score: "0"
79
+ }
80
+ ]
81
+ });
82
+ ```
83
+
84
+ The client sends:
68
85
 
69
86
  ```json
70
87
  {
71
- "dependencies": {
72
- "@healthcloudai/hc-http": "workspace:*",
73
- "@healthcloudai/hc-login-connector": "workspace:*"
74
- }
88
+ "Data": {
89
+ "UserTestResultId": "<USER_TEST_RESULT_ID>",
90
+ "Result": [
91
+ {
92
+ "Analyte": "Purchase",
93
+ "ReportedValue": "drug store",
94
+ "StoredValue": "drug store",
95
+ "Score": "0"
96
+ }
97
+ ]
98
+ }
75
99
  }
76
100
  ```
77
101
 
78
- ## How the Client Works
102
+ ## Response Contract
79
103
 
80
- The client receives both the HTTP client and the login client:
104
+ Safe CDX methods return the response shape produced by their backend endpoint without unwrapping or transforming it.
81
105
 
82
- ```ts
83
- const http = new FetchClient();
84
- const loginClient = new HCLoginClient(http);
106
+ Most documented methods return the standard Health Cloud response envelope:
85
107
 
86
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
87
- environment: "dev",
88
- });
108
+ ```ts
109
+ interface APIResponse<T> {
110
+ Data: T | null;
111
+ IsOK: boolean;
112
+ ErrorMessage: string | null;
113
+ }
89
114
  ```
90
115
 
91
- The shared `HttpClient` is responsible for executing HTTP requests.
116
+ Several Safe CDX operations return a service response inside `APIResponse.Data`, or return that service response directly:
92
117
 
93
- The `HCLoginClient` is responsible for the authenticated request headers.
118
+ ```ts
119
+ interface SafeAPIResponse<T> {
120
+ success: boolean;
121
+ data: T | null;
122
+ message: string | null;
123
+ code: number;
124
+ }
125
+ ```
94
126
 
95
- The Safe CDX client is responsible for:
127
+ The following methods return `SafeAPIResponse<T>` directly, without the outer `APIResponse<T>` wrapper:
96
128
 
97
- - resolving the Safe CDX environment host
98
- - building Safe CDX URLs
99
- - adding auth headers from `HCLoginClient`
100
- - adding optional API key headers when configured
101
- - wrapping POST request payloads into `{ Data: payload }`
102
- - validating `ApiResponse` results where the backend returns `{ IsOK, Data, ErrorMessage }`
103
- - exposing one SDK method per Safe CDX route
129
+ ```ts
130
+ safeCdx.updateCvmlStatus(...)
131
+ safeCdx.getCvmlResults(...)
132
+ safeCdx.getResultDetails(...)
133
+ safeCdx.getImageCaptureUrl(...)
134
+ ```
104
135
 
105
- The Safe CDX client does not create its own internal `fetch` helpers for standard Safe CDX API routes.
136
+ `HCSafeCDXClient` does not replace an API-defined response with a different return value. Local errors may still be thrown before or outside a Safe CDX API response, such as invalid API key configuration or a failed direct image upload. Transport-level handling for unsuccessful HTTP responses is determined by the supplied `HttpClient`.
106
137
 
107
- ## Environment Configuration
138
+ ## Parameter Design
108
139
 
109
- The connector supports the following environments:
140
+ Public methods follow the documented patient workflow request shapes.
110
141
 
111
142
  ```ts
112
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
113
- environment: "dev",
114
- });
143
+ safeCdx.getTestProfileByGTIN(gtin)
144
+ safeCdx.getTestProfilesByAccount(includeRegisterTestDetails?)
145
+ safeCdx.createUploadUrl(userTestResultId, gtin, imageType?)
146
+ safeCdx.updateCvmlStatus(imageOfCaptureId, cvmlStatus)
147
+ safeCdx.getCvmlResults(imageOfCaptureId)
148
+ safeCdx.getPendingResults(excludeStatus?)
149
+ safeCdx.getLastResults(excludeStatus?)
150
+ safeCdx.getTestHistory(excludeStatus?)
151
+ safeCdx.getResultDetails(userTestResultId)
152
+ safeCdx.getResultPdf(userTestResultId)
153
+ safeCdx.getImageCaptureUrl(userTestResultId)
154
+ safeCdx.resumeFlow(userTestResultId, resumed?)
155
+ safeCdx.finalizeTest(userTestResultId)
115
156
  ```
116
157
 
117
- Available environments:
158
+
159
+ ## Typical Patient Workflow
118
160
 
119
161
  ```ts
120
- "dev" | "uat" | "prod"
121
- ```
162
+ const profiles = await safeCdx.getTestProfilesByAccount();
163
+
164
+ const profile = await safeCdx.getTestProfileByGTIN("850024942325");
165
+
166
+ if (!profile.IsOK || !profile.Data) {
167
+ throw new Error(profile.ErrorMessage ?? "Unable to resolve the test profile.");
168
+ }
169
+
170
+ const userTestResultId = profile.Data.ID;
171
+ const gtin = profile.Data.DiagnosticProfile?.TestInfo.gtin;
172
+
173
+ if (!userTestResultId || !gtin) {
174
+ throw new Error("The selected test profile does not include the required workflow identifiers.");
175
+ }
122
176
 
123
- Safe CDX environment host mapping is handled internally by the connector:
177
+ const upload = await safeCdx.createUploadUrl(userTestResultId, gtin);
178
+
179
+ if (!upload.IsOK || !upload.Data?.preSignedURL || !upload.Data.Metadata?.UploadId) {
180
+ throw new Error(upload.ErrorMessage ?? "Unable to prepare image upload.");
181
+ }
182
+
183
+ const preSignedURL = upload.Data.preSignedURL;
184
+ const imageOfCaptureId = upload.Data.Metadata.UploadId;
124
185
 
125
- ```txt
126
- dev -> dev-api-hcs.healthcloud-services.com
127
- uat -> uat-api-hcs.healthcloud-services.com
128
- prod -> api-hcs.healthcloud-services.com
186
+ await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
187
+ await safeCdx.updateCvmlStatus(imageOfCaptureId, "ImageProcessing");
188
+
189
+ const cvmlResults = await safeCdx.getCvmlResults(imageOfCaptureId);
190
+
191
+ await safeCdx.submitAnswers({
192
+ UserTestResultId: userTestResultId,
193
+ Result: [
194
+ {
195
+ Analyte: "Purchase",
196
+ ReportedValue: "drug store",
197
+ StoredValue: "drug store",
198
+ Score: "0"
199
+ }
200
+ ]
201
+ });
202
+
203
+ await safeCdx.finalizeTest(userTestResultId);
204
+
205
+ const details = await safeCdx.getResultDetails(userTestResultId);
206
+ const pdf = await safeCdx.getResultPdf(userTestResultId);
207
+ const imageCaptureUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
129
208
  ```
130
209
 
131
- These hosts are separate from the login connector base URL.
210
+ The application is responsible for preserving workflow values returned between steps, including `userTestResultId`, `gtin`, `preSignedURL`, and `imageOfCaptureId`.
132
211
 
133
- ## Authentication
212
+ ## Public Methods
213
+
214
+ ### `getTestProfilesByAccount(...)`
134
215
 
135
- Safe CDX API calls require authenticated Health Cloud headers.
216
+ Lists test profiles available to the authenticated patient account. The backend resolves the tenant from the authenticated patient context.
136
217
 
137
- The connector gets those headers from:
218
+ #### Signature
138
219
 
139
220
  ```ts
140
- loginClient.getAuthHeader()
221
+ safeCdx.getTestProfilesByAccount(
222
+ includeRegisterTestDetails?: boolean
223
+ ): Promise<APIResponse<GetTestProfilesByAccountData>>
141
224
  ```
142
225
 
143
- This means the login connector must be configured and authenticated before Safe CDX methods are called.
226
+ `GetTestProfilesByAccountData` is:
227
+
228
+ ```ts
229
+ type GetTestProfilesByAccountData =
230
+ SafeAPIResponse<TestProfileByAccountItem[]>;
231
+ ```
144
232
 
145
- Example flow:
233
+ #### Parameters
234
+
235
+ | Parameter | Type | Required | Description |
236
+ |---|---|---:|---|
237
+ | `includeRegisterTestDetails` | `boolean` | No | Includes registration-specific test details when `true`. Defaults to `true`. |
238
+
239
+ #### Usage
146
240
 
147
241
  ```ts
148
- const http = new FetchClient();
242
+ const profiles = await safeCdx.getTestProfilesByAccount();
243
+ ```
149
244
 
150
- const loginClient = new HCLoginClient(http);
245
+ ```ts
246
+ const profiles = await safeCdx.getTestProfilesByAccount(true);
247
+ ```
151
248
 
152
- // loginClient.configure(...)
153
- // await loginClient.loginPatient(...)
249
+ #### API request sent internally
154
250
 
155
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
156
- environment: "dev",
157
- });
251
+ ```json
252
+ {
253
+ "Data": {
254
+ "IncludeRegisterTestDetails": true
255
+ }
256
+ }
257
+ ```
258
+
259
+ #### API response example
260
+
261
+ ```json
262
+ {
263
+ "Data": {
264
+ "success": true,
265
+ "data": [
266
+ {
267
+ "gtin": "810172700093",
268
+ "productName": "Speedy Swab COVID-19 + Flu Self-Test",
269
+ "description": "",
270
+ "testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/4307c803740644ab8c4594f69783c1d8-638694157517533356.png",
271
+ "language": "en",
272
+ "registerTestDetail": {
273
+ "title": "Scan the barcode",
274
+ "description": "<p><span style=\"font-size: 12pt; font-family: Avenir, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;\">Hold your phone over the barcode on the box.</span></p>",
275
+ "buttonTitle": "Scan Code",
276
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/1ccc293f924a4c5bb2c5fc76f474c625-638769599179275771.png"
277
+ }
278
+ },
279
+ {
280
+ "gtin": "860002060439",
281
+ "productName": "Winx UTI Test + Treat",
282
+ "description": "",
283
+ "testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/36649cb6377c435a9e4f83af0f2fea46-639058689126409862.png",
284
+ "language": "en",
285
+ "registerTestDetail": {
286
+ "title": "",
287
+ "description": "<p></p>",
288
+ "buttonTitle": "Scan Barcode on the Box",
289
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/ad72c9b8c3324c1cb7aa9dbf29e46d64-638905990491115782.png"
290
+ }
291
+ },
292
+ {
293
+ "gtin": "850024942325",
294
+ "productName": "Vaginal Health pH Test + Treat",
295
+ "description": "",
296
+ "testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/3cebedbb69cb4455af33da5ab524ec93-639058688331122684.png",
297
+ "language": "en",
298
+ "registerTestDetail": {
299
+ "title": "",
300
+ "description": "<p></p>",
301
+ "buttonTitle": "Scan Barcode on the Box",
302
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/1475ae0004084d749ee2dfc9123af952-638905989776168388.png"
303
+ }
304
+ }
305
+ ],
306
+ "message": "Success",
307
+ "code": 0
308
+ },
309
+ "ErrorMessage": null,
310
+ "IsOK": true
311
+ }
158
312
  ```
159
313
 
160
- Safe CDX does not manually receive a token. It does not expose `setAccessToken()`. Auth is inherited from the configured login client.
314
+ ---
315
+
316
+ ### `getTestProfileByGTIN(...)`
161
317
 
162
- ## Optional API Key Header
318
+ Resolves the Safe CDX test profile associated with a GTIN barcode.
163
319
 
164
- If an API key header is required by the environment, it can be set on the Safe CDX client:
320
+ #### Signature
165
321
 
166
322
  ```ts
167
- safeCdx.setApiKey("x-api-key", apiKeyValue);
323
+ safeCdx.getTestProfileByGTIN(gtin: string): Promise<APIResponse<GetTestProfileByGTINData>>
168
324
  ```
169
325
 
170
- When configured, the API key header is added to Safe CDX API calls together with the login auth headers.
326
+ #### Parameters
171
327
 
172
- ## Request Payload Wrapping
328
+ | Parameter | Type | Required | Description |
329
+ |---|---|---:|---|
330
+ | `gtin` | `string` | Yes | GTIN barcode value used to resolve the test profile. |
173
331
 
174
- For POST requests, only the inner payload should be passed to SDK methods.
175
-
176
- The SDK wraps the payload internally as:
332
+ #### Usage
177
333
 
178
334
  ```ts
335
+ const profile = await safeCdx.getTestProfileByGTIN("850024942325");
336
+ ```
337
+
338
+
339
+ #### API response example
340
+
341
+ ```json
179
342
  {
180
- Data: payload
343
+ "Data": {
344
+ "ID": "6a104bba6068dd9a213db810",
345
+ "FHIRID": null,
346
+ "CQLID": null,
347
+ "TestName": "UTI Test + Treat",
348
+ "CustomTestName": "UTI Test + Treat",
349
+ "IsDeactivated": false,
350
+ "IsOrderable": true,
351
+ "EnablePublicHealthReporting": true,
352
+ "IsSelfAssessmentMode": true,
353
+ "CaptureMethod": 0,
354
+ "IsMLDisabled": false,
355
+ "ShortName": "UTI Test + Treat",
356
+ "DescriptionHTML": null,
357
+ "OrderableName": "Winx Health",
358
+ "VendorName": null,
359
+ "ManufactureName": null,
360
+ "VendorTestID": "0123",
361
+ "Sku": "SH00131",
362
+ "LabTestType": null,
363
+ "TestClassification": "NOT_SPECIFIED",
364
+ "KitRegistrationRequired": false,
365
+ "RequiresProviderVerificationOfResults": false,
366
+ "DiagnosticProfile": {
367
+ "_id": "6780031a991814469e88237f",
368
+ "Created": "2025-01-09T17:10:50.8Z",
369
+ "Modified": "2026-05-06T12:39:08.732Z",
370
+ "TestInfo": {
371
+ "gtin": "860002060439",
372
+ "testInfoHeader": "Winx Health",
373
+ "productName": "Winx UTI Test + Treat",
374
+ "testCategory": "Rapid",
375
+ "fdaStatus": "EUA",
376
+ "cvmlTestName": "winx-uti",
377
+ "cvmlDuration": 20,
378
+ "isCVMLResult": true,
379
+ "accountIds": [
380
+ "winxhealth",
381
+ "safehealth",
382
+ "test-account",
383
+ "speedyswab",
384
+ "healthcheck"
385
+ ],
386
+ "cvmlRetryLimit": 2,
387
+ "failoverEnabled": true,
388
+ "cvmlPollingPolicy": "FullResult"
389
+ },
390
+ "Analyte": [
391
+ {
392
+ "_id": "1",
393
+ "DisplayOrder": 1,
394
+ "Display": "CVML",
395
+ "Name": "Leukocyte",
396
+ "Question": "What is the Leukocyte reading?",
397
+ "Image": "/analyte/WinxLeuk.png",
398
+ "Responses": [
399
+ {
400
+ "value": "Negative -",
401
+ "displayOrder": 1,
402
+ "storedValue": "Neg",
403
+ "cvmlValue": "Neg",
404
+ "score": "0"
405
+ },
406
+ {
407
+ "value": "Positive 70+",
408
+ "displayOrder": 3,
409
+ "storedValue": "70+",
410
+ "cvmlValue": "70+",
411
+ "score": "4"
412
+ }
413
+ ]
414
+ },
415
+ {
416
+ "_id": "2",
417
+ "DisplayOrder": 2,
418
+ "Display": "CVML",
419
+ "Name": "Nitrite",
420
+ "Question": "What is the Nitrite reading?",
421
+ "Image": "/analyte/WinxNitr.png",
422
+ "Responses": [
423
+ {
424
+ "value": "Negative",
425
+ "displayOrder": 1,
426
+ "storedValue": "Neg",
427
+ "cvmlValue": "Neg",
428
+ "score": "0"
429
+ },
430
+ {
431
+ "value": "Positive High++",
432
+ "displayOrder": 3,
433
+ "storedValue": "High++",
434
+ "cvmlValue": "High++",
435
+ "score": "2"
436
+ }
437
+ ]
438
+ }
439
+ ],
440
+ "Cta": [
441
+ {
442
+ "id": "1",
443
+ "title": "No UTI is detected",
444
+ "instructions": "If you're still having symptoms, we recommend talking to your doctor about additional testing.",
445
+ "linkType": "url",
446
+ "linkText": "Find a Doctor",
447
+ "linkValue": "https://book.zocdoc.com/?utm_source=winx&utm_medium=app&utm_campaign=testandtreat"
448
+ }
449
+ ],
450
+ "Indication": [
451
+ {
452
+ "id": "4",
453
+ "title": "Indication",
454
+ "text": "Results suggest you have a UTI",
455
+ "recommendTitle": "Recommendation",
456
+ "recommendText": "Drink lots of water and connect with a doctor to discuss treatment and next steps.",
457
+ "ctaId": "2"
458
+ }
459
+ ],
460
+ "Decision": [
461
+ {
462
+ "calculation": "Total: analyte.responses.score",
463
+ "results": [
464
+ {
465
+ "indicationId": "4",
466
+ "value": "4"
467
+ }
468
+ ]
469
+ }
470
+ ]
471
+ },
472
+ "ProductAssetDetail": {
473
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/ProductAsset/Image/47197f0c75844ba3b11335f7bfe1959b-639062362985442065.png",
474
+ "body": "<p></p>"
475
+ },
476
+ "DisclaimerDetail": {
477
+ "body": "<p><span style=\"font-family: Avenir, -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif; font-size: 14pt;\">At this time please refer back to your paper Instructions For Use (IFU).</span></p>",
478
+ "title": "Record Results",
479
+ "continueButtonTitle": "Continue",
480
+ "showDisclaimer": false
481
+ },
482
+ "ScanImageDetail": {
483
+ "ImageUrl": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/ScanImage/Image/590172d7548e4b3eb09bfe42679fc6b1-638900206144635626.png"
484
+ },
485
+ "Instructions": [
486
+ {
487
+ "NavigationTitle": "Instructional Video",
488
+ "Type": "CollectionInstructionOnly",
489
+ "Title": "Instructional Video",
490
+ "ButtonTitle": "Continue",
491
+ "TimeInSeconds": 0,
492
+ "VideoUrl": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/CollectionInstruction/Video/3a74d7091d65455ab675f8ddd5f9d9d5-638918232791589391.mp4",
493
+ "SequenceOrder": 0
494
+ }
495
+ ],
496
+ "BillingInfo": {
497
+ "Taxable": false
498
+ },
499
+ "Associations": null,
500
+ "TestReportAsset": null,
501
+ "Articles": [],
502
+ "TestResults": [
503
+ {
504
+ "PatientTestResult": 0,
505
+ "ResultTitle": "Positive",
506
+ "ResultDisplay": "Positive",
507
+ "TreatmentPlanDescription": "",
508
+ "IsReturnToDashboardAllowed": false,
509
+ "IsOrderTestAllowed": false,
510
+ "IsFindTestRetailerAllowed": false
511
+ }
512
+ ],
513
+ "GS1GTINs": null,
514
+ "Included": null,
515
+ "LOINCCodes": null,
516
+ "TestLOINCCodes": null,
517
+ "TestSNOMEDCTCodes": null,
518
+ "Created": "2024-12-23T18:00:48.619Z",
519
+ "Modified": "2026-05-13T17:07:07.048Z",
520
+ "CreatedByID": null,
521
+ "ModifiedByID": null,
522
+ "TenantID": null
523
+ },
524
+ "ErrorMessage": null,
525
+ "IsOK": true
181
526
  }
182
527
  ```
183
528
 
184
- Correct:
529
+ ---
530
+
531
+ ### `createUploadUrl(...)`
532
+
533
+ Creates a pre-signed URL for uploading a test image.
534
+
535
+ #### Signature
185
536
 
186
537
  ```ts
187
- await safeCdx.submitAnswers({
188
- UserTestResultId: userTestResultId,
189
- Result: [
190
- {
191
- Analyte: "Purchase",
192
- ReportedValue: "drug store",
193
- StoredValue: "drug store",
194
- Score: "0",
195
- },
196
- ],
197
- });
538
+ safeCdx.createUploadUrl(
539
+ userTestResultId: string,
540
+ gtin: string,
541
+ imageType?: string
542
+ ): Promise<APIResponse<CreateUploadUrlData>>
198
543
  ```
199
544
 
200
- Incorrect:
545
+ #### Parameters
546
+
547
+ | Parameter | Type | Required | Description |
548
+ |---|---|---:|---|
549
+ | `userTestResultId` | `string` | Yes | Test result identifier reused through the workflow. |
550
+ | `gtin` | `string` | Yes | GTIN associated with the selected test. |
551
+ | `imageType` | `string` | No | Image type used in upload metadata. Defaults to `"jpg"`. |
552
+
553
+ #### Usage
201
554
 
202
555
  ```ts
203
- await safeCdx.submitAnswers({
204
- Data: {
205
- UserTestResultId: userTestResultId,
206
- Result: [],
207
- },
208
- });
556
+ const upload = await safeCdx.createUploadUrl(
557
+ userTestResultId,
558
+ gtin,
559
+ "jpg"
560
+ );
209
561
  ```
210
562
 
211
- The caller should not manually add the `Data` wrapper.
563
+ #### API request sent internally
212
564
 
213
- ## API Response Handling
565
+ ```json
566
+ {
567
+ "Data": {
568
+ "Gtin": "<GTIN>",
569
+ "UserTestResultId": "<USER_TEST_RESULT_ID>",
570
+ "Metadata": {
571
+ "ImageType": "jpg"
572
+ }
573
+ }
574
+ }
575
+ ```
214
576
 
215
- Most Safe CDX endpoints return the standard backend response envelope:
577
+ #### API response example
216
578
 
217
- ```ts
579
+ ```json
218
580
  {
219
- Data: T;
220
- IsOK: boolean;
221
- ErrorMessage: string | null;
581
+ "Data": {
582
+ "preSignedURL": "https://sf-us-west-2-lower-neural-cdx-img-uploads-quality.s3.us-west-2.amazonaws.com/healthcheck/winx-uti/860002060439_6a104bba6068dd9a213db810-1.jpg?X-Amz-Expires=1200&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***&X-Amz-Date=20260522T123118Z&X-Amz-SignedHeaders=host&X-Amz-Signature=***",
583
+ "Metadata": {
584
+ "UploadId": "860002060439_6a104bba6068dd9a213db810-1",
585
+ "Type": "Rapid Test Kit Upload",
586
+ "File": "860002060439_6a104bba6068dd9a213db810-1.jpg"
587
+ }
588
+ },
589
+ "ErrorMessage": null,
590
+ "IsOK": true
222
591
  }
223
592
  ```
224
593
 
225
- For these endpoints, the client validates `IsOK`.
594
+ ---
595
+
596
+ ### `uploadImage(...)`
597
+
598
+ Uploads an image directly to the pre-signed URL returned by `createUploadUrl(...)`. This method does not call a Safe CDX API route and does not send Health Cloud authentication headers.
599
+
600
+ #### Signature
226
601
 
227
- If `IsOK` is `false`, the client throws `SafeCDXError`.
602
+ ```ts
603
+ safeCdx.uploadImage(
604
+ preSignedURL: string,
605
+ image: BodyInit,
606
+ contentType?: string
607
+ ): Promise<void>
608
+ ```
228
609
 
229
- This validation exists because an HTTP `200` response does not always mean the backend operation succeeded. The HTTP client validates the transport layer, while the Safe CDX client validates the API response envelope.
610
+ #### Parameters
230
611
 
231
- Some endpoints return a raw service result instead of the standard `ApiResponse` envelope. Those methods return the raw result directly.
612
+ | Parameter | Type | Required | Description |
613
+ |---|---|---:|---|
614
+ | `preSignedURL` | `string` | Yes | Temporary storage upload URL returned by `createUploadUrl(...)`. |
615
+ | `image` | `BodyInit` | Yes | Raw image body, such as a `File`, `Blob` or compatible upload body. |
616
+ | `contentType` | `string` | No | MIME type of the uploaded file. Defaults to `"image/jpeg"`. |
232
617
 
233
- Raw result methods include:
618
+ #### Usage
234
619
 
235
620
  ```ts
236
- updateCvmlStatus(...)
237
- getCvmlResults(...)
238
- getResultDetails(...)
239
- getImageCaptureUrl(...)
621
+ await safeCdx.uploadImage(
622
+ preSignedURL,
623
+ imageBody,
624
+ "image/jpeg"
625
+ );
240
626
  ```
241
627
 
242
- ## Image Upload Handling
628
+ A failed direct upload throws `SafeCDXError`.
243
629
 
244
- Image upload is different from normal Safe CDX API calls.
630
+ ---
245
631
 
246
- The method `createUploadUrl(...)` returns a pre-signed S3 URL. That URL is then used to upload the image file directly.
632
+ ### `updateCvmlStatus(...)`
633
+
634
+ Updates the CVML processing status for a captured image. This endpoint returns `SafeAPIResponse<T>` directly.
635
+
636
+ #### Signature
247
637
 
248
638
  ```ts
249
- await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
639
+ safeCdx.updateCvmlStatus(
640
+ imageOfCaptureId: string,
641
+ cvmlStatus: string
642
+ ): Promise<UpdateCvmlStatusResponse>
250
643
  ```
251
644
 
252
- This upload does not go through the shared `HttpClient`.
645
+ `UpdateCvmlStatusResponse` is:
253
646
 
254
- The reason is that the shared `FetchClient.put()` is JSON-oriented and sends request bodies with `JSON.stringify(...)`. Image upload needs to send a raw `BodyInit`, such as a `Blob`, `File`, `ArrayBuffer`, or stream-compatible body.
647
+ ```ts
648
+ type UpdateCvmlStatusResponse =
649
+ SafeAPIResponse<EntityReferenceData>;
650
+ ```
255
651
 
256
- Authorization headers should not be sent to the pre-signed URL. The pre-signed URL already contains temporary upload authorization.
652
+ #### Parameters
257
653
 
258
- ## Typical Safe CDX Flow
654
+ | Parameter | Type | Required | Description |
655
+ |---|---|---:|---|
656
+ | `imageOfCaptureId` | `string` | Yes | Identifier returned in the image upload metadata. |
657
+ | `cvmlStatus` | `string` | Yes | CVML processing status to apply, such as `"ImageProcessing"`. |
259
658
 
260
- A typical flow looks like this:
659
+ #### Usage
261
660
 
262
661
  ```ts
263
- const http = new FetchClient();
662
+ const response = await safeCdx.updateCvmlStatus(
663
+ imageOfCaptureId,
664
+ "ImageProcessing"
665
+ );
666
+ ```
264
667
 
265
- const loginClient = new HCLoginClient(http);
668
+ #### API request sent internally
266
669
 
267
- // Configure and authenticate loginClient first.
670
+ ```json
671
+ {
672
+ "Data": {
673
+ "ImageOfCaptureId": "<IMAGE_OF_CAPTURE_ID>",
674
+ "CvmlStatus": "ImageProcessing"
675
+ }
676
+ }
677
+ ```
268
678
 
269
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
270
- environment: "dev",
271
- defaultLanguage: "en",
272
- defaultImageType: "jpg",
273
- });
679
+ #### API response example
680
+
681
+ ```json
682
+ {
683
+ "success": true,
684
+ "data": {
685
+ "_id": "6a104bba6068dd9a213db810"
686
+ },
687
+ "message": "Success",
688
+ "code": 0
689
+ }
274
690
  ```
275
691
 
276
- ### 1. Get available test profiles
692
+ ---
693
+
694
+ ### `getCvmlResults(...)`
695
+
696
+ Polls CVML results for a captured image. This endpoint returns `SafeAPIResponse<T>` directly.
697
+
698
+ #### Signature
277
699
 
278
700
  ```ts
279
- const profiles = await safeCdx.getTestProfilesByAccount();
701
+ safeCdx.getCvmlResults(
702
+ imageOfCaptureId: string
703
+ ): Promise<GetCvmlResultsResponse>
280
704
  ```
281
705
 
282
- ### 2. Resolve test profile by GTIN
706
+ `GetCvmlResultsResponse` is:
283
707
 
284
708
  ```ts
285
- const profile = await safeCdx.getTestProfileByGTIN("850024942325");
286
-
287
- const userTestResultId = profile.Data.ID;
288
- const gtin = profile.Data.DiagnosticProfile.TestInfo.gtin;
709
+ type GetCvmlResultsResponse =
710
+ SafeAPIResponse<CvmlResultsData>;
289
711
  ```
290
712
 
291
- ### 3. Create upload URL
713
+ #### Parameters
714
+
715
+ | Parameter | Type | Required | Description |
716
+ |---|---|---:|---|
717
+ | `imageOfCaptureId` | `string` | Yes | Identifier of the captured image being processed. |
718
+
719
+ #### Usage
292
720
 
293
721
  ```ts
294
- const upload = await safeCdx.createUploadUrl(userTestResultId, gtin);
722
+ const response = await safeCdx.getCvmlResults(imageOfCaptureId);
723
+ ```
295
724
 
296
- const preSignedURL = upload.Data.preSignedURL;
297
- const imageOfCaptureId = upload.Data.Metadata.UploadId;
725
+ #### API response example
726
+
727
+ ```json
728
+ {
729
+ "success": true,
730
+ "data": {
731
+ "userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
732
+ "status": "Started",
733
+ "cvmlStatus": "Retake",
734
+ "gtin": "860002060439",
735
+ "isCVMLResult": true,
736
+ "isSelfReportingPostTest": false,
737
+ "schemaVersion": 1,
738
+ "capture": {
739
+ "retryCount": 0,
740
+ "retryLimit": 0,
741
+ "failoverEnabled": true,
742
+ "lastCvmlStatus": "Retake",
743
+ "failoverTriggered": false
744
+ },
745
+ "questionnaireSnapshot": {
746
+ "isValid": []
747
+ },
748
+ "resumed": false,
749
+ "imageOfCaptureResult": {
750
+ "parsed": {
751
+ "outcome": "Retake",
752
+ "responseCode": "Err4",
753
+ "responseMessage": "ml_result_code_error_04_msg",
754
+ "responseTitle": "ml_result_code_error_04_title"
755
+ }
756
+ },
757
+ "failoverEnabled": true,
758
+ "failoverTriggered": false,
759
+ "resumable": false,
760
+ "showValidity": false,
761
+ "showError": true,
762
+ "_id": "6a104bba6068dd9a213db810"
763
+ },
764
+ "message": "Success",
765
+ "code": 0
766
+ }
298
767
  ```
299
768
 
300
- ### 4. Upload image
769
+ ---
770
+
771
+ ### `getPendingResults(...)`
772
+
773
+ Returns pending or incomplete test results for the authenticated patient.
774
+
775
+ #### Signature
301
776
 
302
777
  ```ts
303
- await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
778
+ safeCdx.getPendingResults(
779
+ excludeStatus?: string
780
+ ): Promise<APIResponse<GetPendingResultsData>>
304
781
  ```
305
782
 
306
- ### 5. Update CVML status
783
+ `GetPendingResultsData` is:
307
784
 
308
785
  ```ts
309
- await safeCdx.updateCvmlStatus(imageOfCaptureId, "ImageProcessing");
786
+ type GetPendingResultsData =
787
+ SafeAPIResponse<TestResultSummary[]>;
310
788
  ```
311
789
 
312
- ### 6. Poll CVML results
790
+ #### Parameters
791
+
792
+ | Parameter | Type | Required | Description |
793
+ |---|---|---:|---|
794
+ | `excludeStatus` | `string` | No | Comma-separated statuses excluded from the returned results. Defaults to `"Invalid,Canceled"`. |
795
+
796
+ #### Usage
313
797
 
314
798
  ```ts
315
- const cvmlResults = await safeCdx.getCvmlResults(imageOfCaptureId);
799
+ const pending = await safeCdx.getPendingResults();
316
800
  ```
317
801
 
318
- ### 7. Submit answers
319
-
320
802
  ```ts
321
- await safeCdx.submitAnswers({
322
- UserTestResultId: userTestResultId,
323
- Result: [
324
- {
325
- Analyte: "Purchase",
326
- ReportedValue: "drug store",
327
- StoredValue: "drug store",
328
- Score: "0",
329
- },
330
- ],
331
- });
803
+ const pending = await safeCdx.getPendingResults(
804
+ "Invalid,Canceled"
805
+ );
332
806
  ```
333
807
 
334
- ### 8. Finalize test
808
+ #### API request sent internally
335
809
 
336
- ```ts
337
- await safeCdx.finalizeTest({
338
- UserTestResultId: userTestResultId,
339
- ctaResult: {
340
- id: "2",
341
- title: "Connect with a Telehealth Provider for Next Steps",
342
- instructions: "Share your test results with a telehealth provider for next steps.",
343
- urlText: "Connect with Telehealth",
344
- url: "https://example.com/start",
345
- },
346
- indicationResult: {
347
- id: "4",
348
- title: "Indication",
349
- text: "Results suggest follow-up may be needed.",
350
- recommendTitle: "Recommendation",
351
- recommendText: "Review the result with a healthcare provider.",
352
- ctaId: "2",
353
- },
354
- decisionResult: {
355
- calculation: "Total: analyte.responses.score",
356
- results: [
810
+ ```json
811
+ {
812
+ "Data": {
813
+ "ExcludeStatus": "Invalid,Canceled"
814
+ }
815
+ }
816
+ ```
817
+
818
+ #### API response example
819
+
820
+ ```json
821
+ {
822
+ "Data": {
823
+ "success": true,
824
+ "data": [
357
825
  {
358
- indicationId: "4",
359
- value: "4",
826
+ "status": "Started",
827
+ "cvmlStatus": "Retake",
828
+ "gtin": "860002060439",
829
+ "testName": "Winx UTI Test + Treat",
830
+ "recordedDate": "2026-05-22T12:49:26.047Z",
831
+ "isCVMLBackground": false,
832
+ "isCVMLResult": true,
833
+ "isSelfReportingPostTest": false,
834
+ "isSelfReportingPreTest": false,
835
+ "viewed": false,
836
+ "schemaVersion": 1,
837
+ "capture": {
838
+ "retryCount": 0,
839
+ "retryLimit": 0,
840
+ "failoverEnabled": true,
841
+ "lastCvmlStatus": "Retake",
842
+ "failoverTriggered": false
843
+ },
844
+ "questionnaireSnapshot": {
845
+ "isValid": []
846
+ },
847
+ "resumed": false,
848
+ "failoverEnabled": true,
849
+ "failoverTriggered": false,
850
+ "resumable": true,
851
+ "resumeNext": "SelfReporting",
852
+ "showValidity": false,
853
+ "showError": true,
854
+ "_id": "6a104bba6068dd9a213db810"
360
855
  },
856
+ {
857
+ "status": "Started",
858
+ "gtin": "810172700093",
859
+ "testName": "Speedy Swab COVID-19 + Flu Self-Test",
860
+ "recordedDate": "2026-05-22T10:11:10.181Z",
861
+ "isCVMLBackground": true,
862
+ "isCVMLResult": false,
863
+ "isSelfReportingPostTest": true,
864
+ "isSelfReportingPreTest": false,
865
+ "viewed": false,
866
+ "schemaVersion": 1,
867
+ "capture": {
868
+ "retryCount": 0,
869
+ "retryLimit": 0,
870
+ "failoverEnabled": true,
871
+ "failoverTriggered": false
872
+ },
873
+ "questionnaireSnapshot": {
874
+ "isValid": [
875
+ {
876
+ "display": "post-test",
877
+ "question": "Do you see a control line (C)?"
878
+ }
879
+ ]
880
+ },
881
+ "resumed": false,
882
+ "failoverEnabled": true,
883
+ "failoverTriggered": false,
884
+ "resumable": false,
885
+ "showValidity": false,
886
+ "showError": true,
887
+ "_id": "6a102bbe6068dd9a213db80f"
888
+ }
361
889
  ],
890
+ "message": "Success",
891
+ "code": 0
362
892
  },
363
- });
893
+ "ErrorMessage": null,
894
+ "IsOK": true
895
+ }
364
896
  ```
365
897
 
366
- ### 9. Retrieve result data
898
+ ---
899
+
900
+ ### `getLastResults(...)`
901
+
902
+ Returns the most recent test result for the authenticated patient.
903
+
904
+ #### Signature
367
905
 
368
906
  ```ts
369
- const details = await safeCdx.getResultDetails(userTestResultId);
907
+ safeCdx.getLastResults(
908
+ excludeStatus?: string
909
+ ): Promise<APIResponse<GetLastResultsData>>
910
+ ```
370
911
 
371
- const pdf = await safeCdx.getResultPdf({
372
- UserTestResultId: userTestResultId,
373
- });
912
+ `GetLastResultsData` is:
374
913
 
375
- const imageCaptureUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
914
+ ```ts
915
+ type GetLastResultsData =
916
+ SafeAPIResponse<TestResultDetails>;
376
917
  ```
377
918
 
378
- ## State Management
919
+ #### Parameters
920
+
921
+ | Parameter | Type | Required | Description |
922
+ |---|---|---:|---|
923
+ | `excludeStatus` | `string` | No | Status excluded from the returned result. Defaults to `"Invalid"`. |
379
924
 
380
- The connector does not manage the full Safe CDX flow state.
925
+ #### Usage
381
926
 
382
- The application should keep track of values returned between steps, such as:
927
+ ```ts
928
+ const lastResult = await safeCdx.getLastResults();
929
+ ```
383
930
 
384
931
  ```ts
385
- const state = {
386
- gtin: undefined,
387
- userTestResultId: undefined,
388
- preSignedURL: undefined,
389
- imageOfCaptureId: undefined,
390
- };
932
+ const lastResult = await safeCdx.getLastResults("Invalid");
391
933
  ```
392
934
 
393
- Important values:
935
+ #### API request sent internally
394
936
 
395
- - `gtin`: product or test kit identifier
396
- - `userTestResultId`: test result record ID used across the flow
397
- - `preSignedURL`: temporary URL used for image upload
398
- - `imageOfCaptureId`: upload identifier returned after creating the upload URL
937
+ ```json
938
+ {
939
+ "Data": {
940
+ "ExcludeStatus": "Invalid"
941
+ }
942
+ }
943
+ ```
399
944
 
400
- These values should be stored by the consuming app and passed to the next SDK method when needed.
945
+ #### API response example
401
946
 
402
- ## Public Methods
947
+ ```json
948
+ {
949
+ "Data": {
950
+ "success": true,
951
+ "data": {
952
+ "cvmlUploadId": "6a104bba6068dd9a213db810",
953
+ "userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
954
+ "tenantID": "healthcheck",
955
+ "isSelfAssessment": false,
956
+ "status": "Started",
957
+ "cvmlStatus": "Retake",
958
+ "gtin": "860002060439",
959
+ "testInfoHeader": "Winx Health",
960
+ "testName": "Winx UTI Test + Treat",
961
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
962
+ "imageOfCaptureUploadAt": "2026-05-22T12:31:18.333Z",
963
+ "imageOfCaptureResults": [
964
+ {
965
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
966
+ "uploadAt": "2026-05-22T12:31:18.333Z",
967
+ "cvmlStatus": "Retake",
968
+ "parsed": {
969
+ "outcome": "Retake",
970
+ "responseCode": "Err4",
971
+ "responseMessage": "ml_result_code_error_04_msg",
972
+ "responseTitle": "ml_result_code_error_04_title"
973
+ }
974
+ }
975
+ ],
976
+ "resultPDF": "archive/2026/05/22/12/48/7ecfd730-c4c3-4cd6-8b12-4210b9a2db28.pdf",
977
+ "result": [
978
+ {
979
+ "analyte": "Purchase",
980
+ "reportedValue": "drug store",
981
+ "storedValue": "drug store",
982
+ "score": "0"
983
+ }
984
+ ],
985
+ "recordedDate": "2026-05-22T12:49:26.047Z",
986
+ "cvmlTestName": "winx-uti",
987
+ "isCVMLBackground": false,
988
+ "isCVMLResult": true,
989
+ "isSelfReportingPostTest": false,
990
+ "isSelfReportingPreTest": false,
991
+ "viewed": false,
992
+ "finalized": false,
993
+ "schemaVersion": 1,
994
+ "capture": {
995
+ "retryCount": 1,
996
+ "retryLimit": 2,
997
+ "failoverEnabled": true,
998
+ "lastCvmlStatus": "Retake",
999
+ "failoverTriggered": false
1000
+ },
1001
+ "questionnaireSnapshot": {
1002
+ "isValid": [],
1003
+ "analyte": [
1004
+ {
1005
+ "_id": "1",
1006
+ "displayOrder": 1,
1007
+ "display": "CVML",
1008
+ "name": "Leukocyte",
1009
+ "question": "What is the Leukocyte reading?",
1010
+ "image": "WinxLeuk.png",
1011
+ "cvmlOrder": 1,
1012
+ "responses": [
1013
+ {
1014
+ "value": "Negative -",
1015
+ "displayOrder": 1,
1016
+ "storedValue": "Neg",
1017
+ "cvmlValue": "Neg",
1018
+ "score": "0"
1019
+ },
1020
+ {
1021
+ "value": "Trace 15+-",
1022
+ "displayOrder": 2,
1023
+ "storedValue": "15+-",
1024
+ "cvmlValue": "15+-",
1025
+ "score": "3"
1026
+ },
1027
+ {
1028
+ "value": "Positive 70+",
1029
+ "displayOrder": 3,
1030
+ "storedValue": "70+",
1031
+ "cvmlValue": "70+",
1032
+ "score": "4"
1033
+ }
1034
+ ]
1035
+ },
1036
+ {
1037
+ "_id": "2",
1038
+ "displayOrder": 2,
1039
+ "display": "CVML",
1040
+ "name": "Nitrite",
1041
+ "question": "What is the Nitrite reading?",
1042
+ "image": "WinxNitr.png",
1043
+ "cvmlOrder": 2,
1044
+ "responses": [
1045
+ {
1046
+ "value": "Negative",
1047
+ "displayOrder": 1,
1048
+ "storedValue": "Neg",
1049
+ "cvmlValue": "Neg",
1050
+ "score": "0"
1051
+ },
1052
+ {
1053
+ "value": "Positive Low+",
1054
+ "displayOrder": 2,
1055
+ "storedValue": "Low+",
1056
+ "cvmlValue": "Low+",
1057
+ "score": "2"
1058
+ },
1059
+ {
1060
+ "value": "Positive High++",
1061
+ "displayOrder": 3,
1062
+ "storedValue": "High++",
1063
+ "cvmlValue": "High++",
1064
+ "score": "2"
1065
+ }
1066
+ ]
1067
+ }
1068
+ ]
1069
+ },
1070
+ "resumed": false,
1071
+ "language": "en",
1072
+ "labVendor": "Winx Health",
1073
+ "labTestType": "Home Test (Rapid)",
1074
+ "externalId": "test-user@example.com",
1075
+ "positiveAnalytes": [],
1076
+ "_id": "6a104bba6068dd9a213db810",
1077
+ "modifier": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
1078
+ "created": "2026-05-22T12:27:38.731Z",
1079
+ "modified": "2026-05-22T12:49:26.047Z"
1080
+ },
1081
+ "message": "Success",
1082
+ "code": 0
1083
+ },
1084
+ "ErrorMessage": null,
1085
+ "IsOK": true
1086
+ }
1087
+ ```
1088
+
1089
+ ---
1090
+
1091
+ ### `getTestHistory(...)`
403
1092
 
404
- ### getTestProfileByGTIN
1093
+ Returns test result history for the authenticated patient.
1094
+
1095
+ #### Signature
405
1096
 
406
1097
  ```ts
407
- const profile = await safeCdx.getTestProfileByGTIN(gtin, language);
1098
+ safeCdx.getTestHistory(
1099
+ excludeStatus?: string
1100
+ ): Promise<APIResponse<GetTestHistoryData>>
408
1101
  ```
409
1102
 
410
- Resolves a test profile by GTIN barcode.
1103
+ `GetTestHistoryData` is:
1104
+
1105
+ ```ts
1106
+ type GetTestHistoryData =
1107
+ SafeAPIResponse<TestResultSummary[]>;
1108
+ ```
411
1109
 
412
- `language` is optional. If not provided, the client uses `defaultLanguage` from config when available.
1110
+ #### Parameters
413
1111
 
1112
+ | Parameter | Type | Required | Description |
1113
+ |---|---|---:|---|
1114
+ | `excludeStatus` | `string` | No | Status excluded from the returned history. Defaults to `"Invalid"`. |
414
1115
 
415
- ### getTestProfile
1116
+ #### Usage
416
1117
 
417
1118
  ```ts
418
- const profile = await safeCdx.getTestProfile(payload);
1119
+ const history = await safeCdx.getTestHistory();
419
1120
  ```
420
1121
 
421
- Returns the full test profile for a given payload.
1122
+ ```ts
1123
+ const history = await safeCdx.getTestHistory("Invalid");
1124
+ ```
422
1125
 
423
- ### getTestProfilesByAccount
1126
+ #### API request sent internally
424
1127
 
425
- ```ts
426
- const profiles = await safeCdx.getTestProfilesByAccount();
1128
+ ```json
1129
+ {
1130
+ "Data": {
1131
+ "ExcludeStatus": "Invalid"
1132
+ }
1133
+ }
427
1134
  ```
428
1135
 
429
- Lists test profiles available to the authenticated account.
1136
+ #### API response example
430
1137
 
431
- A custom payload may also be provided:
1138
+ ```json
1139
+ {
1140
+ "Data": {
1141
+ "success": true,
1142
+ "data": [
1143
+ {
1144
+ "status": "Started",
1145
+ "cvmlStatus": "Retake",
1146
+ "testInfoHeader": "Winx Health",
1147
+ "testName": "Winx UTI Test + Treat",
1148
+ "recordedDate": "2026-05-22T12:49:26.047Z",
1149
+ "viewed": false,
1150
+ "schemaVersion": 1,
1151
+ "overallResult": "Unknown",
1152
+ "positiveAnalytes": [],
1153
+ "_id": "6a104bba6068dd9a213db810",
1154
+ "created": "2026-05-22T12:27:38.731Z"
1155
+ },
1156
+ {
1157
+ "status": "Started",
1158
+ "testInfoHeader": "Biolabs International",
1159
+ "testName": "Speedy Swab COVID-19 + Flu Self-Test",
1160
+ "recordedDate": "2026-05-22T10:11:10.181Z",
1161
+ "viewed": false,
1162
+ "schemaVersion": 1,
1163
+ "overallResult": "Unknown",
1164
+ "positiveAnalytes": [],
1165
+ "_id": "6a102bbe6068dd9a213db80f",
1166
+ "created": "2026-05-22T10:11:10.181Z"
1167
+ }
1168
+ ],
1169
+ "message": "Success",
1170
+ "code": 0
1171
+ },
1172
+ "ErrorMessage": null,
1173
+ "IsOK": true
1174
+ }
1175
+ ```
1176
+
1177
+ ---
1178
+
1179
+ ### `getResultDetails(...)`
1180
+
1181
+ Returns detailed data for a specific test attempt. This endpoint returns `SafeAPIResponse<T>` directly.
1182
+
1183
+ #### Signature
432
1184
 
433
1185
  ```ts
434
- const profiles = await safeCdx.getTestProfilesByAccount({
435
- IncludeRegisterTestDetails: true,
436
- });
1186
+ safeCdx.getResultDetails(
1187
+ userTestResultId: string
1188
+ ): Promise<GetResultDetailsResponse>
437
1189
  ```
438
1190
 
439
- ### createUploadUrl
1191
+ `GetResultDetailsResponse` is:
440
1192
 
441
1193
  ```ts
442
- const upload = await safeCdx.createUploadUrl(
443
- userTestResultId,
444
- gtin,
445
- "jpg"
446
- );
1194
+ type GetResultDetailsResponse =
1195
+ SafeAPIResponse<TestResultDetails>;
447
1196
  ```
448
1197
 
449
- Creates a pre-signed image upload URL.
1198
+ #### Parameters
450
1199
 
451
- `imageType` is optional. If not provided, the client uses `defaultImageType` from config, or `jpg` by default.
1200
+ | Parameter | Type | Required | Description |
1201
+ |---|---|---:|---|
1202
+ | `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
452
1203
 
453
- ### uploadImage
1204
+ #### Usage
454
1205
 
455
1206
  ```ts
456
- await safeCdx.uploadImage(preSignedURL, imageBody, "image/jpeg");
1207
+ const details = await safeCdx.getResultDetails(userTestResultId);
457
1208
  ```
458
1209
 
459
- Uploads the image directly to the pre-signed URL.
1210
+ #### API request sent internally
460
1211
 
461
- This method does not call a Safe CDX API route.
1212
+ ```json
1213
+ {
1214
+ "Data": {
1215
+ "UserTestResultId": "<USER_TEST_RESULT_ID>"
1216
+ }
1217
+ }
1218
+ ```
462
1219
 
463
- ### updateCvmlStatus
1220
+ #### API response example
464
1221
 
465
- ```ts
466
- const result = await safeCdx.updateCvmlStatus(
467
- imageOfCaptureId,
468
- "ImageProcessing"
469
- );
1222
+ ```json
1223
+ {
1224
+ "success": true,
1225
+ "data": {
1226
+ "cvmlUploadId": "6a104bba6068dd9a213db810",
1227
+ "userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
1228
+ "tenantID": "healthcheck",
1229
+ "isSelfAssessment": false,
1230
+ "status": "Started",
1231
+ "cvmlStatus": "Retake",
1232
+ "gtin": "860002060439",
1233
+ "testInfoHeader": "Winx Health",
1234
+ "testName": "Winx UTI Test + Treat",
1235
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
1236
+ "imageOfCaptureUploadAt": "2026-05-22T12:31:18.333Z",
1237
+ "imageOfCaptureResults": [
1238
+ {
1239
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
1240
+ "uploadAt": "2026-05-22T12:31:18.333Z",
1241
+ "cvmlStatus": "Retake",
1242
+ "parsed": {
1243
+ "outcome": "Retake",
1244
+ "responseCode": "Err4",
1245
+ "responseMessage": "ml_result_code_error_04_msg",
1246
+ "responseTitle": "ml_result_code_error_04_title"
1247
+ }
1248
+ }
1249
+ ],
1250
+ "result": [],
1251
+ "recordedDate": "2026-05-22T12:27:38.731Z",
1252
+ "cvmlTestName": "winx-uti",
1253
+ "isCVMLBackground": false,
1254
+ "isCVMLResult": true,
1255
+ "isSelfReportingPostTest": false,
1256
+ "isSelfReportingPreTest": false,
1257
+ "viewed": false,
1258
+ "finalized": false,
1259
+ "schemaVersion": 1,
1260
+ "capture": {
1261
+ "retryCount": 1,
1262
+ "retryLimit": 2,
1263
+ "failoverEnabled": true,
1264
+ "lastCvmlStatus": "Retake",
1265
+ "failoverTriggered": false
1266
+ },
1267
+ "questionnaireSnapshot": {
1268
+ "isValid": [],
1269
+ "analyte": [
1270
+ {
1271
+ "_id": "1",
1272
+ "displayOrder": 1,
1273
+ "display": "CVML",
1274
+ "name": "Leukocyte",
1275
+ "question": "What is the Leukocyte reading?",
1276
+ "image": "WinxLeuk.png",
1277
+ "cvmlOrder": 1,
1278
+ "responses": [
1279
+ {
1280
+ "value": "Negative -",
1281
+ "displayOrder": 1,
1282
+ "storedValue": "Neg",
1283
+ "cvmlValue": "Neg",
1284
+ "score": "0"
1285
+ },
1286
+ {
1287
+ "value": "Trace 15+-",
1288
+ "displayOrder": 2,
1289
+ "storedValue": "15+-",
1290
+ "cvmlValue": "15+-",
1291
+ "score": "3"
1292
+ },
1293
+ {
1294
+ "value": "Positive 70+",
1295
+ "displayOrder": 3,
1296
+ "storedValue": "70+",
1297
+ "cvmlValue": "70+",
1298
+ "score": "4"
1299
+ }
1300
+ ]
1301
+ },
1302
+ {
1303
+ "_id": "2",
1304
+ "displayOrder": 2,
1305
+ "display": "CVML",
1306
+ "name": "Nitrite",
1307
+ "question": "What is the Nitrite reading?",
1308
+ "image": "WinxNitr.png",
1309
+ "cvmlOrder": 2,
1310
+ "responses": [
1311
+ {
1312
+ "value": "Negative",
1313
+ "displayOrder": 1,
1314
+ "storedValue": "Neg",
1315
+ "cvmlValue": "Neg",
1316
+ "score": "0"
1317
+ },
1318
+ {
1319
+ "value": "Positive Low+",
1320
+ "displayOrder": 2,
1321
+ "storedValue": "Low+",
1322
+ "cvmlValue": "Low+",
1323
+ "score": "2"
1324
+ },
1325
+ {
1326
+ "value": "Positive High++",
1327
+ "displayOrder": 3,
1328
+ "storedValue": "High++",
1329
+ "cvmlValue": "High++",
1330
+ "score": "2"
1331
+ }
1332
+ ]
1333
+ }
1334
+ ]
1335
+ },
1336
+ "resumed": false,
1337
+ "language": "en",
1338
+ "labVendor": "Winx Health",
1339
+ "labTestType": "Home Test (Rapid)",
1340
+ "statusResultsText": "",
1341
+ "externalId": "test-user@example.com",
1342
+ "overallResult": "Unknown",
1343
+ "positiveAnalytes": [],
1344
+ "_id": "6a104bba6068dd9a213db810",
1345
+ "modifier": "system",
1346
+ "created": "2026-05-22T12:27:38.731Z",
1347
+ "modified": "2026-05-22T12:33:17.308Z"
1348
+ },
1349
+ "message": "Success",
1350
+ "code": 0
1351
+ }
470
1352
  ```
471
1353
 
472
- Updates the CVML processing status for a captured image.
1354
+ ---
1355
+
1356
+ ### `getResultPdf(...)`
473
1357
 
474
- This method returns a raw service result.
1358
+ Requests the PDF result for a specific test attempt.
475
1359
 
476
- ### getCvmlResults
1360
+ #### Signature
477
1361
 
478
1362
  ```ts
479
- const result = await safeCdx.getCvmlResults(imageOfCaptureId);
1363
+ safeCdx.getResultPdf(userTestResultId: string): Promise<APIResponse<GetResultPdfData>>
480
1364
  ```
481
1365
 
482
- Polls CVML analysis results for a captured image.
1366
+ `GetResultPdfData` is:
483
1367
 
484
- This method returns a raw service result.
1368
+ ```ts
1369
+ type GetResultPdfData = SafeAPIResponse<string>;
1370
+ ```
1371
+
1372
+ #### Parameters
485
1373
 
486
- ### getPendingResults
1374
+ | Parameter | Type | Required | Description |
1375
+ |---|---|---:|---|
1376
+ | `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
1377
+
1378
+ #### Usage
487
1379
 
488
1380
  ```ts
489
- const pending = await safeCdx.getPendingResults();
1381
+ const pdf = await safeCdx.getResultPdf(userTestResultId);
490
1382
  ```
491
1383
 
492
- Returns pending or incomplete test results.
1384
+ #### API request sent internally
1385
+
1386
+ ```json
1387
+ {
1388
+ "Data": {
1389
+ "UserTestResultId": "<USER_TEST_RESULT_ID>"
1390
+ }
1391
+ }
1392
+ ```
1393
+
1394
+ #### API response example
1395
+
1396
+ ```json
1397
+ {
1398
+ "Data": {
1399
+ "success": true,
1400
+ "data": "https://safe-manual-test-results-us-west-2-quality-speed.s3.us-west-2.amazonaws.com/archive/2026/05/22/12/48/7ecfd730-c4c3-4cd6-8b12-4210b9a2db28.pdf?X-Amz-Expires=900&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***&X-Amz-Date=20260522T124843Z&X-Amz-SignedHeaders=host&X-Amz-Signature=***",
1401
+ "message": "Success",
1402
+ "code": 0
1403
+ },
1404
+ "ErrorMessage": null,
1405
+ "IsOK": true
1406
+ }
1407
+ ```
493
1408
 
494
- A custom payload may also be provided:
1409
+ ---
1410
+
1411
+ ### `getImageCaptureUrl(...)`
1412
+
1413
+ Requests image capture data for a specific test attempt. This endpoint returns `SafeAPIResponse<T>` directly.
1414
+
1415
+ #### Signature
495
1416
 
496
1417
  ```ts
497
- const pending = await safeCdx.getPendingResults({
498
- ExcludeStatus: "Started",
499
- });
1418
+ safeCdx.getImageCaptureUrl(
1419
+ userTestResultId: string
1420
+ ): Promise<GetImageCaptureUrlResponse>
500
1421
  ```
501
1422
 
502
- ### getLastResults
1423
+ `GetImageCaptureUrlResponse` is:
503
1424
 
504
1425
  ```ts
505
- const last = await safeCdx.getLastResults();
1426
+ type GetImageCaptureUrlResponse =
1427
+ SafeAPIResponse<string>;
506
1428
  ```
507
1429
 
508
- Returns the most recent test result for the authenticated patient.
1430
+ #### Parameters
1431
+
1432
+ | Parameter | Type | Required | Description |
1433
+ |---|---|---:|---|
1434
+ | `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
509
1435
 
510
- ### getTestHistory
1436
+ #### Usage
511
1437
 
512
1438
  ```ts
513
- const history = await safeCdx.getTestHistory();
1439
+ const imageUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
514
1440
  ```
515
1441
 
516
- Returns the test history for the authenticated patient.
1442
+ #### API request sent internally
1443
+
1444
+ ```json
1445
+ {
1446
+ "Data": {
1447
+ "UserTestResultId": "<USER_TEST_RESULT_ID>"
1448
+ }
1449
+ }
1450
+ ```
517
1451
 
518
- ### getResultDetails
1452
+ #### API response example
519
1453
 
520
- ```ts
521
- const details = await safeCdx.getResultDetails(userTestResultId);
1454
+ ```json
1455
+ {
1456
+ "success": true,
1457
+ "data": "https://sf-us-west-2-lower-neural-cdx-img-uploads-quality.s3.us-west-2.amazonaws.com/healthcheck/winx-uti/860002060439_6a104bba6068dd9a213db810-1.jpg?X-Amz-Expires=900&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***&X-Amz-Date=20260522T125017Z&X-Amz-SignedHeaders=host&X-Amz-Signature=***",
1458
+ "message": "Success",
1459
+ "code": 0
1460
+ }
522
1461
  ```
523
1462
 
524
- Returns detailed result data for a specific test attempt.
1463
+ ---
1464
+
1465
+ ### `resumeFlow(...)`
525
1466
 
526
- This method returns a raw service result.
1467
+ Marks a test attempt as resumed or not resumed.
527
1468
 
528
- ### getResultPdf
1469
+ #### Signature
529
1470
 
530
1471
  ```ts
531
- const pdf = await safeCdx.getResultPdf({
532
- UserTestResultId: userTestResultId,
533
- });
1472
+ safeCdx.resumeFlow(
1473
+ userTestResultId: string,
1474
+ resumed?: boolean
1475
+ ): Promise<APIResponse<ResumeFlowData>>
534
1476
  ```
535
1477
 
536
- Generates or retrieves the PDF report for a specific test result.
537
-
538
- ### getImageCaptureUrl
1478
+ `ResumeFlowData` is:
539
1479
 
540
1480
  ```ts
541
- const imageUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
1481
+ type ResumeFlowData =
1482
+ SafeAPIResponse<ResumeFlowResult>;
542
1483
  ```
543
1484
 
544
- Returns the image capture URL for a specific test result.
1485
+ #### Parameters
545
1486
 
546
- This method returns a raw service result.
1487
+ | Parameter | Type | Required | Description |
1488
+ |---|---|---:|---|
1489
+ | `userTestResultId` | `string` | Yes | Identifier of the test attempt. |
1490
+ | `resumed` | `boolean` | No | Resume state sent to the API. Defaults to `true`. |
547
1491
 
548
- ### getAnalytics
1492
+ #### Usage
549
1493
 
550
1494
  ```ts
551
- const analytics = await safeCdx.getAnalytics();
1495
+ const response = await safeCdx.resumeFlow(userTestResultId);
552
1496
  ```
553
1497
 
554
- Returns analytics data for test results.
1498
+ ```ts
1499
+ const response = await safeCdx.resumeFlow(
1500
+ userTestResultId,
1501
+ true
1502
+ );
1503
+ ```
1504
+
1505
+ #### API request sent internally
1506
+
1507
+ ```json
1508
+ {
1509
+ "Data": {
1510
+ "UserTestResultId": "<USER_TEST_RESULT_ID>",
1511
+ "Resumed": true
1512
+ }
1513
+ }
1514
+ ```
555
1515
 
556
- A custom payload may also be provided:
1516
+ #### API response example
1517
+
1518
+ ```json
1519
+ {
1520
+ "Data": {
1521
+ "success": true,
1522
+ "data": {
1523
+ "cvmlStatus": "Retake",
1524
+ "reportType": "SELF",
1525
+ "resumed": true,
1526
+ "failoverTriggered": false,
1527
+ "showValidity": false,
1528
+ "_id": "6a02f6da6068dd9a213db7cb"
1529
+ },
1530
+ "message": "Success",
1531
+ "code": 0
1532
+ },
1533
+ "ErrorMessage": null,
1534
+ "IsOK": true
1535
+ }
1536
+ ```
1537
+
1538
+ ---
1539
+
1540
+ ### `submitAnswers(...)`
1541
+
1542
+ Submits analyte answers for a test attempt. This method accepts a structured object because the request contains a collection of answer entries.
1543
+
1544
+ #### Signature
557
1545
 
558
1546
  ```ts
559
- const analytics = await safeCdx.getAnalytics(payload);
1547
+ safeCdx.submitAnswers(
1548
+ submission: SubmitAnswersRequest
1549
+ ): Promise<APIResponse<SubmitAnswersData>>
560
1550
  ```
561
1551
 
562
- ### resumeFlow
1552
+ `SubmitAnswersData` is:
563
1553
 
564
1554
  ```ts
565
- const resumed = await safeCdx.resumeFlow(userTestResultId);
1555
+ type SubmitAnswersData =
1556
+ SafeAPIResponse<EntityReferenceData>;
566
1557
  ```
567
1558
 
568
- Marks a test attempt as resumed.
1559
+ #### Parameters
1560
+
1561
+ | Parameter | Type | Required | Description |
1562
+ |---|---|---:|---|
1563
+ | `submission.UserTestResultId` | `string` | Yes | Identifier of the test attempt. |
1564
+ | `submission.Result` | `AnswerResult[]` | Yes | Answer entries submitted for the test attempt. |
569
1565
 
570
- The second argument can be used to explicitly set the resumed value:
1566
+ #### Usage
571
1567
 
572
1568
  ```ts
573
- const resumed = await safeCdx.resumeFlow(userTestResultId, true);
1569
+ const response = await safeCdx.submitAnswers({
1570
+ UserTestResultId: userTestResultId,
1571
+ Result: [
1572
+ {
1573
+ Analyte: "Purchase",
1574
+ ReportedValue: "drug store",
1575
+ StoredValue: "drug store",
1576
+ Score: "0"
1577
+ }
1578
+ ]
1579
+ });
574
1580
  ```
575
1581
 
576
- ### submitAnswers
1582
+ #### API request sent internally
577
1583
 
578
- ```ts
579
- const result = await safeCdx.submitAnswers(payload);
1584
+ ```json
1585
+ {
1586
+ "Data": {
1587
+ "UserTestResultId": "<USER_TEST_RESULT_ID>",
1588
+ "Result": [
1589
+ {
1590
+ "Analyte": "Purchase",
1591
+ "ReportedValue": "drug store",
1592
+ "StoredValue": "drug store",
1593
+ "Score": "0"
1594
+ }
1595
+ ]
1596
+ }
1597
+ }
1598
+ ```
1599
+
1600
+ #### API response example
1601
+
1602
+ ```json
1603
+ {
1604
+ "Data": {
1605
+ "success": true,
1606
+ "data": {
1607
+ "_id": "6a104bba6068dd9a213db810"
1608
+ },
1609
+ "message": "Success",
1610
+ "code": 0
1611
+ },
1612
+ "ErrorMessage": null,
1613
+ "IsOK": true
1614
+ }
580
1615
  ```
581
1616
 
582
- Submits analyte answers for a test attempt.
1617
+ ---
1618
+
1619
+ ### `finalizeTest(...)`
583
1620
 
584
- `Result` should contain at least one answer item. The connector does not generate analyte answers automatically.
1621
+ Finalizes the test attempt identified by `userTestResultId`.
585
1622
 
586
- ### finalizeTest
1623
+ #### Signature
587
1624
 
588
1625
  ```ts
589
- const result = await safeCdx.finalizeTest(payload);
1626
+ safeCdx.finalizeTest(userTestResultId: string): Promise<APIResponse<FinalizeTestData>>
590
1627
  ```
591
1628
 
592
- Finalizes a test attempt with CTA, indication, and decision results.
1629
+ #### Parameters
1630
+
1631
+ | Parameter | Type | Required | Description |
1632
+ |---|---|---:|---|
1633
+ | `userTestResultId` | `string` | Yes | Identifier of the test attempt to finalize. |
593
1634
 
594
- The payload should include:
1635
+ #### Usage
1636
+
1637
+ ```ts
1638
+ const response = await safeCdx.finalizeTest(userTestResultId);
1639
+ ```
595
1640
 
596
- - `UserTestResultId`
597
- - `ctaResult`
598
- - `indicationResult`
599
- - `decisionResult`
1641
+ #### API request sent internally
1642
+
1643
+ ```json
1644
+ {
1645
+ "Data": {
1646
+ "UserTestResultId": "<USER_TEST_RESULT_ID>"
1647
+ }
1648
+ }
1649
+ ```
600
1650
 
601
1651
 
602
1652
  ## Notes
603
1653
 
604
- - The connector does not perform login.
605
- - The connector uses `HCLoginClient` for authenticated request headers.
1654
+ - `HCSafeCDXClient` documents the authenticated patient workflow supported by the current client.
1655
+ - The connector does not perform login; it uses `HCLoginClient` for authenticated patient headers.
1656
+ - Safe CDX uses its own service URL derived from `loginClient.getEnvironment()`.
606
1657
  - The connector does not use `loginClient.getBaseUrl()` for Safe CDX API routes.
607
- - Safe CDX routes use the Safe CDX environment host from `SafeCDXConfig`.
608
- - Standard Safe CDX API methods use the shared `HttpClient`.
609
1658
  - POST payloads are wrapped internally as `{ Data: payload }`.
610
- - The caller should pass only the inner payload to SDK methods.
611
- - Image upload uses the pre-signed URL directly and sends the raw file body.
612
- - Authorization headers should not be sent to the pre-signed upload URL.
613
- - Pre-signed URLs are time-bound and should not be logged in production.
1659
+ - Patient tenant and identity context are supplied by the backend from the authenticated request.
1660
+ - API responses are returned in their endpoint-defined shape without being unwrapped or transformed by `HCSafeCDXClient`.
1661
+ - Direct image upload uses the pre-signed URL and sends the raw file body without Health Cloud authorization headers.