@healthcloudai/hc-safe-cdx 0.4.1 → 0.4.2

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