@healthcloudai/hc-safe-cdx 0.1.3 → 0.2.0

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
@@ -9,6 +9,7 @@ The connector follows the same shared-client pattern used by other Health Cloud
9
9
  - `HCSafeCDXClient` builds Safe CDX URLs, wraps request payloads, and exposes one method per Safe CDX route.
10
10
 
11
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
+ Like `HCHealthRecordClient`, `HCSafeCDXClient` reads the configured environment from `HCLoginClient` and derives its own service base URL internally.
12
13
 
13
14
  Image upload is handled separately because the upload target is a pre-signed S3 URL, not a Safe CDX API route.
14
15
 
@@ -33,14 +34,10 @@ const http = new FetchClient();
33
34
 
34
35
  const loginClient = new HCLoginClient(http);
35
36
 
36
- // Configure and authenticate the login client first.
37
- // Exact login/configuration calls depend on the login connector setup used by the app.
37
+ loginClient.configure("healthcheck", "dev");
38
+ await loginClient.login(email, password);
38
39
 
39
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
40
- environment: "dev",
41
- defaultLanguage: "en",
42
- defaultImageType: "jpg",
43
- });
40
+ const safeCdx = new HCSafeCDXClient(http, loginClient);
44
41
  ```
45
42
 
46
43
  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()`.
@@ -83,9 +80,10 @@ The client receives both the HTTP client and the login client:
83
80
  const http = new FetchClient();
84
81
  const loginClient = new HCLoginClient(http);
85
82
 
86
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
87
- environment: "dev",
88
- });
83
+ loginClient.configure("healthcheck", "dev");
84
+ await loginClient.login(email, password);
85
+
86
+ const safeCdx = new HCSafeCDXClient(http, loginClient);
89
87
  ```
90
88
 
91
89
  The shared `HttpClient` is responsible for executing HTTP requests.
@@ -94,7 +92,7 @@ The `HCLoginClient` is responsible for the authenticated request headers.
94
92
 
95
93
  The Safe CDX client is responsible for:
96
94
 
97
- - resolving the Safe CDX environment host
95
+ - deriving the Safe CDX base URL from the login client's configured environment
98
96
  - building Safe CDX URLs
99
97
  - adding auth headers from `HCLoginClient`
100
98
  - adding optional API key headers when configured
@@ -104,31 +102,33 @@ The Safe CDX client is responsible for:
104
102
 
105
103
  The Safe CDX client does not create its own internal `fetch` helpers for standard Safe CDX API routes.
106
104
 
107
- ## Environment Configuration
105
+ ## Base URL Configuration
108
106
 
109
- The connector supports the following environments:
107
+ Safe CDX base URL configuration follows the same constructor pattern as the Health Record connector:
110
108
 
111
109
  ```ts
112
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
113
- environment: "dev",
114
- });
110
+ loginClient.configure("healthcheck", "dev");
111
+
112
+ const safeCdx = new HCSafeCDXClient(http, loginClient);
115
113
  ```
116
114
 
117
- Available environments:
115
+ The Safe CDX client reads the environment from:
118
116
 
119
117
  ```ts
120
- "dev" | "uat" | "prod"
118
+ loginClient.getEnvironment()
121
119
  ```
122
120
 
123
- Safe CDX environment host mapping is handled internally by the connector:
121
+ Safe CDX still uses its own service base URL. It does not reuse `loginClient.getBaseUrl()`.
122
+
123
+ Environment mapping is handled internally by the connector:
124
124
 
125
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
126
+ dev -> https://dev-api-hcs.healthcloud-services.com/api/console/hcservice/safecdx
127
+ uat -> https://uat-api-hcs.healthcloud-services.com/api/console/hcservice/safecdx
128
+ prod -> https://api-hcs.healthcloud-services.com/api/console/hcservice/safecdx
129
129
  ```
130
130
 
131
- These hosts are separate from the login connector base URL.
131
+ These base URLs are separate from the login connector base URL.
132
132
 
133
133
  ## Authentication
134
134
 
@@ -149,12 +149,10 @@ const http = new FetchClient();
149
149
 
150
150
  const loginClient = new HCLoginClient(http);
151
151
 
152
- // loginClient.configure(...)
153
- // await loginClient.loginPatient(...)
152
+ loginClient.configure("healthcheck", "dev");
153
+ await loginClient.login(email, password);
154
154
 
155
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
156
- environment: "dev",
157
- });
155
+ const safeCdx = new HCSafeCDXClient(http, loginClient);
158
156
  ```
159
157
 
160
158
  Safe CDX does not manually receive a token. It does not expose `setAccessToken()`. Auth is inherited from the configured login client.
@@ -228,9 +226,7 @@ If `IsOK` is `false`, the client throws `SafeCDXError`.
228
226
 
229
227
  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.
230
228
 
231
- Some endpoints return a raw service result instead of the standard `ApiResponse` envelope. Those methods return the raw result directly.
232
-
233
- Raw result methods include:
229
+ For the following methods, the client returns the received response directly without applying `ApiResponse` validation:
234
230
 
235
231
  ```ts
236
232
  updateCvmlStatus(...)
@@ -264,13 +260,10 @@ const http = new FetchClient();
264
260
 
