dataspace-client-sdk-node 0.1.1 → 0.1.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.
package/dist/types.d.ts CHANGED
@@ -158,6 +158,16 @@ export type FamilyOrganizationSummary = {
158
158
  missingFields?: string[];
159
159
  updatedAt?: string;
160
160
  };
161
+ export type OfferPreview = {
162
+ offerId?: string;
163
+ amount?: string;
164
+ currency?: string;
165
+ seats?: number;
166
+ planName?: string;
167
+ sku?: string;
168
+ paymentMethod?: string;
169
+ checkoutUrl?: string;
170
+ };
161
171
  /**
162
172
  * Input for organization activation in GW using ICA-derived proof material.
163
173
  *
@@ -166,11 +176,40 @@ export type FamilyOrganizationSummary = {
166
176
  */
167
177
  export type GatewayOrganizationActivationInput = {
168
178
  vpToken: string;
179
+ /** Generic requested seats/members for initial offer sizing. Defaults to 2. */
180
+ numberOfMembers?: number;
181
+ organizationVc?: string;
182
+ legalRepresentativeVc?: string;
183
+ regulatoryEvidence?: Record<string, unknown>;
184
+ /** @deprecated Prefer `numberOfMembers` and explicit input fields. */
185
+ additionalClaims?: Record<string, unknown>;
186
+ };
187
+ export type GatewayOrganizationActivationSimpleInput = {
188
+ jurisdiction?: string;
189
+ sector?: string;
190
+ vpToken: string;
191
+ serviceProviderDidWeb?: string;
192
+ serviceProviderUrl?: string;
193
+ controllerEmail?: string;
194
+ controllerTelephone?: string;
195
+ controllerRole: string;
196
+ numberOfMembers?: number;
197
+ timeoutSeconds?: number;
198
+ intervalSeconds?: number;
169
199
  organizationVc?: string;
170
200
  legalRepresentativeVc?: string;
171
201
  regulatoryEvidence?: Record<string, unknown>;
172
202
  additionalClaims?: Record<string, unknown>;
173
203
  };
204
+ export type LegalOrganizationOrderSimpleInput = {
205
+ jurisdiction?: string;
206
+ sector?: string;
207
+ offerId: string;
208
+ timeoutSeconds?: number;
209
+ intervalSeconds?: number;
210
+ dataType?: string;
211
+ additionalClaims?: Record<string, unknown>;
212
+ };
174
213
  /**
175
214
  * Input for device activation based on activation code exchange + DCR.
176
215
  */
@@ -180,6 +219,16 @@ export type EmployeeDeviceActivationInput = {
180
219
  dcrPayload: Record<string, unknown>;
181
220
  pollOptions?: PollOptions;
182
221
  };
222
+ export type EmployeeDeviceActivationSimpleInput = {
223
+ tenantId?: string;
224
+ jurisdiction?: string;
225
+ sector?: string;
226
+ activationCode: string;
227
+ idToken: string;
228
+ dcrPayload: Record<string, unknown>;
229
+ timeoutSeconds?: number;
230
+ intervalSeconds?: number;
231
+ };
183
232
  /**
184
233
  * Result of device activation flow.
185
234
  *
@@ -217,6 +266,36 @@ export type SubjectOrganizationBootstrapResult = {
217
266
  registration: SubmitAndPollResult;
218
267
  confirmation?: SubmitAndPollResult;
219
268
  };
269
+ export type IndividualOrganizationBootstrapSimpleInput = {
270
+ tenantId?: string;
271
+ jurisdiction?: string;
272
+ sector?: string;
273
+ alternateName: string;
274
+ controllerEmail?: string;
275
+ controllerTelephone?: string;
276
+ controllerRole?: string;
277
+ timeoutSeconds?: number;
278
+ intervalSeconds?: number;
279
+ additionalClaims?: Record<string, unknown>;
280
+ };
281
+ export type IndividualOrganizationBootstrapSimpleResult = {
282
+ registration: SubmitAndPollResult;
283
+ offerId: string;
284
+ confirmation: SubmitAndPollResult;
285
+ };
286
+ export type IndividualOrganizationStartSimpleResult = {
287
+ registration: SubmitAndPollResult;
288
+ offerId: string;
289
+ offerPreview: OfferPreview;
290
+ };
291
+ export type IndividualOrganizationConfirmOrderSimpleInput = {
292
+ tenantId?: string;
293
+ jurisdiction?: string;
294
+ sector?: string;
295
+ offerId: string;
296
+ timeoutSeconds?: number;
297
+ intervalSeconds?: number;
298
+ };
220
299
  /**
221
300
  * Input for UC 5.5 IPS/FHIR import and index update.
222
301
  */
