@healthcloudai/hc-login-connector 0.2.0 → 0.3.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
@@ -19,7 +19,7 @@ below follow the current behavior implemented in `src/client.ts`.
19
19
  6. Password reset initiation and password reset confirmation
20
20
  7. Access token, ID token, tenant, and base URL helper methods
21
21
  8. Token refresh using the stored refresh token
22
- 9. Authenticated patient header retrieval through `getUserInfo()`
22
+ 9. Authenticated patient header retrieval through `getPatientHeader()`
23
23
  10. Built on the shared Healthcheck HttpClient layer
24
24
 
25
25
  ---
@@ -114,7 +114,7 @@ The usage example above reads the value from local state.
114
114
  `getBaseUrl()` returns the currently configured base URL from local state.
115
115
 
116
116
  ```txt
117
- https://dev-api-healthcheck.healthcloud-services.com/api
117
+ https://dev-api-healthcheck.healthcloud-services.com
118
118
  ```
119
119
 
120
120
  ---
@@ -242,10 +242,14 @@ strings. If a field needs to contain multiple values, serialize those values int
242
242
  one string value, such as a comma separated list or another separator-separated
243
243
  list expected by the integration.
244
244
 
245
- `verifyEmailCode` is a dedicated boolean option that controls whether `User.Attributes.VERIFY_EMAIL_CODE` is included in the registration payload, depending on whether email verification should be handled through an OTP code.
245
+ `isOTP` is a dedicated boolean option that controls whether `User.Attributes.VERIFY_EMAIL_CODE` is included in the registration payload, depending on whether email verification should be handled through an OTP code.
246
246
 
247
- - If `verifyEmailCode` is `true`, the client sends `VERIFY_EMAIL_CODE: "true"`.
248
- - If `verifyEmailCode` is `false` or omitted, `VERIFY_EMAIL_CODE` is not sent.
247
+ - If `isOTP` is `true`, the client sends `VERIFY_EMAIL_CODE: "true"`.
248
+ - If `isOTP` is `false` or omitted, `VERIFY_EMAIL_CODE` is not sent.
249
+
250
+ #### OTP-based registration
251
+
252
+ Use this when email verification should be handled through an OTP code. In this case, `isOTP` should be `true`.
249
253
 
250
254
  ```ts
251
255
  await loginClient.register(
@@ -254,7 +258,7 @@ await loginClient.register(
254
258
  "John",
255
259
  "Smith",
256
260
  {
257
- verifyEmailCode: true,
261
+ isOTP: true,
258
262
  attributes: {
259
263
  Specialty: "Cardiology",
260
264
  NPINumber: "1234567890",
@@ -268,7 +272,7 @@ await loginClient.register(
268
272
  ```
269
273
 
270
274
  #### Full API request
271
- Request sent for the usage example above:
275
+ Request sent for the OTP example above:
272
276
 
273
277
  ```json
274
278
  {
@@ -295,6 +299,39 @@ Request sent for the usage example above:
295
299
  }
296
300
  ```
297
301
 
302
+ #### Link-based registration
303
+
304
+ Use this when email verification should be handled through an email link. In this case, `isOTP` should be omitted or `false`.
305
+
306
+ ```ts
307
+ await loginClient.register(
308
+ "john.smith@example.com",
309
+ "ExamplePassword123!",
310
+ "John",
311
+ "Smith"
312
+ );
313
+ ```
314
+
315
+ Effective request body:
316
+
317
+ ```json
318
+ {
319
+ "Data": {
320
+ "TenantID": "test-tenant",
321
+ "Credentials": {
322
+ "Email": "john.smith@example.com",
323
+ "Password": "ExamplePassword123!"
324
+ },
325
+ "User": {
326
+ "FirstName": "John",
327
+ "LastName": "Smith",
328
+ "Email": "john.smith@example.com",
329
+ "Attributes": {}
330
+ }
331
+ }
332
+ }
333
+ ```
334
+
298
335
  #### API response
299
336
 
300
337
  Status:
@@ -317,7 +354,7 @@ Notes:
317
354
  - `attributes` is the extension point for arbitrary additional registration fields.
318
355
  - The module does not need to know custom attribute names in advance.
319
356
  - All `attributes` values should be strings. Multi-value attributes should be serialized into one string value, such as a comma separated list or another integration-specific separator-separated list.