265
261
  const loginClient = new HCLoginClient(http);
266
262
 
267
- // Configure and authenticate loginClient first.
263
+ loginClient.configure("healthcheck", "dev");
264
+ await loginClient.login(email, password);
268
265
 
269
- const safeCdx = new HCSafeCDXClient(http, loginClient, {
270
- environment: "dev",
271
- defaultLanguage: "en",
272
- defaultImageType: "jpg",
273
- });
266
+ const safeCdx = new HCSafeCDXClient(http, loginClient);
274
267
  ```
275
268
 
276
269
  ### 1. Get available test profiles
@@ -336,30 +329,6 @@ await safeCdx.submitAnswers({
336
329
  ```ts
337
330
  await safeCdx.finalizeTest({
338
331
  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: [
357
- {
358
- indicationId: "4",
359
- value: "4",
360
- },
361
- ],
362
- },
363
332
  });
364
333
  ```
365
334
 
@@ -404,22 +373,201 @@ These values should be stored by the consuming app and passed to the next SDK me
404
373
  ### getTestProfileByGTIN
405
374
 
406
375
  ```ts
407
- const profile = await safeCdx.getTestProfileByGTIN(gtin, language);
376
+ const profile = await safeCdx.getTestProfileByGTIN(gtin);
408
377
  ```
409
378
 
410
379
  Resolves a test profile by GTIN barcode.
411
380
 
412
- `language` is optional. If not provided, the client uses `defaultLanguage` from config when available.
381
+ #### API Response example
413
382
 
414
-
415
- ### getTestProfile
416
-
417
- ```ts
418
- const profile = await safeCdx.getTestProfile(payload);
383
+ ```json
384
+ {
385
+ "Data": {
386
+ "ID": "6a104bba6068dd9a213db810",
387
+ "FHIRID": null,
388
+ "CQLID": null,
389
+ "TestName": "UTI Test + Treat",
390
+ "CustomTestName": "UTI Test + Treat",
391
+ "IsDeactivated": false,
392
+ "IsOrderable": true,
393
+ "EnablePublicHealthReporting": true,
394
+ "IsSelfAssessmentMode": true,
395
+ "CaptureMethod": 0,
396
+ "IsMLDisabled": false,
397
+ "ShortName": "UTI Test + Treat",
398
+ "DescriptionHTML": null,
399
+ "OrderableName": "Winx Health",
400
+ "VendorName": null,
401
+ "ManufactureName": null,
402
+ "VendorTestID": "0123",
403
+ "Sku": "SH00131",
404
+ "LabTestType": null,
405
+ "TestClassification": "NOT_SPECIFIED",
406
+ "KitRegistrationRequired": false,
407
+ "RequiresProviderVerificationOfResults": false,
408
+ "DiagnosticProfile": {
409
+ "_id": "6780031a991814469e88237f",
410
+ "Created": "2025-01-09T17:10:50.8Z",
411
+ "Modified": "2026-05-06T12:39:08.732Z",
412
+ "TestInfo": {
413
+ "gtin": "860002060439",
414
+ "testInfoHeader": "Winx Health",
415
+ "productName": "Winx UTI Test + Treat",
416
+ "testCategory": "Rapid",
417
+ "fdaStatus": "EUA",
418
+ "cvmlTestName": "winx-uti",
419
+ "cvmlDuration": 20,
420
+ "isCVMLResult": true,
421
+ "accountIds": [
422
+ "winxhealth",
423
+ "safehealth",
424
+ "test-account",
425
+ "speedyswab",
426
+ "healthcheck"
427
+ ],
428
+ "cvmlRetryLimit": 2,
429
+ "failoverEnabled": true,
430
+ "cvmlPollingPolicy": "FullResult"
431
+ },
432
+ "Analyte": [
433
+ {
434
+ "_id": "1",
435
+ "DisplayOrder": 1,
436
+ "Display": "CVML",
437
+ "Name": "Leukocyte",
438
+ "Question": "What is the Leukocyte reading?",
439
+ "Image": "/analyte/WinxLeuk.png",
440
+ "Responses": [
441
+ {
442
+ "value": "Negative -",
443
+ "displayOrder": 1,
444
+ "storedValue": "Neg",
445
+ "cvmlValue": "Neg",
446
+ "score": "0"
447
+ },
448
+ {
449
+ "value": "Positive 70+",
450
+ "displayOrder": 3,
451
+ "storedValue": "70+",
452
+ "cvmlValue": "70+",
453
+ "score": "4"
454
+ }
455
+ ]
456
+ },
457
+ {
458
+ "_id": "2",
459
+ "DisplayOrder": 2,
460
+ "Display": "CVML",
461
+ "Name": "Nitrite",
462
+ "Question": "What is the Nitrite reading?",
463
+ "Image": "/analyte/WinxNitr.png",
464
+ "Responses": [
465
+ {
466
+ "value": "Negative",
467
+ "displayOrder": 1,
468
+ "storedValue": "Neg",
469
+ "cvmlValue": "Neg",
470
+ "score": "0"
471
+ },
472
+ {
473
+ "value": "Positive High++",
474
+ "displayOrder": 3,
475
+ "storedValue": "High++",
476
+ "cvmlValue": "High++",
477
+ "score": "2"
478
+ }
479
+ ]
480
+ }
481
+ ],
482
+ "Cta": [
483
+ {
484
+ "id": "1",
485
+ "title": "No UTI is detected",
486
+ "instructions": "If you're still having symptoms, we recommend talking to your doctor about additional testing.",
487
+ "linkType": "url",
488
+ "linkText": "Find a Doctor",
489
+ "linkValue": "https://book.zocdoc.com/?utm_source=winx&utm_medium=app&utm_campaign=testandtreat"
490
+ }
491
+ ],
492
+ "Indication": [
493
+ {
494
+ "id": "4",
495
+ "title": "Indication",
496
+ "text": "Results suggest you have a UTI",
497
+ "recommendTitle": "Recommendation",
498
+ "recommendText": "Drink lots of water and connect with a doctor to discuss treatment and next steps.",
499
+ "ctaId": "2"
500
+ }
501
+ ],
502
+ "Decision": [
503
+ {
504
+ "calculation": "Total: analyte.responses.score",
505
+ "results": [
506
+ {
507
+ "indicationId": "4",
508
+ "value": "4"
509
+ }
510
+ ]
511
+ }
512
+ ]
513
+ },
514
+ "ProductAssetDetail": {
515
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/ProductAsset/Image/47197f0c75844ba3b11335f7bfe1959b-639062362985442065.png",
516
+ "body": "<p></p>"
517
+ },
518
+ "DisclaimerDetail": {
519
+ "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>",
520
+ "title": "Record Results",
521
+ "continueButtonTitle": "Continue",
522
+ "showDisclaimer": false
523
+ },
524
+ "ScanImageDetail": {
525
+ "ImageUrl": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/ScanImage/Image/590172d7548e4b3eb09bfe42679fc6b1-638900206144635626.png"
526
+ },
527
+ "Instructions": [
528
+ {
529
+ "NavigationTitle": "Instructional Video",
530
+ "Type": "CollectionInstructionOnly",
531
+ "Title": "Instructional Video",
532
+ "ButtonTitle": "Continue",
533
+ "TimeInSeconds": 0,
534
+ "VideoUrl": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/CollectionInstruction/Video/3a74d7091d65455ab675f8ddd5f9d9d5-638918232791589391.mp4",
535
+ "SequenceOrder": 0
536
+ }
537
+ ],
538
+ "BillingInfo": {
539
+ "Taxable": false
540
+ },
541
+ "Associations": null,
542
+ "TestReportAsset": null,
543
+ "Articles": [],
544
+ "TestResults": [
545
+ {
546
+ "PatientTestResult": 0,
547
+ "ResultTitle": "Positive",
548
+ "ResultDisplay": "Positive",
549
+ "TreatmentPlanDescription": "",
550
+ "IsReturnToDashboardAllowed": false,
551
+ "IsOrderTestAllowed": false,
552
+ "IsFindTestRetailerAllowed": false
553
+ }
554
+ ],
555
+ "GS1GTINs": null,
556
+ "Included": null,
557
+ "LOINCCodes": null,
558
+ "TestLOINCCodes": null,
559
+ "TestSNOMEDCTCodes": null,
560
+ "Created": "2024-12-23T18:00:48.619Z",
561
+ "Modified": "2026-05-13T17:07:07.048Z",
562
+ "CreatedByID": null,
563
+ "ModifiedByID": null,
564
+ "TenantID": null
565
+ },
566
+ "ErrorMessage": null,
567
+ "IsOK": true
568
+ }
419
569
  ```