@@ -367,6 +446,8 @@ export type ClientOptions = {
367
446
  bearerToken?: string;
368
447
  defaultHeaders?: Record<string, string>;
369
448
  wallet?: WalletProvider;
449
+ /** Optional default tenant context so calls can omit ctx repeatedly. */
450
+ ctx?: RouteContext;
370
451
  };
371
452
  /**
372
453
  * Options for identity-exchange.v1 backend PKCE + token exchange flow.
@@ -435,6 +516,17 @@ export type SmartTokenExchangeInput = {
435
516
  exchangePayload: Record<string, unknown>;
436
517
  path?: string;
437
518
  };
519
+ export type SmartTokenRequestSimpleInput = {
520
+ tenantId?: string;
521
+ jurisdiction?: string;
522
+ sector?: string;
523
+ idToken: string;
524
+ scopes: string[];
525
+ endpointId?: string;
526
+ timeoutSeconds?: number;
527
+ intervalSeconds?: number;
528
+ additionalClaims?: Record<string, unknown>;
529
+ };
438
530
  export type SmartTokenExchangeResult = {
439
531
  status: 'fetched' | 'cached' | 'failed';
440
532
  accessToken?: string;
@@ -0,0 +1,122 @@
1
+ # Backend Node Integration
2
+
3
+ This guide is backend-only and uses `dataspace-client-sdk-node`.
4
+
5
+ Recommended initialization:
6
+
7
+ ```ts
8
+ const client = new DataspaceNodeClient({
9
+ baseUrl,
10
+ bearerToken,
11
+ ctx: { tenantId, jurisdiction, sector },
12
+ });
13
+ ```
14
+
15
+ Alternative (mutable context):
16
+
17
+ ```ts
18
+ const client = new DataspaceNodeClient({ baseUrl, bearerToken });
19
+ client.setContextOrg({ tenantId, jurisdiction, sector });
20
+ client.setDefaultTimeoutSeconds(12);
21
+ client.setDefaultIntervalSeconds(2);
22
+ ```
23
+
24
+ ## Flow A. Legal Organization Onboarding (B2B)
25
+
26
+ 1. Receive from frontend: `jurisdiction`, `sector`, `vpToken`.
27
+ 2. Activate in GW:
28
+ - SDK method: `activateOrganizationInGatewaySimple(...)` (recommended)
29
+ - SDK method: `activateOrganizationInGatewayFromIcaProof(...)` (advanced)
30
+ 3. Complete legal organization order (always; amount may be `0`) using `offerId` returned by activation response.
31
+ - `hostRegistryOrderBatchPath(...)`
32
+ - `hostRegistryOrderPollPath(...)`
33
+ - `submitAndPoll(...)`
34
+ 4. Run DCR/token bootstrap:
35
+ - `activateEmployeeDeviceWithActivationCodeSimple(...)` (recommended)
36
+ - `activateEmployeeDeviceWithActivationCode(...)` (advanced)
37
+ - `requestSmartTokenSimple(...)` (recommended)
38
+ - `requestSmartToken(...)` (advanced)
39
+ - `authenticateBackendPkceAndExchange(...)` or `authenticateBackendSmartStandard(...)`
40
+
41
+ ### Custom backend code example: activate endpoint
42
+
43
+ ```ts
44
+ app.post('/api/onboarding/legal/activate', async (req, res) => {
45
+ const client = new DataspaceNodeClient({ baseUrl, bearerToken });
46
+ client.setContextOrg({
47
+ tenantId: req.body.tenantId,
48
+ jurisdiction: req.body.jurisdiction,
49
+ sector: req.body.sector,
50
+ });
51
+ const activation = await client.activateOrganizationInGatewaySimple({
52
+ vpToken: req.body.vpToken,
53
+ serviceProviderDidWeb: req.body.serviceProviderDidWeb, // or serviceProviderUrl
54
+ serviceProviderUrl: req.body.serviceProviderUrl,
55
+ controllerEmail: req.body.controllerEmail,
56
+ controllerTelephone: req.body.controllerTelephone, // optional alternative to email
57
+ controllerRole: req.body.controllerRole,
58
+ numberOfMembers: req.body.numberOfMembers ?? 2,
59
+ });
60
+ const offerId = client.getOfferIdFromResponse(activation);
61
+ if (!offerId) throw new Error('Offer id missing in activation response');
62
+ const offer = client.getOfferPreviewFromResponse(activation);
63
+ // Use `offer` to render amount/currency/description to user before acceptance.
64
+ const order = await client.confirmLegalOrganizationOrderSimple({
65
+ offerId,
66
+ });
67
+ res.json({ activation, offerId, order });
68
+ });
69
+ ```
70
+
71
+ Async UX note:
72
+ - `submitAndPoll` is convenient but returns final state.
73
+ - If you need progress updates, call lower-level `submitBatch` + `pollUntilComplete`
74
+ and stream step transitions to frontend.
75
+
76
+ ## Flow B. Personal Organization Onboarding (individual/family)
77
+
78
+ 1. Receive registration payload from frontend.
79
+ 2. Start registration and extract offer:
80
+ - SDK method: `startIndividualOrganizationSimple(...)` (recommended)
81
+ 3. Frontend shows offer and user accepts.
82
+ 4. Confirm order:
83
+ - SDK method: `confirmIndividualOrganizationOrderSimple(...)` (recommended)
84
+ 5. Provisional one-shot helper (auto-order, for legacy/fork scenarios):
85
+ - SDK method: `bootstrapIndividualOrganizationSimple(...)` (provisional)
86
+ 3. Continue consent/clinical lifecycle as needed:
87
+ - `grantProfessionalAccessSimple(...)`
88
+ - `importIpsOrFhirAndUpdateIndex(...)`
89
+ - `requestSmartTokenSimple(...)` (recommended)
90
+ - `requestSmartToken(...)` (advanced)
91
+ - `generateDigitalTwinFromSubjectData(...)`
92
+
93
+ ### Custom backend code example: personal register endpoint
94
+
95
+ ```ts
96
+ app.post('/api/onboarding/personal/register', async (req, res) => {
97
+ const client = new DataspaceNodeClient({ baseUrl, bearerToken });
98
+ const ctx = {
99
+ tenantId: req.body.tenantId,
100
+ jurisdiction: req.body.jurisdiction,
101
+ sector: req.body.sector,
102
+ };
103
+
104
+ const started = await client.startIndividualOrganizationSimple({
105
+ alternateName: req.body.alternateName,
106
+ controllerEmail: req.body.controllerEmail,
107
+ controllerTelephone: req.body.controllerTelephone,
108
+ controllerRole: req.body.controllerRole || 'org.hl7.v3.RoleCode|RESPRSN',
109
+ });
110
+ // Return offer to frontend for explicit acceptance UX.
111
+ // Later, call confirmIndividualOrganizationOrderSimple({ offerId: started.offerId }).
112
+ res.json(started);
113
+ });
114
+ ```
115
+
116
+ ## References
117
+
118
+ - `examples/e2e-bootstrap-tenant.mjs`
119
+ - `examples/host-activate-and-employee.mjs`
120
+ - `examples/e2e-individual-flow.mjs`
121
+ - `tests/uc5-org-onboarding.flow.test.mjs`
122
+ - `tests/uc5-subject-data.flow.test.mjs`
@@ -1,31 +1,55 @@
1
- # Data Model Alignment (GW + Chat + SDK)
1
+ # Data Model Alignment (GW + ICA + DataConv + SDK)
2
2
 
3
- Alignment reference for SDK consumers and backend integrators.
3
+ Reference alignment for SDK consumers and backend integrators.
4
4
 
5
- ## Business vs Infra Sector
5
+ ## API Namespace Alignment
6
6
 
7
- - Infra host onboarding uses network routing sector (`HOST_REGISTRY_SECTOR`).
7
+ - Gateway onboarding and operational flows use `/host/...` with `auth`-based security (OIDC + smart token post-DCR).
8
+ - ICA flows use `/ica/...` namespace.
9
+ - DataConv flows use `/publisher/...` namespace.
10
+ - SDK helpers should abstract these prefixes so integrators do not rebuild paths manually.
11
+
12
+ ## Business Sector vs Host Registry Sector
13
+
14
+ - Host onboarding/routing can use infrastructure/network sector (`HOST_REGISTRY_SECTOR`).
8
15
  - Tenant runtime model uses business sector (`SECTOR`).
9
- - Canonical tenant vault ID:
16
+ - Canonical tenant vault ID format:
10
17
  - `<SECTOR>_<tenantId>`
11
18
  - Example: `health-care_acme`
12
19
 
20
+ ## Legal Organization Activation Inputs
21
+
22
+ - `vp_token` is the primary proof artifact.
23
+ - Organization VC can be extracted from the VP (`LegalOrganizationCredential` / `OrganizationCredential`).
24
+ - Representative VC is optional depending on policy, but contact/role claims are still required for controller bootstrap.
25
+ - Controller contact supports both:
26
+ - `org.schema.Person.email`
27
+ - `org.schema.Person.telephone`
28
+ - Do not document phone-only assumptions as general contract.
29
+
30
+ ## Offer/Order Commercial Contract
31
+
32
+ - Offer/Order remains part of the onboarding business flow even when amount is `0`.
33
+ - SDK should expose helpers to:
34
+ - extract offer identifier from activation/orderable claims
35
+ - expose offer preview fields (amount, currency, description) for UI confirmation
36
+ - Backend should send Order acceptance explicitly using `Order.acceptedOffer.identifier`.
37
+
13
38
  ## Individual Organization Model
14
39
 
15
- - Section: `<prefix>_individual`
16
- - Doc ID: `org.schema.Organization.identifier.value` (UUID)
40
+ - Section naming: `<prefix>_individual`.
41
+ - Canonical individual org identifier: `org.schema.Organization.identifier.value` (UUID).
42
+ - Contact model is the same as legal org onboarding: email or telephone.
17
43
 
18
44
  ## Search Contract (`searchFamilyOrganization`)
19
45
 
20
46
  - Input mapping:
21
47
  - `controllerPhone` -> `org.schema.Organization.owner.telephone`
48
+ - `controllerEmail` -> `org.schema.Organization.owner.email`
22
49
  - `usualname` -> `org.schema.Organization.alternateName`
23
50
  - `birthDate` -> `org.schema.Organization.foundingDate` (optional)
24
51
 
25
- ## Voice Phone Contract
26
-
27
- - Preferred claim for subject call target:
28
- - `org.schema.Organization.telephone`
29
- - Fallback claim:
30
- - `org.schema.Organization.owner.telephone`
52
+ ## Voice/Phone Use Case Notes
31
53
 
54
+ - Voice agents may prioritize `telephone` for call target resolution.
55
+ - Generic portal/web integrations must support both email and telephone identity channels.
@@ -0,0 +1,335 @@
1
+ # Portal Backend Integration Handover
2
+
3
+ Target audience: team implementing a portal backend that integrates with GW via `dataspace-client-sdk-node`.
4
+
5
+ ## 1) What this SDK does vs does not do
6
+
7
+ `dataspace-client-sdk-node` does:
8
+ - build GW paths
9
+ - submit/poll async DIDComm plain flows
10
+ - provide high-level wrappers for common UC5 steps
11
+
12
+ `dataspace-client-sdk-node` does not do:
13
+ - ICA UX orchestration in browser
14
+ - wallet UX/signing UX for end-users
15
+ - generic OIDC4VP UI flow control
16
+
17
+ `vp_token` source:
18
+ - produced by your identity/wallet flow (usually ICA/OIDC4VP side)
19
+ - then passed to backend and used in GW `_activate`
20
+
21
+ ## 2) Responsibility split
22
+
23
+ 1. Portal frontend:
24
+ - UX, contract signature flow, identity/wallet flow
25
+ - obtains `vpToken`
26
+ - sends onboarding payloads to portal backend
27
+ 2. Portal backend (this integration):
28
+ - uses this SDK to call GW
29
+ - executes end-to-end business order and polling
30
+ 3. GW backend:
31
+ - validates proofs, processes onboarding, authorization and data access contracts
32
+
33
+ ### Recommended SDK initialization
34
+
35
+ Initialize once with default context to avoid repeating `tenantId/jurisdiction/sector`:
36
+
37
+ ```ts
38
+ const client = new DataspaceNodeClient({
39
+ baseUrl,
40
+ bearerToken,
41
+ ctx: { tenantId, jurisdiction, sector },
42
+ });
43
+ ```
44
+
45
+ ## 3) Legal organization onboarding (complete, numbered)
46
+
47
+ Canonical order:
48
+ `_verify -> _activate -> Offer -> Order -> DCR -> token`
49
+
50
+ ### Step 1. Frontend obtains `vpToken`
51
+
52
+ Expected backend input:
53
+ - `jurisdiction` (required)
54
+ - `sector` (required)
55
+ - `vpToken` (required)
56
+ - `numberOfMembers` (optional, generic; default `2`)
57
+
58
+ Notes:
59
+ - `jurisdiction` is explicit route context. It is not inferred from VAT.
60
+ - `sector` is explicit route context. It is not inferred from DID.
61
+
62
+ Set once in client:
63
+
64
+ ```ts
65
+ client.setContextOrg({ tenantId, jurisdiction, sector });
66
+ client.setDefaultTimeoutSeconds(120);
67
+ client.setDefaultIntervalSeconds(2);
68
+ ```
69
+
70
+ ### Step 2. Backend activates organization in GW
71
+
72
+ Preferred SDK method (friendly):
73
+ - `activateOrganizationInGatewaySimple(...)`
74
+
75
+ ```ts
76
+ const activation = await client.activateOrganizationInGatewaySimple({
77
+ vpToken,
78
+ serviceProviderDidWeb, // or serviceProviderUrl (SDK maps URL -> did:web:<host>)
79
+ controllerEmail, // or controllerTelephone
80
+ controllerRole, // e.g. ISCO-08|1112
81
+ numberOfMembers, // optional, default 2
82
+ });
83
+ ```
84
+
85
+ Advanced equivalent (less friendly, same behavior):
86
+ - `activateOrganizationInGatewayFromIcaProof(ctx, input, pollOptions)`
87
+
88
+ ```ts
89
+ const activation = await client.activateOrganizationInGatewayFromIcaProof(
90
+ { jurisdiction, sector },
91
+ {
92
+ vpToken,
93
+ numberOfMembers,
94
+ },
95
+ { timeoutMs: 120000, intervalMs: 2000 },
96
+ );
97
+ ```
98
+
99
+ Default behavior:
100
+ - if `numberOfMembers` is not provided, SDK defaults to `2`
101
+ (controller + one operational employee).
102
+
103
+ ### Step 3. Backend extracts `offerId` from `_activate` response
104
+
105
+ Use SDK helper:
106
+ - `client.getOfferIdFromResponse(activation)`
107
+
108
+ Extract offer preview fields from the first DIDComm entry via:
109
+ - `client.getFirstDidcommDataEntryFromResponse(activation)`
110
+ - or directly as UI object: `client.getOfferPreviewFromResponse(activation)`
111
+
112
+ Expected offer claims (GW examples/contracts):
113
+ - `org.schema.Offer.identifier`
114
+ - `org.schema.Offer.price`
115
+ - `org.schema.Offer.priceCurrency`
116
+ - `org.schema.Offer.eligibleQuantity.value`
117
+ - `org.schema.Offer.itemOffered.name`
118
+ - `org.schema.Offer.itemOffered.sku`
119
+ - `org.schema.Offer.acceptedPaymentMethod`
120
+ - `org.schema.Offer.checkoutPageURLTemplate`
121
+
122
+ Recommended UI mapping helper:
123
+ - `client.getOfferPreviewFromResponse(activation)` ->
124
+ `{ offerId, amount, currency, seats, planName, sku, paymentMethod, checkoutUrl }`
125
+
126
+ ### Step 4. Backend executes legal organization order (always)
127
+
128
+ Even with `0` amount, Order is still required.
129
+
130
+ Preferred SDK method (friendly):
131
+ - `confirmLegalOrganizationOrderSimple(...)`
132
+
133
+ ```ts
134
+ const legalOrgOrder = await client.confirmLegalOrganizationOrderSimple({
135
+ offerId,
136
+ });
137
+ ```
138
+
139
+ Advanced equivalent (less friendly):
140
+ - `hostRegistryOrderBatchPath(...)`
141
+ - `hostRegistryOrderPollPath(...)`
142
+ - `submitAndPoll(...)`
143
+
144
+ Example:
145
+
146
+ ```ts
147
+ const offerId = client.getOfferIdFromResponse(activation);
148
+ if (!offerId) throw new Error('Offer id missing in activation response.');
149
+
150
+ const legalOrgOrder = await client.submitAndPoll(
151
+ client.hostRegistryOrderBatchPath({ jurisdiction, sector }),
152
+ client.hostRegistryOrderPollPath({ jurisdiction, sector }),
153
+ {
154
+ thid: `order-${Date.now()}`,
155
+ body: {
156
+ data: [{
157
+ type: 'Organization-order-request-v1.0',
158
+ meta: { claims: { 'Order.acceptedOffer.identifier': offerId } },
159
+ }],
160
+ },
161
+ },
162
+ );
163
+ ```
164
+
165
+ ### Step 4.1 UX requirement: show offer summary before order
166
+
167
+ Portal UX should present:
168
+ 1. Offer identifier
169
+ 2. Total amount and currency (if provided)
170
+ 3. Product/license summary
171
+ 4. Acceptance action (user confirms, then backend sends Order)
172
+
173
+ Even if amount is `0`, user acceptance and Order submission still happen.
174
+
175
+ ### Step 5. Backend creates doctor employee
176
+
177
+ SDK method:
178
+ - `createOrganizationEmployee(...)`
179
+
180
+ ```ts
181
+ const employee = await client.createOrganizationEmployee(
182
+ { tenantId, jurisdiction, sector },
183
+ {
184
+ employeeClaims: {
185
+ '@context': 'org.schema',
186
+ 'org.schema.Person.email': 'doctor@example.com',
187
+ 'org.schema.Person.hasOccupation': 'ISCO-08|2211',
188
+ },
189
+ },
190
+ );
191
+ ```
192
+
193
+ ### Step 6. Backend activates doctor device (DCR path)
194
+
195
+ SDK method:
196
+ - `activateEmployeeDeviceWithActivationCodeSimple(...)` (recommended)
197
+ - `activateEmployeeDeviceWithActivationCode(...)` (advanced)
198
+
199
+ ```ts
200
+ const device = await client.activateEmployeeDeviceWithActivationCodeSimple({
201
+ activationCode,
202
+ idToken: doctorUserIdToken,
203
+ dcrPayload: {
204
+ application_type: 'web',
205
+ token_endpoint_auth_method: 'private_key_jwt',
206
+ jwks: { keys: [doctorPublicJwk] },
207
+ },
208
+ });
209
+ ```
210
+
211
+ ### Step 7. Backend obtains SMART token for authorized access
212
+
213
+ SDK method:
214
+ - `requestSmartTokenSimple(...)` (recommended)
215
+ - `requestSmartToken(...)` (advanced)
216
+
217
+ ```ts
218
+ const smart = await client.requestSmartTokenSimple({
219
+ idToken: doctorUserIdToken,
220
+ endpointId: 'doctor-portal',
221
+ scopes: ['organization/Composition.rs'],
222
+ });
223
+ ```
224
+
225
+ ## 4) Individual subject onboarding + access grant (doctor reads index)
226
+
227
+ ### Step 8. Register personal organization + family order
228
+
229
+ SDK method:
230
+ - `bootstrapSubjectOrganizationIndex(...)`
231
+
232
+ ```ts
233
+ const subjectBootstrap = await client.bootstrapSubjectOrganizationIndex(
234
+ { tenantId, jurisdiction, sector },
235
+ {
236
+ registrationPayload,
237
+ confirmationPayload, // include accepted offer id
238
+ },
239
+ );
240
+ ```
241
+
242
+ ### Step 9. Grant doctor consent to read subject index/data
243
+
244
+ SDK method:
245
+ - `grantProfessionalAccessSimple(...)`
246
+
247
+ ```ts
248
+ await client.grantProfessionalAccessSimple(
249
+ { tenantId, jurisdiction, sector },
250
+ {
251
+ subjectDid: subjectDidWeb,
252
+ actor: { identifier: doctorDidWeb },
253
+ actorRole: 'Practitioner',
254
+ purpose: 'TREAT',
255
+ actions: ['organization/Composition.rs'],
256
+ },
257
+ );
258
+ ```
259
+
260
+ ### Step 10. Doctor uses SMART token to read permitted resources
261
+
262
+ Use bearer returned by step 7 in subsequent resource calls.
263
+
264
+ ## 5) Minimal backend request contracts from frontend
265
+
266
+ Legal activate request:
267
+
268
+ ```json
269
+ {
270
+ "jurisdiction": "ES",
271
+ "sector": "health-care",
272
+ "vpToken": "<vp_token>"
273
+ }
274
+ ```
275
+
276
+ Personal register request:
277
+
278
+ ```json
279
+ {
280
+ "tenantId": "acme",
281
+ "jurisdiction": "ES",
282
+ "sector": "health-care",
283
+ "registrationPayload": {},
284
+ "confirmationPayload": {}
285
+ }
286
+ ```
287
+
288
+ ## 6) Primary SDK methods checklist
289
+
290
+ 1. `activateOrganizationInGatewayFromIcaProof`
291
+ 2. extract `offerId` from activation response
292
+ 3. `submitAndPoll` + `hostRegistryOrderBatchPath` / `hostRegistryOrderPollPath`
293
+ 4. `createOrganizationEmployee`
294
+ 5. `activateEmployeeDeviceWithActivationCodeSimple`
295
+ 6. `requestSmartTokenSimple`
296
+ 7. `bootstrapSubjectOrganizationIndex`
297
+ 8. `grantProfessionalAccessSimple`
298
+
299
+ ## 7) Async/poll UX pattern (important)
300
+
301
+ All onboarding operations are async (`submit` + `poll`).
302
+
303
+ Recommended backend pattern per step:
304
+ 1. Emit status `submitted` after POST returns `202`.
305
+ 2. Poll until completion.
306
+ 3. Emit status `completed` or `failed` with diagnostics.
307
+
308
+ Polling interval behavior:
309
+ - if caller sets `intervalMs`, that value is forced.
310
+ - if caller does not set `intervalMs`, SDK uses backend `Retry-After` when present.
311
+ - fallback default is `2000ms`.
312
+
313
+ Recommended portal UX states:
314
+ 1. `Verifying identity proof`
315
+ 2. `Activating organization`
316
+ 3. `Offer available` (show price/summary)
317
+ 4. `Waiting for acceptance`
318
+ 5. `Submitting order`
319
+ 6. `Creating employee`
320
+ 7. `Activating device`
321
+ 8. `Token ready`
322
+
323
+ Implementation note:
324
+ - If you need real-time UX updates, expose backend progress via SSE/WebSocket.
325
+ - If not, frontend can poll your backend orchestration endpoint for step status.
326
+
327
+ ## 8) References
328
+
329
+ - `docs/BACKEND_NODE_INTEGRATION.md`
330
+ - `docs/REACT_WEB_INTEGRATION.md`
331
+ - `docs/DEVELOPER_USE_CASES.md`
332
+ - `docs/API.md`
333
+ - `examples/e2e-bootstrap-tenant.mjs`
334
+ - `tests/uc5-org-onboarding.flow.test.mjs`
335
+ - `tests/uc5-subject-data.flow.test.mjs`