320
- - `verifyEmailCode` takes precedence over any manually provided `VERIFY_EMAIL_CODE` inside `attributes`.
357
+ - `isOTP` takes precedence over any manually provided `VERIFY_EMAIL_CODE` inside `attributes`.
321
358
 
322
359
  ---
323
360
 
@@ -329,9 +366,13 @@ Registers a patient for the configured tenant using the richer registration payl
329
366
 
330
367
  `TenantID` is injected internally from `configure(...)`.
331
368
 
332
- `verifyEmailCode` behaves the same way as in `register(...)`:
369
+ `isOTP` behaves the same way as in `register(...)`:
333
370
  when enabled, the client injects `VERIFY_EMAIL_CODE: "true"` into `User.Attributes`.
334
371
 
372
+ #### OTP-based registration
373
+
374
+ Use this when email verification should be handled through an OTP code. In this case, `isOTP` should be `true`.
375
+
335
376
  ```ts
336
377
  await loginClient.registerFull({
337
378
  email: "john.smith@example.com",
@@ -348,7 +389,7 @@ await loginClient.registerFull({
348
389
  genderIdentity: "Male",
349
390
  status: 0,
350
391
  language: "en",
351
- verifyEmailCode: true,
392
+ isOTP: true,
352
393
  attributes: {
353
394
  source: "sdk-test"
354
395
  },
@@ -406,6 +447,40 @@ Request sent for the usage example above:
406
447
  }
407
448
  ```
408
449
 
450
+ #### Link-based registration
451
+
452
+ Use this when email verification should be handled through an email link. In this case, `isOTP` should be omitted or `false`.
453
+
454
+ ```ts
455
+ await loginClient.registerFull({
456
+ email: "john.smith@example.com",
457
+ password: "ExamplePassword123!",
458
+ firstName: "John",
459
+ lastName: "Smith"
460
+ });
461
+ ```
462
+
463
+ Effective request body:
464
+
465
+ ```json
466
+ {
467
+ "Data": {
468
+ "TenantID": "test-tenant",
469
+ "Credentials": {
470
+ "Email": "john.smith@example.com",
471
+ "Password": "ExamplePassword123!",
472
+ "TenantID": "test-tenant"
473
+ },
474
+ "User": {
475
+ "FirstName": "John",
476
+ "LastName": "Smith",
477
+ "Email": "john.smith@example.com",
478
+ "Attributes": {}
479
+ }
480
+ }
481
+ }
482
+ ```
483
+
409
484
  #### API response
410
485
 
411
486
  Status:
@@ -435,24 +510,28 @@ verification settings.
435
510
  when email verification should be handled through an OTP code, the client sends
436
511
  `VERIFY_EMAIL_CODE: "true"` inside `User.Attributes`.
437
512
 
438
- - If `options.verifyEmailCode` is `true`, the client sends `VERIFY_EMAIL_CODE: "true"`.
439
- - The value is sent as a string inside `User.Attributes`, matching the `register(...)` method behavior when `verifyEmailCode` is `true`.
440
- - If `options.verifyEmailCode` is `false` or omitted, `VERIFY_EMAIL_CODE` is not sent.
513
+ - If `options.isOTP` is `true`, the client sends `VERIFY_EMAIL_CODE: "true"`.
514
+ - The value is sent as a string inside `User.Attributes`, matching the `register(...)` method behavior when `isOTP` is `true`.
515
+ - If `options.isOTP` is `false` or omitted, `VERIFY_EMAIL_CODE` is not sent.
441
516
  - Calling `verifyEmail(...)` without `options` keeps OTP email verification disabled by default.
442
517
 
518
+ #### OTP-based email verification
519
+
520
+ Use this when email verification should be handled through an OTP code. In this case, `options.isOTP` should be `true`.
521
+
443
522
  ```ts
444
523
  await loginClient.verifyEmail(
445
524
  "john.smith@example.com",
446
525
  "123456",
447
526
  "en",
448
527
  {
449
- verifyEmailCode: true
528
+ isOTP: true
450
529
  }
451
530
  );
452
531
  ```
453
532
 
454
533
  #### Full API request
455
- Request sent for the usage example above:
534
+ Request sent for the OTP example above:
456
535
 
457
536
  ```json
458
537
  {
@@ -471,6 +550,34 @@ Request sent for the usage example above:
471
550
  }
472
551
  ```