420
570
 
421
- Returns the full test profile for a given payload.
422
-
423
571
  ### getTestProfilesByAccount
424
572
 
425
573
  ```ts
@@ -436,6 +584,61 @@ const profiles = await safeCdx.getTestProfilesByAccount({
436
584
  });
437
585
  ```
438
586
 
587
+ #### API Response example
588
+
589
+ ```json
590
+ {
591
+ "Data": {
592
+ "success": true,
593
+ "data": [
594
+ {
595
+ "gtin": "810172700093",
596
+ "productName": "Speedy Swab COVID-19 + Flu Self-Test",
597
+ "description": "",
598
+ "testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/4307c803740644ab8c4594f69783c1d8-638694157517533356.png",
599
+ "language": "en",
600
+ "registerTestDetail": {
601
+ "title": "Scan the barcode",
602
+ "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>",
603
+ "buttonTitle": "Scan Code",
604
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/1ccc293f924a4c5bb2c5fc76f474c625-638769599179275771.png"
605
+ }
606
+ },
607
+ {
608
+ "gtin": "860002060439",
609
+ "productName": "Winx UTI Test + Treat",
610
+ "description": "",
611
+ "testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/36649cb6377c435a9e4f83af0f2fea46-639058689126409862.png",
612
+ "language": "en",
613
+ "registerTestDetail": {
614
+ "title": "",
615
+ "description": "<p></p>",
616
+ "buttonTitle": "Scan Barcode on the Box",
617
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/ad72c9b8c3324c1cb7aa9dbf29e46d64-638905990491115782.png"
618
+ }
619
+ },
620
+ {
621
+ "gtin": "850024942325",
622
+ "productName": "Vaginal Health pH Test + Treat",
623
+ "description": "",
624
+ "testKitImageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/3cebedbb69cb4455af33da5ab524ec93-639058688331122684.png",
625
+ "language": "en",
626
+ "registerTestDetail": {
627
+ "title": "",
628
+ "description": "<p></p>",
629
+ "buttonTitle": "Scan Barcode on the Box",
630
+ "imageURL": "https://safe-content-cache-us-west-2-quality-speed.s3-us-west-2.amazonaws.com/LabTestOrderable/RegisterTest/Image/1475ae0004084d749ee2dfc9123af952-638905989776168388.png"
631
+ }
632
+ }
633
+ ],
634
+ "message": "Success",
635
+ "code": 0
636
+ },
637
+ "ErrorMessage": null,
638
+ "IsOK": true
639
+ }
640
+ ```
641
+
439
642
  ### createUploadUrl