473
552
 
553
+ #### Link-based email verification
554
+
555
+ Use this when email verification should be handled through an email link. In this case, `options.isOTP` should be omitted or `false`.
556
+
557
+ ```ts
558
+ await loginClient.verifyEmail(
559
+ "john.smith@example.com",
560
+ "123456",
561
+ "en"
562
+ );
563
+ ```
564
+
565
+ Effective request body:
566
+
567
+ ```json
568
+ {
569
+ "Data": {
570
+ "Data": "123456",
571
+ "Step": "EMAIL_VERIFY",
572
+ "User": {
573
+ "Email": "john.smith@example.com",
574
+ "TenantID": "test-tenant"
575
+ },
576
+ "Language": "en"
577
+ }
578
+ }
579
+ ```
580
+
474
581
  #### API response
475
582
 
476
583
  Status:
@@ -500,23 +607,27 @@ settings.
500
607
  when email verification should be handled through an OTP code, the client sends
501
608
  `VERIFY_EMAIL_CODE: "true"` inside `User.Attributes`.
502
609
 
503
- - If `options.verifyEmailCode` is `true`, the client sends `VERIFY_EMAIL_CODE: "true"`.
504
- - The value is sent as a string inside `User.Attributes`, matching the `register(...)` method behavior when `verifyEmailCode` is `true`.
505
- - If `options.verifyEmailCode` is `false` or omitted, `VERIFY_EMAIL_CODE` is not sent.
610
+ - If `options.isOTP` is `true`, the client sends `VERIFY_EMAIL_CODE: "true"`.
611
+ - The value is sent as a string inside `User.Attributes`, matching the `register(...)` method behavior when `isOTP` is `true`.
612
+ - If `options.isOTP` is `false` or omitted, `VERIFY_EMAIL_CODE` is not sent.
506
613
  - Calling `resendEmailVerify(...)` without `options` keeps OTP email verification disabled by default.
507
614
 
615
+ #### OTP-based resend
616
+
617
+ Use this when email verification should be handled through an OTP code. In this case, `options.isOTP` should be `true`.
618
+
508
619
  ```ts
509
620
  await loginClient.resendEmailVerify(
510
621
  "john.smith@example.com",
511
622
  "en",
512
623
  {
513
- verifyEmailCode: true
624
+ isOTP: true
514
625
  }
515
626
  );
516
627
  ```
517
628
 
518
629
  #### Full API request
519
- Request sent for the usage example above:
630
+ Request sent for the OTP example above:
520
631
 
521
632
  ```json
522
633
  {
@@ -535,6 +646,33 @@ Request sent for the usage example above:
535
646
  }
536
647
  ```
537
648
 
649
+ #### Link-based resend
650
+
651
+ Use this when email verification should be handled through an email link. In this case, `options.isOTP` should be omitted or `false`.
652
+
653
+ ```ts
654
+ await loginClient.resendEmailVerify(
655
+ "john.smith@example.com",
656
+ "en"
657
+ );
658
+ ```
659
+
660
+ Effective request body:
661
+
662
+ ```json
663
+ {
664
+ "Data": {
665
+ "Data": "",
666
+ "Step": "RESEND_EMAIL_VERIFY",
667
+ "User": {
668
+ "Email": "john.smith@example.com",
669
+ "TenantID": "test-tenant"
670
+ },
671
+ "Language": "en"
672
+ }
673
+ }
674
+ ```
675
+
538
676
  #### API response
539
677
 
540
678
  Status:
@@ -851,20 +989,20 @@ Status:
851
989
 
852
990
  ### Reset Password
853
991
 
854
- `resetPassword` supports both OTP-based and link-based reset initiation
992
+ `requestPasswordReset` supports both OTP-based and link-based reset initiation
855
993
  flows.
856
994
 
857
- `IsPasswordResetWithOTP` is optional. It is included in the request body only
858
- when `isPasswordResetWithOTP` is `true`. When `false` or omitted, the field is
859
- not sent at all and `false` is not serialized into the payload.
995
+ `isOTP` is optional. When it is `true`, the client includes
996
+ `IsPasswordResetWithOTP: true` in the API request. When `false` or omitted, the
997
+ backend field is not sent at all.
860
998
 
861
999
  #### OTP-based password reset initiation
862
1000
 
863
1001
  Use this when the password reset flow should be handled through an OTP code.
864
- In this case, `IsPasswordResetWithOTP` should be `true`.
1002
+ In this case, `isOTP` should be `true`.
865
1003
 
866
1004
  ```ts
867
- await loginClient.resetPassword("john.smith@example.com", true);
1005
+ await loginClient.requestPasswordReset("john.smith@example.com", true);
868
1006
  ```
869
1007
 
870
1008
  #### Full API request
@@ -882,11 +1020,11 @@ await loginClient.resetPassword("john.smith@example.com", true);
882
1020
  #### Link-based password reset initiation
883
1021
 
884
1022
  Use this when the password reset flow should be handled from an email link.
885
- In this case, `IsPasswordResetWithOTP` should be omitted by passing `false` or
1023
+ In this case, `isOTP` should be omitted by passing `false` or
886
1024
  leaving the second argument out.
887
1025
 
888
1026
  ```ts
889
- await loginClient.resetPassword("john.smith@example.com", false);
1027
+ await loginClient.requestPasswordReset("john.smith@example.com", false);
890
1028
  ```
891
1029
 
892
1030
  Effective request body:
@@ -923,28 +1061,27 @@ Status:
923
1061
 
924
1062
  ### Reset Password Confirm
925
1063
 
926
- `resetPasswordConfirm` supports both OTP-based and link-based confirmation
1064
+ `confirmPasswordReset` supports both OTP-based and link-based confirmation
927
1065
  flows.
928
1066
 
929
- If `IsPasswordResetWithOTP` is `true`, `Code` is required. If
930
- `IsPasswordResetWithOTP` is not `true`, `Token` is required. For the
931
- link-based flow, the frontend is responsible for taking the token from the
932
- password reset email link and passing that token in the payload.
1067
+ If `isOTP` is `true`, `Code` is required. If `isOTP` is not `true`, `Token` is
1068
+ required. The client maps `isOTP: true` to `IsPasswordResetWithOTP: true` in the
1069
+ API request. For the link-based flow, the frontend is responsible for taking
1070
+ the token from the password reset email link and passing that token in the
1071
+ payload.
933
1072
 
934
1073
  #### OTP-based password reset confirmation
935
1074
 
936
1075
  Use this when the password reset flow was started with OTP. In this case,
937
- `IsPasswordResetWithOTP` should be `true` and `Code` should contain the final
1076
+ `isOTP` should be `true` and `Code` should contain the final
938
1077
  reset code.
939
1078
 
940
1079
  ```ts
941
- await loginClient.resetPasswordConfirm({
942
- Data: {
943
- Email: "john.smith@example.com",
944
- Password: "ExamplePassword123!",
945
- IsPasswordResetWithOTP: true,
946
- Code: "123456"
947
- }
1080
+ await loginClient.confirmPasswordReset({
1081
+ Email: "john.smith@example.com",
1082
+ Password: "ExamplePassword123!",
1083
+ isOTP: true,
1084
+ Code: "123456"
948
1085
  });
949
1086
  ```
950
1087
 
@@ -967,15 +1104,13 @@ Request body:
967
1104
  Use this when the password reset flow was started from an email link. In this
968
1105
  case, `Token` should be included in the payload, the frontend should read the
969
1106
  token value from the password reset email link and send that token in the
970
- request payload, and `IsPasswordResetWithOTP` should be omitted or `false`.
1107
+ request payload, and `isOTP` should be omitted or `false`.
971
1108
 
972
1109
  ```ts
973
- await loginClient.resetPasswordConfirm({
974
- Data: {
975
- Email: "john.smith@example.com",
976
- Password: "ExamplePassword123!",
977
- Token: "token-from-email-link"
978
- }
1110
+ await loginClient.confirmPasswordReset({
1111
+ Email: "john.smith@example.com",
1112
+ Password: "ExamplePassword123!",
1113
+ Token: "token-from-email-link"
979
1114
  });
980
1115
  ```
981
1116
 
@@ -1102,19 +1237,19 @@ If an API key has been configured through setApiKey(...), the returned header ob
1102
1237
 
1103
1238
  ---
1104
1239
 
1105
- ### Get User Info
1240
+ ### Get Patient Header
1106
1241
 
1107
- Public signature: `loginClient.getUserInfo()`
1242
+ Public signature: `loginClient.getPatientHeader()`
1108
1243
 