440
643
 
441
644
  ```ts
@@ -448,7 +651,24 @@ const upload = await safeCdx.createUploadUrl(
448
651
 
449
652
  Creates a pre-signed image upload URL.
450
653
 
451
- `imageType` is optional. If not provided, the client uses `defaultImageType` from config, or `jpg` by default.
654
+ `imageType` is optional. If not provided, the client sends `jpg`.
655
+
656
+ #### API Response example
657
+
658
+ ```json
659
+ {
660
+ "Data": {
661
+ "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=***",
662
+ "Metadata": {
663
+ "UploadId": "860002060439_6a104bba6068dd9a213db810-1",
664
+ "Type": "Rapid Test Kit Upload",
665
+ "File": "860002060439_6a104bba6068dd9a213db810-1.jpg"
666
+ }
667
+ },
668
+ "ErrorMessage": null,
669
+ "IsOK": true
670
+ }
671
+ ```
452
672
 
453
673
  ### uploadImage
454
674
 
@@ -471,7 +691,20 @@ const result = await safeCdx.updateCvmlStatus(
471
691
 
472
692
  Updates the CVML processing status for a captured image.
473
693
 
474
- This method returns a raw service result.
694
+ The client returns the received response directly without applying `ApiResponse` validation.
695
+
696
+ #### API Response example
697
+
698
+ ```json
699
+ {
700
+ "success": true,
701
+ "data": {
702
+ "_id": "6a104bba6068dd9a213db810"
703
+ },
704
+ "message": "Success",
705
+ "code": 0
706
+ }
707
+ ```
475
708
 
476
709
  ### getCvmlResults
477
710
 
@@ -481,7 +714,51 @@ const result = await safeCdx.getCvmlResults(imageOfCaptureId);
481
714
 
482
715
  Polls CVML analysis results for a captured image.
483
716
 
484
- This method returns a raw service result.
717
+ The client returns the received response directly without applying `ApiResponse` validation.
718
+
719
+ #### API Response example
720
+
721
+ ```json
722
+ {
723
+ "success": true,
724
+ "data": {
725
+ "userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
726
+ "status": "Started",
727
+ "cvmlStatus": "Retake",
728
+ "gtin": "860002060439",
729
+ "isCVMLResult": true,
730
+ "isSelfReportingPostTest": false,
731
+ "schemaVersion": 1,
732
+ "capture": {
733
+ "retryCount": 0,
734
+ "retryLimit": 0,
735
+ "failoverEnabled": true,
736
+ "lastCvmlStatus": "Retake",
737
+ "failoverTriggered": false
738
+ },
739
+ "questionnaireSnapshot": {
740
+ "isValid": []
741
+ },
742
+ "resumed": false,
743
+ "imageOfCaptureResult": {
744
+ "parsed": {
745
+ "outcome": "Retake",
746
+ "responseCode": "Err4",
747
+ "responseMessage": "ml_result_code_error_04_msg",
748
+ "responseTitle": "ml_result_code_error_04_title"
749
+ }
750
+ },
751
+ "failoverEnabled": true,
752
+ "failoverTriggered": false,
753
+ "resumable": false,
754
+ "showValidity": false,
755
+ "showError": true,
756
+ "_id": "6a104bba6068dd9a213db810"
757
+ },
758
+ "message": "Success",
759
+ "code": 0
760
+ }
761
+ ```
485
762
 
486
763
  ### getPendingResults
487
764
 
@@ -491,14 +768,104 @@ const pending = await safeCdx.getPendingResults();
491
768
 
492
769
  Returns pending or incomplete test results.
493
770
 
494
- A custom payload may also be provided:
771
+ When called without an argument, the SDK sends:
772
+
773
+ ```ts
774
+ {
775
+ Data: {
776
+ ExcludeStatus: "Invalid,Canceled",
777
+ },
778
+ }
779
+ ```
780
+
781
+ The same request can be made explicitly:
495
782
 
496
783
  ```ts
497
784
  const pending = await safeCdx.getPendingResults({
498
- ExcludeStatus: "Started",
785
+ ExcludeStatus: "Invalid,Canceled",
499
786
  });