1109
1244
  Calls the patient header endpoint and returns the raw server response.
1110
1245
  It requires a stored ID token.
1111
1246
 
1112
1247
  ```ts
1113
- const userInfo = await loginClient.getUserInfo();
1248
+ const header = await loginClient.getPatientHeader();
1114
1249
  ```
1115
1250
 
1116
1251
  #### Full API request
1117
- `getUserInfo()` does not send a request body.
1252
+ `getPatientHeader()` does not send a request body.
1118
1253
 
1119
1254
  #### API response
1120
1255
 
@@ -1128,42 +1263,60 @@ Status:
1128
1263
  {
1129
1264
  "Data": {
1130
1265
  "Record": {
1131
- "ID": "record-id-example",
1132
- "TenantID": "test-tenant",
1266
+ "Status": 1,
1267
+ "Sex": "Male",
1268
+ "GenderIdentity": "Male",
1269
+ "HasInsurance": true,
1270
+ "HasIDCard": true,
1271
+ "HasSelfie": true,
1272
+ "Flags": null,
1133
1273
  "FirstName": "John",
1134
1274
  "LastName": "Smith",
1275
+ "MiddleName": null,
1276
+ "BirthDate": "1990-01-01T00:00:00",
1135
1277
  "Email": "john.smith@example.com",
1136
1278
  "Phone": "+15555550123",
1137
- "BirthDate": "1990-01-01T00:00:00",
1138
- "Gender": "male",
1139
- "Sex": "Male",
1279
+ "Gender": "Male",
1140
1280
  "Race": "White",
1141
- "Ethnicity": "Not Hispanic or Latino",
1142
- "HasInsurance": false,
1143
- "HasIDCard": false,
1144
- "HasSelfie": false,
1281
+ "Ethnicity": "",
1282
+ "CRMID": null,
1283
+ "AppleUserId": null,
1145
1284
  "Address": {
1146
- "StreetAndNumber": "123 Test St",
1285
+ "StreetAndNumber": "1 Main St",
1147
1286
  "Extension": null,
1148
1287
  "City": "Springfield",
1149
- "State": "CA",
1150
- "PostalCode": "90210",
1288
+ "State": "IL",
1289
+ "PostalCode": "62701",
1151
1290
  "Country": "US"
1152
1291
  },
1153
1292
  "Attributes": {
1154
- "FHIRPatientID": "patient-id-example",
1155
- "CompositeID": "composite-id-example",
1156
- "VERIFY_EMAIL_CODE": "true"
1293
+ "AthenaPatientID": "patient-id-example",
1294
+ "AthenaGuarantorFirstName": "John",
1295
+ "AthenaGuarantorLastName": "Smith",
1296
+ "AthenaGuarantorEmail": "john.smith@example.com",
1297
+ "AthenaCountryCode": "USA",
1298
+ "AthenaDepartmentId": "1",
1299
+ "AthenaEmailExists": "True",
1300
+ "AthenaTestPatient": "False",
1301
+ "PatientImageURL": "https://storage.example.com/selfie_example.jpg",
1302
+ "CompositeID": "tenant-id/john.smith@example.com",
1303
+ "Insurance": "EXAMPLE INSURANCE"
1157
1304
  },
1158
- "CompositeID": "composite-id-example",
1159
- "FHIRID": "fhir-id-example"
1305
+ "CompositeID": "tenant-id/john.smith@example.com",
1306
+ "FHIRID": "fhir-id-example",
1307
+ "AthenaID": "patient-id-example",
1308
+ "Created": "0001-01-01T00:00:00",
1309
+ "Modified": "0001-01-01T00:00:00",
1310
+ "CreatedByID": null,
1311
+ "ModifiedByID": null,
1312
+ "IsDeactivated": false,
1313
+ "TenantID": "test-tenant",
1314
+ "ID": "record-uuid-example"
1160
1315
  },
1161
1316
  "Encounters": null,
1162
- "EHR": "fhir",
1317
+ "EHR": "athena",
1163
1318
  "PendingActions": [
1164
- "ADD_INSURANCE",
1165
- "ADD_ID",
1166
- "IMPORT_VITALS"
1319
+ "TAKE_TEST"
1167
1320
  ]
1168
1321
  },
1169
1322
  "ErrorMessage": null,
@@ -1231,10 +1384,47 @@ eyJ_id_token_example
1231
1384
  - Use `submitOnboardingStep(...)` or the onboarding helper methods to complete required onboarding steps.
1232
1385
  - Call `login(...)` to authenticate and store the returned access, refresh, and ID tokens in memory.
1233
1386
  - Use `refreshToken()` to replace the current token set when a stored refresh token is available.
1234
- - Use `getAuthHeader()`, `getAccessToken()`, `getIDToken()`, and `getUserInfo()` for authenticated flows after login.
1387
+ - Use `getAuthHeader()`, `getAccessToken()`, `getIDToken()`, and `getPatientHeader()` for authenticated flows after login.
1235
1388
  - Other Healthcheck connectors can consume `getAuthHeader()` instead of managing auth tokens directly.
1236
1389
 
1237
1390
 
1391
+ ## Token Expiry and Refresh
1392
+
1393
+ The SDK does **not** auto-refresh tokens. Call `refreshToken()` proactively when the token is about to expire.
1394
+
1395
+ `getTokens()` returns the current in-memory token set, including `expiresAt` — an absolute Unix millisecond timestamp parsed from the `Expiration` field the backend returns.
1396
+
1397
+ `isTokenExpired()` is a helper that returns `true` when there is no stored token or when `Date.now() >= expiresAt`.
1398
+
1399
+ Recommended pattern:
1400
+
1401
+ ```ts
1402
+ async function ensureFreshToken(loginClient: HCLoginClient): Promise<void> {
1403
+ if (loginClient.isTokenExpired()) {
1404
+ await loginClient.refreshToken();
1405
+ }
1406
+ }
1407
+
1408
+ // Call before any authenticated connector operation
1409
+ await ensureFreshToken(loginClient);
1410
+ const result = await otherConnector.someMethod();
1411
+ ```
1412
+
1413
+ `getTokens()` shape:
1414
+
1415
+ ```ts
1416
+ interface AuthTokens {
1417
+ accessToken: string;
1418
+ refreshToken: string;
1419
+ idToken: string | null;
1420
+ expiresAt: number; // absolute milliseconds since Unix epoch
1421
+ }
1422
+ ```
1423
+
1424
+ `refreshToken()` does not require a current ID token. It uses the stored refresh token from the previous successful `login()` call.
1425
+
1426
+ ---
1427
+
1238
1428
  ## Notes
1239
1429
 
1240
1430
  - `configure()` stores tenant configuration locally and does not send an HTTP request.
@@ -1242,7 +1432,15 @@ eyJ_id_token_example
1242
1432
  - `register()`, `verifyEmail()`, and `resendEmailVerify()` all follow the same string-based `User.Attributes.VERIFY_EMAIL_CODE` pattern for OTP email verification flows.
1243
1433
  - `saveAddress()` accepts an `Address`.
1244
1434
  - `login()` stores the initial token set internally, and `refreshToken()` updates that stored token set when a refresh token is available.
1245
- - `refreshToken()` does not require a current ID token. It uses the stored refresh token from the previous successful login.
1246
- - `getUserInfo()` calls the patient header endpoint.
1247
- - `getAuthHeader()`, `getAccessToken()`, `getIDToken()`, `getBaseUrl()`, and `getTenantId()` return local values and do not perform HTTP requests.
1435
+ - `getPatientHeader()` calls the patient header endpoint.
1436
+ - `getAuthHeader()` returns `Authorization: Bearer <idToken>` + `X-Tenant-ID`. It does **not** carry an API key — each connector adds its own key independently.
1437
+ - `getAuthHeader()`, `getAccessToken()`, `getIDToken()`, `getBaseUrl()`, `getTenantId()`, `getTokens()`, and `isTokenExpired()` return or evaluate local values and do not perform HTTP requests.
1248
1438
  - This connector is intended to be reused by other Healthcheck SDK connectors built on the same HTTP layer.
1439
+
1440
+ ---
1441
+
1442
+ ## getPatientHeader vs getDashboard
1443
+
1444
+ `HCLoginClient.getPatientHeader()` calls `/api/patient/header` and returns a lightweight patient record plus pending actions. Typically called once after login to check patient state.
1445
+
1446
+ `HCSettingsClient.getDashboard()` (in `hc-settings-connector`) calls `/api/patient/dashboard` and returns a richer dashboard including encounters. These are different endpoints with different response shapes — not duplicates.