500
787
  ```
501
788
 
789
+ #### API Response example
790
+
791
+ ```json
792
+ {
793
+ "Data": {
794
+ "success": true,
795
+ "data": [
796
+ {
797
+ "status": "Started",
798
+ "cvmlStatus": "Retake",
799
+ "gtin": "860002060439",
800
+ "testName": "Winx UTI Test + Treat",
801
+ "recordedDate": "2026-05-22T12:49:26.047Z",
802
+ "isCVMLBackground": false,
803
+ "isCVMLResult": true,
804
+ "isSelfReportingPostTest": false,
805
+ "isSelfReportingPreTest": false,
806
+ "viewed": false,
807
+ "schemaVersion": 1,
808
+ "capture": {
809
+ "retryCount": 0,
810
+ "retryLimit": 0,
811
+ "failoverEnabled": true,
812
+ "lastCvmlStatus": "Retake",
813
+ "failoverTriggered": false
814
+ },
815
+ "questionnaireSnapshot": {
816
+ "isValid": []
817
+ },
818
+ "resumed": false,
819
+ "failoverEnabled": true,
820
+ "failoverTriggered": false,
821
+ "resumable": true,
822
+ "resumeNext": "SelfReporting",
823
+ "showValidity": false,
824
+ "showError": true,
825
+ "_id": "6a104bba6068dd9a213db810"
826
+ },
827
+ {
828
+ "status": "Started",
829
+ "gtin": "810172700093",
830
+ "testName": "Speedy Swab COVID-19 + Flu Self-Test",
831
+ "recordedDate": "2026-05-22T10:11:10.181Z",
832
+ "isCVMLBackground": true,
833
+ "isCVMLResult": false,
834
+ "isSelfReportingPostTest": true,
835
+ "isSelfReportingPreTest": false,
836
+ "viewed": false,
837
+ "schemaVersion": 1,
838
+ "capture": {
839
+ "retryCount": 0,
840
+ "retryLimit": 0,
841
+ "failoverEnabled": true,
842
+ "failoverTriggered": false
843
+ },
844
+ "questionnaireSnapshot": {
845
+ "isValid": [
846
+ {
847
+ "display": "post-test",
848
+ "question": "Do you see a control line (C)?"
849
+ }
850
+ ]
851
+ },
852
+ "resumed": false,
853
+ "failoverEnabled": true,
854
+ "failoverTriggered": false,
855
+ "resumable": false,
856
+ "showValidity": false,
857
+ "showError": true,
858
+ "_id": "6a102bbe6068dd9a213db80f"
859
+ }
860
+ ],
861
+ "message": "Success",
862
+ "code": 0
863
+ },
864
+ "ErrorMessage": null,
865
+ "IsOK": true
866
+ }
867
+ ```
868
+
502
869
  ### getLastResults
503
870
 
504
871
  ```ts
@@ -507,6 +874,160 @@ const last = await safeCdx.getLastResults();
507
874
 
508
875
  Returns the most recent test result for the authenticated patient.
509
876
 
877
+ When called without an argument, the SDK sends:
878
+
879
+ ```ts
880
+ {
881
+ Data: {
882
+ ExcludeStatus: "Invalid",
883
+ },
884
+ }
885
+ ```
886
+
887
+ #### API Response example
888
+
889
+ ```json
890
+ {
891
+ "Data": {
892
+ "success": true,
893
+ "data": {
894
+ "cvmlUploadId": "6a104bba6068dd9a213db810",
895
+ "userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
896
+ "tenantID": "healthcheck",
897
+ "isSelfAssessment": false,
898
+ "status": "Started",
899
+ "cvmlStatus": "Retake",
900
+ "gtin": "860002060439",
901
+ "testInfoHeader": "Winx Health",
902
+ "testName": "Winx UTI Test + Treat",
903
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
904
+ "imageOfCaptureUploadAt": "2026-05-22T12:31:18.333Z",
905
+ "imageOfCaptureResults": [
906
+ {
907
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
908
+ "uploadAt": "2026-05-22T12:31:18.333Z",
909
+ "cvmlStatus": "Retake",
910
+ "parsed": {
911
+ "outcome": "Retake",
912
+ "responseCode": "Err4",
913
+ "responseMessage": "ml_result_code_error_04_msg",
914
+ "responseTitle": "ml_result_code_error_04_title"
915
+ }
916
+ }
917
+ ],
918
+ "resultPDF": "archive/2026/05/22/12/48/7ecfd730-c4c3-4cd6-8b12-4210b9a2db28.pdf",
919
+ "result": [
920
+ {
921
+ "analyte": "Purchase",
922
+ "reportedValue": "drug store",
923
+ "storedValue": "drug store",
924
+ "score": "0"
925
+ }
926
+ ],
927
+ "recordedDate": "2026-05-22T12:49:26.047Z",
928
+ "cvmlTestName": "winx-uti",
929
+ "isCVMLBackground": false,
930
+ "isCVMLResult": true,
931
+ "isSelfReportingPostTest": false,
932
+ "isSelfReportingPreTest": false,
933
+ "viewed": false,
934
+ "finalized": false,
935
+ "schemaVersion": 1,
936
+ "capture": {
937
+ "retryCount": 1,
938
+ "retryLimit": 2,
939
+ "failoverEnabled": true,
940
+ "lastCvmlStatus": "Retake",
941
+ "failoverTriggered": false
942
+ },
943
+ "questionnaireSnapshot": {
944
+ "isValid": [],
945
+ "analyte": [
946
+ {
947
+ "_id": "1",
948
+ "displayOrder": 1,
949
+ "display": "CVML",
950
+ "name": "Leukocyte",
951
+ "question": "What is the Leukocyte reading?",
952
+ "image": "WinxLeuk.png",
953
+ "cvmlOrder": 1,
954
+ "responses": [
955
+ {
956
+ "value": "Negative -",
957
+ "displayOrder": 1,
958
+ "storedValue": "Neg",
959
+ "cvmlValue": "Neg",
960
+ "score": "0"
961
+ },
962
+ {
963
+ "value": "Trace 15+-",
964
+ "displayOrder": 2,
965
+ "storedValue": "15+-",
966
+ "cvmlValue": "15+-",
967
+ "score": "3"
968
+ },
969
+ {
970
+ "value": "Positive 70+",
971
+ "displayOrder": 3,
972
+ "storedValue": "70+",
973
+ "cvmlValue": "70+",
974
+ "score": "4"
975
+ }
976
+ ]
977
+ },
978
+ {
979
+ "_id": "2",
980
+ "displayOrder": 2,
981
+ "display": "CVML",
982
+ "name": "Nitrite",
983
+ "question": "What is the Nitrite reading?",
984
+ "image": "WinxNitr.png",
985
+ "cvmlOrder": 2,
986
+ "responses": [
987
+ {
988
+ "value": "Negative",
989
+ "displayOrder": 1,
990
+ "storedValue": "Neg",
991
+ "cvmlValue": "Neg",
992
+ "score": "0"
993
+ },
994
+ {
995
+ "value": "Positive Low+",
996
+ "displayOrder": 2,
997
+ "storedValue": "Low+",
998
+ "cvmlValue": "Low+",
999
+ "score": "2"
1000
+ },
1001
+ {
1002
+ "value": "Positive High++",
1003
+ "displayOrder": 3,
1004
+ "storedValue": "High++",
1005
+ "cvmlValue": "High++",
1006
+ "score": "2"
1007
+ }
1008
+ ]
1009
+ }
1010
+ ]
1011
+ },
1012
+ "resumed": false,
1013
+ "language": "en",
1014
+ "labVendor": "Winx Health",
1015
+ "labTestType": "Home Test (Rapid)",
1016
+ "externalId": "test-user@example.com",
1017
+ "positiveAnalytes": [],
1018
+ "_id": "6a104bba6068dd9a213db810",
1019
+ "modifier": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
1020
+ "created": "2026-05-22T12:27:38.731Z",
1021
+ "modified": "2026-05-22T12:49:26.047Z"
1022
+ },
1023
+ "message": "Success",
1024
+ "code": 0
1025
+ },
1026
+ "ErrorMessage": null,
1027
+ "IsOK": true
1028
+ }
1029
+ ```
1030
+
510
1031
  ### getTestHistory
511
1032
 
512
1033
  ```ts
@@ -515,6 +1036,57 @@ const history = await safeCdx.getTestHistory();
515
1036
 
516
1037
  Returns the test history for the authenticated patient.
517
1038
 
1039
+ When called without an argument, the SDK sends:
1040
+
1041
+ ```ts
1042
+ {
1043
+ Data: {
1044
+ ExcludeStatus: "Invalid",
1045
+ },
1046
+ }
1047
+ ```
1048
+
1049
+ #### API Response example
1050
+
1051
+ ```json
1052
+ {
1053
+ "Data": {
1054
+ "success": true,
1055
+ "data": [
1056
+ {
1057
+ "status": "Started",
1058
+ "cvmlStatus": "Retake",
1059
+ "testInfoHeader": "Winx Health",
1060
+ "testName": "Winx UTI Test + Treat",
1061
+ "recordedDate": "2026-05-22T12:49:26.047Z",
1062
+ "viewed": false,
1063
+ "schemaVersion": 1,
1064
+ "overallResult": "Unknown",
1065
+ "positiveAnalytes": [],
1066
+ "_id": "6a104bba6068dd9a213db810",
1067
+ "created": "2026-05-22T12:27:38.731Z"
1068
+ },
1069
+ {
1070
+ "status": "Started",
1071
+ "testInfoHeader": "Biolabs International",
1072
+ "testName": "Speedy Swab COVID-19 + Flu Self-Test",
1073
+ "recordedDate": "2026-05-22T10:11:10.181Z",
1074
+ "viewed": false,
1075
+ "schemaVersion": 1,
1076
+ "overallResult": "Unknown",
1077
+ "positiveAnalytes": [],
1078
+ "_id": "6a102bbe6068dd9a213db80f",
1079
+ "created": "2026-05-22T10:11:10.181Z"
1080
+ }
1081
+ ],
1082
+ "message": "Success",
1083
+ "code": 0
1084
+ },
1085
+ "ErrorMessage": null,
1086
+ "IsOK": true
1087
+ }
1088
+ ```
1089
+
518
1090
  ### getResultDetails
519
1091
 
520
1092
  ```ts
@@ -523,7 +1095,141 @@ const details = await safeCdx.getResultDetails(userTestResultId);
523
1095
 
524
1096
  Returns detailed result data for a specific test attempt.
525
1097
 
526
- This method returns a raw service result.
1098
+ The client returns the received response directly without applying `ApiResponse` validation.
1099
+
1100
+ #### API Response example
1101
+
1102
+ ```json
1103
+ {
1104
+ "success": true,
1105
+ "data": {
1106
+ "cvmlUploadId": "6a104bba6068dd9a213db810",
1107
+ "userID": "5d00527a-2a9c-4d68-a7f4-8f154b2ca3e6",
1108
+ "tenantID": "healthcheck",
1109
+ "isSelfAssessment": false,
1110
+ "status": "Started",
1111
+ "cvmlStatus": "Retake",
1112
+ "gtin": "860002060439",
1113
+ "testInfoHeader": "Winx Health",
1114
+ "testName": "Winx UTI Test + Treat",
1115
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
1116
+ "imageOfCaptureUploadAt": "2026-05-22T12:31:18.333Z",
1117
+ "imageOfCaptureResults": [
1118
+ {
1119
+ "imageOfCapture": "860002060439_6a104bba6068dd9a213db810-1.jpg",
1120
+ "uploadAt": "2026-05-22T12:31:18.333Z",
1121
+ "cvmlStatus": "Retake",
1122
+ "parsed": {
1123
+ "outcome": "Retake",
1124
+ "responseCode": "Err4",
1125
+ "responseMessage": "ml_result_code_error_04_msg",
1126
+ "responseTitle": "ml_result_code_error_04_title"
1127
+ }
1128
+ }
1129
+ ],
1130
+ "result": [],
1131
+ "recordedDate": "2026-05-22T12:27:38.731Z",
1132
+ "cvmlTestName": "winx-uti",
1133
+ "isCVMLBackground": false,
1134
+ "isCVMLResult": true,
1135
+ "isSelfReportingPostTest": false,
1136
+ "isSelfReportingPreTest": false,
1137
+ "viewed": false,
1138
+ "finalized": false,
1139
+ "schemaVersion": 1,
1140
+ "capture": {
1141
+ "retryCount": 1,
1142
+ "retryLimit": 2,
1143
+ "failoverEnabled": true,
1144
+ "lastCvmlStatus": "Retake",
1145
+ "failoverTriggered": false
1146
+ },
1147
+ "questionnaireSnapshot": {
1148
+ "isValid": [],
1149
+ "analyte": [
1150
+ {
1151
+ "_id": "1",
1152
+ "displayOrder": 1,
1153
+ "display": "CVML",
1154
+ "name": "Leukocyte",
1155
+ "question": "What is the Leukocyte reading?",
1156
+ "image": "WinxLeuk.png",
1157
+ "cvmlOrder": 1,
1158
+ "responses": [
1159
+ {
1160
+ "value": "Negative -",
1161
+ "displayOrder": 1,
1162
+ "storedValue": "Neg",
1163
+ "cvmlValue": "Neg",
1164
+ "score": "0"
1165
+ },
1166
+ {
1167
+ "value": "Trace 15+-",
1168
+ "displayOrder": 2,
1169
+ "storedValue": "15+-",
1170
+ "cvmlValue": "15+-",
1171
+ "score": "3"
1172
+ },
1173
+ {
1174
+ "value": "Positive 70+",
1175
+ "displayOrder": 3,
1176
+ "storedValue": "70+",
1177
+ "cvmlValue": "70+",
1178
+ "score": "4"
1179
+ }
1180
+ ]
1181
+ },
1182
+ {
1183
+ "_id": "2",
1184
+ "displayOrder": 2,
1185
+ "display": "CVML",
1186
+ "name": "Nitrite",
1187
+ "question": "What is the Nitrite reading?",
1188
+ "image": "WinxNitr.png",
1189
+ "cvmlOrder": 2,
1190
+ "responses": [
1191
+ {
1192
+ "value": "Negative",
1193
+ "displayOrder": 1,
1194
+ "storedValue": "Neg",
1195
+ "cvmlValue": "Neg",
1196
+ "score": "0"
1197
+ },
1198
+ {
1199
+ "value": "Positive Low+",
1200
+ "displayOrder": 2,
1201
+ "storedValue": "Low+",
1202
+ "cvmlValue": "Low+",
1203
+ "score": "2"
1204
+ },
1205
+ {
1206
+ "value": "Positive High++",
1207
+ "displayOrder": 3,
1208
+ "storedValue": "High++",
1209
+ "cvmlValue": "High++",
1210
+ "score": "2"
1211
+ }
1212
+ ]
1213
+ }
1214
+ ]
1215
+ },
1216
+ "resumed": false,
1217
+ "language": "en",
1218
+ "labVendor": "Winx Health",
1219
+ "labTestType": "Home Test (Rapid)",
1220
+ "statusResultsText": "",
1221
+ "externalId": "test-user@example.com",
1222
+ "overallResult": "Unknown",
1223
+ "positiveAnalytes": [],
1224
+ "_id": "6a104bba6068dd9a213db810",
1225
+ "modifier": "system",
1226
+ "created": "2026-05-22T12:27:38.731Z",
1227
+ "modified": "2026-05-22T12:33:17.308Z"
1228
+ },
1229
+ "message": "Success",
1230
+ "code": 0
1231
+ }
1232
+ ```
527
1233
 
528
1234
  ### getResultPdf
529
1235
 
@@ -533,30 +1239,42 @@ const pdf = await safeCdx.getResultPdf({
533
1239
  });
534
1240
  ```
535
1241
 
536
- Generates or retrieves the PDF report for a specific test result.
1242
+ Requests the PDF result for a specific test result.
537
1243
 
538
- ### getImageCaptureUrl
1244
+ #### API Response example
539
1245
 
540
- ```ts
541
- const imageUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
1246
+ ```json
1247
+ {
1248
+ "Data": {
1249
+ "success": true,
1250
+ "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=***",
1251
+ "message": "Success",
1252
+ "code": 0
1253
+ },
1254
+ "ErrorMessage": null,
1255
+ "IsOK": true
1256
+ }
542
1257
  ```
543
1258
 
544
- Returns the image capture URL for a specific test result.
545
-
546
- This method returns a raw service result.
547
-
548
- ### getAnalytics
1259
+ ### getImageCaptureUrl
549
1260
 
550
1261
  ```ts
551
- const analytics = await safeCdx.getAnalytics();
1262
+ const imageUrl = await safeCdx.getImageCaptureUrl(userTestResultId);
552
1263
  ```
553
1264
 
554
- Returns analytics data for test results.
1265
+ Requests image capture data for a specific test result.
555
1266
 
556
- A custom payload may also be provided:
1267
+ The client returns the received response directly without applying `ApiResponse` validation.
557
1268
 
558
- ```ts
559
- const analytics = await safeCdx.getAnalytics(payload);
1269
+ #### API Response example
1270
+
1271
+ ```json
1272
+ {
1273
+ "success": true,
1274
+ "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=***",
1275
+ "message": "Success",
1276
+ "code": 0
1277
+ }
560
1278
  ```
561
1279
 
562
1280
  ### resumeFlow
@@ -573,6 +1291,28 @@ The second argument can be used to explicitly set the resumed value:
573
1291
  const resumed = await safeCdx.resumeFlow(userTestResultId, true);
574
1292
  ```
575
1293
 
1294
+ #### API Response example
1295
+
1296
+ ```json
1297
+ {
1298
+ "Data": {
1299
+ "success": true,
1300
+ "data": {
1301
+ "cvmlStatus": "Retake",
1302
+ "reportType": "SELF",
1303
+ "resumed": true,
1304
+ "failoverTriggered": false,
1305
+ "showValidity": false,
1306
+ "_id": "6a02f6da6068dd9a213db7cb"
1307
+ },
1308
+ "message": "Success",
1309
+ "code": 0
1310
+ },
1311
+ "ErrorMessage": null,
1312
+ "IsOK": true
1313
+ }
1314
+ ```
1315
+
576
1316
  ### submitAnswers
577
1317
 
578
1318
  ```ts
@@ -583,20 +1323,42 @@ Submits analyte answers for a test attempt.
583
1323
 
584
1324
  `Result` should contain at least one answer item. The connector does not generate analyte answers automatically.
585
1325
 
1326
+ #### API Response example
1327
+
1328
+ ```json
1329
+ {
1330
+ "Data": {
1331
+ "success": true,
1332
+ "data": {
1333
+ "_id": "6a104bba6068dd9a213db810"
1334
+ },
1335
+ "message": "Success",
1336
+ "code": 0
1337
+ },
1338
+ "ErrorMessage": null,
1339
+ "IsOK": true
1340
+ }
1341
+ ```
1342
+
586
1343
  ### finalizeTest
587
1344
 
588
1345
  ```ts
589
- const result = await safeCdx.finalizeTest(payload);
1346
+ const result = await safeCdx.finalizeTest({
1347
+ UserTestResultId: userTestResultId,
1348
+ });
590
1349
  ```
591
1350
 
592
- Finalizes a test attempt with CTA, indication, and decision results.
1351
+ Finalizes a test by `UserTestResultId`.
593
1352
 
594
- The payload should include:
1353
+ The SDK sends:
595
1354
 
596
- - `UserTestResultId`
597
- - `ctaResult`
598
- - `indicationResult`
599
- - `decisionResult`
1355
+ ```ts
1356
+ {
1357
+ Data: {
1358
+ UserTestResultId: userTestResultId,
1359
+ },
1360
+ }
1361
+ ```
600
1362
 
601
1363
 
602
1364
  ## Notes
@@ -604,7 +1366,7 @@ The payload should include:
604
1366
  - The connector does not perform login.
605
1367
  - The connector uses `HCLoginClient` for authenticated request headers.
606
1368
  - The connector does not use `loginClient.getBaseUrl()` for Safe CDX API routes.
607
- - Safe CDX routes use the Safe CDX environment host from `SafeCDXConfig`.
1369
+ - Safe CDX derives its service base URL from `loginClient.getEnvironment()`.
608
1370
  - Standard Safe CDX API methods use the shared `HttpClient`.
609
1371
  - POST payloads are wrapped internally as `{ Data: payload }`.
610
1372
  - The caller should pass only the inner payload to SDK methods.