@fluxyte/sdk 0.9.0 → 0.10.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
@@ -1,666 +1,700 @@
1
- # Fluxyte SDK (TypeScript)
2
-
3
- The official TypeScript SDK for the Fluxyte Onboarding AI API: a context-aware,
4
- documentation-grounded assistant designed for onboarding and setup flows (not generic chat).
5
-
6
- ## Unreleased
7
-
8
- - Chat and voice now use top-level `sessionId` or `userId`; their context is optional.
9
- - `PublicChatContext` contains only validated catalogue/step hints, diagnostics, runtime metadata, locale, and timezone.
10
- - Structured `error` and `attemptedAction` objects provide richer troubleshooting context.
11
- - Named public context, request, response, diagnostic, and event types are exported from all entrypoints.
12
-
13
- ## What's New In 0.7.0
14
-
15
- - Product Catalogue offering cards now expose optional `imageAlt` and `videoUrl` fields.
16
- - Use `imageAlt` when rendering catalogue images and open `videoUrl` as a validated hosted-video destination.
17
- - Existing `imageUrl`, pricing, availability, and Commerce action fields remain backward compatible.
18
-
19
- ```ts
20
- const response = await client.chat({
21
- message: "Show me your plans",
22
- sessionId: "session_123",
23
- });
24
-
25
- for (const offering of response.commerce?.offerings ?? []) {
26
- console.log(offering.imageUrl, offering.imageAlt, offering.videoUrl);
27
- }
28
- ```
29
-
30
- ## What's New In 0.6.0
31
-
32
- - Product Catalogue responses now include typed `commerce` presentation data.
33
- - Commerce presentations contain products/services offerings, prices, availability, features, and safe action IDs.
34
- - `openCommerceAction()` resolves an external checkout only after the API validates the organization, plan, catalogue publication, offering availability, and action.
35
- - `useStreamingChat()` now exposes both `commerce` and `resources` from the completed response.
36
-
37
- ## What's New In 0.5.0
38
-
39
- - Structured resources are available on chat, streaming, and voice responses.
40
- - Supported resource kinds: `VIDEO`, `AUDIO`, `STORE`, `REDIRECT`, `REFERRAL`, and `CHECKOUT`.
41
- - Video and audio resources can include a safe `embedUrl` for inline rendering.
42
- - `useStreamingChat()` now exposes the completed response's `resources`.
43
-
44
- ## Install
45
-
46
- ```bash
47
- npm install @fluxyte/sdk
48
- ```
49
-
50
- ## Requirements
51
-
52
- - Browser or Node.js 18+ (global `fetch` available)
53
- - A Fluxyte API key (sent as `X-API-Key`)
54
- - `pk_*` for browser/public SDK usage
55
- - `sk_*` for backend/server usage only
56
-
57
- ## Authentication (API Key)
58
-
59
- Treat your API key like a password. Do not commit it to source control.
60
-
61
- ### Create an API key
62
-
63
- 1. Sign up at `https://fluxyte.com/signup`
64
- 2. Verify your email
65
- 3. Open the `Account` menu
66
- 4. Select `API Keys`
67
- 5. Click `Create API Key`
68
- 6. Choose key type:
69
- - `pk_*` (PUBLIC) for frontend/browser SDK usage
70
- - `sk_*` (SECRET) for backend/server-only usage
71
- 7. Copy your API key (shown once)
72
-
73
- ### Use an API key
74
-
75
- All integrations authenticate with an API key sent in the `X-API-Key` header.
76
-
77
- - Use `pk_*` keys in browser/public SDK integrations
78
- - Use `sk_*` keys only in backend/server environments
79
- - For `pk_*` keys, configure allowed origins to restrict frontend domains
80
- - The key controls organization access, docs scope, and analytics ownership
81
-
82
- ### Environment examples
83
-
84
- - Vite: `VITE_FLUXYTE_API_KEY=pk_...`
85
- - Next.js: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...` (public key only)
86
- - Server: `FLUXYTE_API_KEY=sk_...`
87
-
88
- The SDK rejects `sk_*` keys in browser environments to prevent accidental exposure.
89
-
90
- ## Key Management
91
-
92
- ### Key Purpose
93
-
94
- - `pk_*` (PUBLIC): for browser/frontend SDK usage.
95
- - `sk_*` (SECRET): for backend/server usage only.
96
-
97
- ### How to get keys
98
-
99
- 1. Sign in to Fluxyte dashboard.
100
- 2. Go to `Account` -> `API Keys`.
101
- 3. Create a key and choose type:
102
- - `PUBLIC (pk_*)` for frontend SDK traffic.
103
- - `SECRET (sk_*)` for backend automation or server integrations.
104
- 4. Copy the key immediately (shown once).
105
-
106
- ### Allowed origins (PUBLIC keys)
107
-
108
- For `pk_*` keys, configure allowed origins in dashboard (for example `https://app.example.com`).
109
- This limits where browser-based SDK requests are accepted from.
110
-
111
- ### Rotation and revocation
112
-
113
- - Rotate keys on a regular schedule (recommended every 60-90 days).
114
- - Keep a short overlap window where old and new keys are both valid during rollout.
115
- - Revoke keys immediately if exposed.
116
- - Update environment variables and redeploy clients after rotation.
117
-
118
- ### Environment examples
119
-
120
- - Frontend: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...`
121
- - Backend: `FLUXYTE_API_KEY=sk_...`
122
-
123
- Never expose `sk_*` in browser bundles, client logs, or public repos.
124
-
125
- ## Choose An Entrypoint
126
-
127
- The SDK ships multiple entrypoints, all with the same API surface:
128
-
129
- - `@fluxyte/sdk/react` (React hooks + provider)
130
- - `@fluxyte/sdk/node` (server-side; validates `fetch` availability)
131
- - `@fluxyte/sdk/vanilla` (browser usage without React)
132
- - `@fluxyte/sdk` (base exports)
133
-
134
- ## Create A Client (Node / Vanilla)
135
-
136
- ```ts
137
- import { OnboardingAIClient } from "@fluxyte/sdk/node";
138
-
139
- const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!);
140
- ```
141
-
142
- ### Client Options (Session Handling)
143
-
144
- The SDK enforces stable identity. Chat and voice accept top-level `sessionId` or
145
- `userId`; onboarding events retain identity inside their event context.
146
-
147
- - By default, it auto-generates and reuses `sessionId` when missing.
148
- - You can disable this with `autoSessionId: false`.
149
-
150
- ```ts
151
- const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!, {
152
- autoSessionId: true, // default
153
- sessionStorageKey: "fluxyte_onboarding_session_id", // browser storage key
154
- });
155
- ```
156
-
157
- ## Chat Completions (Non-Streaming)
158
-
159
- ```ts
160
- const res = await client.chat({
161
- message: "How do I connect my database?",
162
- sessionId: "sess_123",
163
- context: {
164
- stepSlug: "connect_database",
165
- pageUrl: "https://app.example.com/setup/database",
166
- },
167
- });
168
-
169
- console.log(res.answerId);
170
- console.log(res.reply);
171
- console.log(res.confidence);
172
- console.log(res.sources);
173
- console.log(res.resources);
174
- console.log(res.commerce);
175
- ```
176
-
177
- ### Product Catalogue and Commerce
178
-
179
- When an organization has published catalogue items and its plan includes Commerce,
180
- chat responses may include a structured presentation alongside the natural-language
181
- reply:
182
-
183
- ```ts
184
- for (const offering of res.commerce?.offerings ?? []) {
185
- console.log(offering.name);
186
- console.log(offering.priceAmountMinor, offering.currency);
187
- console.log(offering.availability);
188
- console.log(offering.features);
189
- console.log(offering.actions);
190
- }
191
-
192
- if (res.commerce?.hasMore) {
193
- // Send a normal semantic continuation, such as "Show me more".
194
- }
195
- ```
196
-
197
- The API returns at most five offerings for catalogue discovery at a time. Do not
198
- implement client-side slicing or infer the next page; send the user's semantic
199
- continuation so the server can advance durable Commerce state.
200
-
201
- Catalogue actions are typed as:
202
-
203
- - `EXTERNAL_CHECKOUT` — resolve through `openCommerceAction()`.
204
- - `REQUEST_QUOTE` — send the action label/intention through chat.
205
- - `CONTACT_SALES` — send the action label/intention through chat.
206
-
207
- Only external checkout is a redirect. Never construct or cache checkout URLs from
208
- catalogue data. Resolve the selected action immediately before navigation:
209
-
210
- ```ts
211
- const action = offering.actions.find(
212
- (candidate) => candidate.kind === "EXTERNAL_CHECKOUT",
213
- );
214
-
215
- if (action) {
216
- const redirect = await client.openCommerceAction(action.id, "sess_123");
217
- if (redirect.kind === "REDIRECT") {
218
- window.location.assign(redirect.url);
219
- }
220
- }
221
- ```
222
-
223
- ### Rich Content Resources
224
-
225
- When approved knowledge or a flow step includes a related resource, the response
226
- can contain:
227
-
228
- ```ts
229
- type ContentResource = {
230
- id: string;
231
- kind: "VIDEO" | "AUDIO" | "STORE" | "REDIRECT" | "REFERRAL" | "CHECKOUT";
232
- url: string;
233
- label: string;
234
- embedUrl?: string | null;
235
- };
236
- ```
237
-
238
- Use `url` for links and actions. Use `embedUrl` only when it is present; it is
239
- the server-approved URL intended for an embedded video or audio player.
240
-
241
- ```ts
242
- for (const resource of res.resources ?? []) {
243
- if (resource.embedUrl) {
244
- renderEmbeddedMedia(resource.embedUrl, resource.label);
245
- } else {
246
- renderResourceLink(resource.url, resource.label);
247
- }
248
- }
249
- ```
250
-
251
- ## Chat Completions (Streaming via SSE)
252
-
253
- Streaming is useful when you want:
254
-
255
- - live typing UX
256
- - progressive rendering
257
- - early access to `answerId` (for feedback)
258
-
259
- Streaming lifecycle:
260
-
261
- 1. `meta` (contains `answerId`)
262
- 2. `delta` (partial text; one or more)
263
- 3. `done` (confidence, sources, resources, commerce)
264
-
265
- ```ts
266
- const stop = client.streamChat(
267
- {
268
- message: "What's the next step?",
269
- sessionId: "sess_123",
270
- context: { stepSlug: "connect_database" },
271
- },
272
- (event) => {
273
- if (event.type === "meta") console.log("Answer ID:", event.answerId);
274
- if (event.type === "delta") process.stdout.write(event.replyDelta);
275
- if (event.type === "done") {
276
- console.log("\nConfidence:", event.confidence);
277
- console.log("Sources:", event.sources);
278
- console.log("Resources:", event.resources);
279
- console.log("Commerce:", event.commerce);
280
- }
281
- },
282
- (err) => {
283
- // Optional: handle auth/network/server errors.
284
- console.error("Streaming error:", err);
285
- },
286
- );
287
-
288
- // stop() cancels streaming
289
- ```
290
-
291
- ## Voice Chat (Speech In + Optional Speech Out)
292
-
293
- ```ts
294
- const voice = await client.voiceChat({
295
- audioBase64: "BASE64_AUDIO",
296
- mimeType: "audio/webm",
297
- userId: "user_123",
298
- context: { stepSlug: "connect_database", locale: "en-NG" },
299
- synthesize: true,
300
- voice: "alloy",
301
- audioFormat: "mp3",
302
- });
303
-
304
- console.log(voice.transcript); // text recognized from audio
305
- console.log(voice.reply); // AI text reply
306
- console.log(voice.replyAudioBase64); // optional synthesized voice
307
- console.log(voice.resources); // approved related links or embedded media
308
- ```
309
-
310
- ## Context
311
-
312
- Context is optional for chat and voice requests. Identity is separate and required;
313
- the SDK auto-generates a reusable `sessionId` unless `autoSessionId` is disabled.
314
-
315
- ```ts
316
- type PublicChatContext = {
317
- catalogueSubjectId?: string;
318
- stepSlug?: string;
319
- endpoint?: string;
320
- pageUrl?: string;
321
- fieldHelp?: {
322
- fieldKey: string;
323
- label?: string;
324
- task?: string;
325
- currentValue?: string;
326
- };
327
- error?: {
328
- code?: string;
329
- description?: string;
330
- field?: string;
331
- source?: "client" | "server" | "network" | "third_party";
332
- httpStatus?: number;
333
- retryable?: boolean;
334
- };
335
- attemptedAction?: {
336
- name: string;
337
- description?: string;
338
- target?: string;
339
- status?: "started" | "blocked" | "failed" | "completed";
340
- };
341
- sdk?: "react" | "node" | "vanilla" | "rest";
342
- sdkVersion?: string;
343
- appVersion?: string;
344
- environment?: "development" | "staging" | "production";
345
- locale?: string;
346
- timezone?: string;
347
- };
348
- ```
349
-
350
- Best practices:
351
-
352
- - Pass `catalogueSubjectId` when the customer is viewing a published product or service.
353
- - Pass `stepSlug` when the customer is inside a published onboarding step.
354
- - Pass `fieldHelp` for headless field-level guidance. Your application owns the button, layout, and any field update.
355
- - Keep `sessionId` and `userId` at the request's top level, outside context.
356
- - Treat context like application state (update it as the user moves)
357
- - For task assistance or troubleshooting, pass non-secret diagnostic context when known
358
-
359
- Identity guidance:
360
-
361
- - Anonymous visitors: let SDK auto-manage `sessionId` or pass your own persisted ID.
362
- - Logged-in users: pass a stable top-level `userId`.
363
-
364
- ### Localization & Timezone
365
-
366
- Pass optional localization and timezone fields so the API can tailor user-facing
367
- formatting and time-aware utility responses:
368
-
369
- ```ts
370
- context: {
371
- locale: "en-US", // browser/client locale for user-facing formatting
372
- timezone: "Africa/Lagos", // IANA timezone for the current user
373
- }
374
- ```
375
-
376
- - `locale` — browser/client locale (for example `en-US`, `en-NG`) used for user-facing formatting.
377
- - `timezone` — IANA timezone for the current user (for example `America/New_York`, `Africa/Lagos`, `Europe/London`).
378
-
379
- ### Task and troubleshooting context
380
-
381
- The public assistant can use structured context to understand the user's current task
382
- and troubleshoot with fewer clarification turns. Pass only non-secret values:
383
-
384
- ```ts
385
- await client.chat({
386
- message: "I get a 401 when I send my first message",
387
- userId: "user_123",
388
- context: {
389
- sdk: "react",
390
- sdkVersion: "0.8.0",
391
- appVersion: "2026.08.30",
392
- environment: "production",
393
- endpoint: "/v1/chat/completions",
394
- pageUrl: "https://app.example.com/setup/chat",
395
- error: {
396
- code: "AUTHENTICATION_FAILED",
397
- description: "The first chat request returned 401 after the user saved their public API key.",
398
- source: "server",
399
- httpStatus: 401,
400
- retryable: false,
401
- },
402
- attemptedAction: {
403
- name: "send_first_chat_message",
404
- description: "Send the first message from the React setup screen.",
405
- target: "Public AI chat",
406
- status: "failed",
407
- },
408
- },
409
- });
410
- ```
411
-
412
- - `sdk` identifies the client or entrypoint in use.
413
- - `sdkVersion` identifies the Fluxyte SDK version and `appVersion` the integrating app.
414
- - `environment` is `development`, `staging`, or `production`.
415
- - `endpoint` identifies the affected API route or operation.
416
- - `error` carries structured, customer-safe diagnostics, including a comprehensive description.
417
- - `attemptedAction` describes the action, target, and outcome without duplicating the user's message.
418
-
419
- Do not put API keys, access tokens, passwords, request bodies containing personal
420
- data, or other secrets in context.
421
-
422
- ### Headless field help
423
-
424
- The SDK does not render field-help buttons or mutate form values. Build the UI that
425
- fits your application and send the current field as context:
426
-
427
- ```ts
428
- await client.chat({
429
- message: "What should I enter here?",
430
- context: {
431
- stepSlug: "business-details",
432
- pageUrl: window.location.href,
433
- fieldHelp: {
434
- fieldKey: "annualRevenue",
435
- label: "Annual revenue",
436
- task: "Completing a business loan application",
437
- currentValue: "50000",
438
- },
439
- },
440
- });
441
- ```
442
-
443
- `fieldHelp` improves routing but is not evidence. When `stepSlug` resolves to an
444
- authored input field, its server-owned definition remains authoritative. Never send
445
- passwords, tokens, payment credentials, identity secrets, or other sensitive values.
446
-
447
- #### Field-help properties
448
-
449
- | Property | Required | Purpose |
450
- | --- | --- | --- |
451
- | `fieldKey` | Yes | Stable form or property key used to identify the field. |
452
- | `label` | No | Customer-visible label that helps interpret unfamiliar field keys. |
453
- | `task` | No | Short description of the form or task the customer is completing. |
454
- | `currentValue` | No | Non-sensitive value useful for explaining formatting or validation. |
455
-
456
- Use `stepSlug` whenever the field belongs to a published Fluxyte flow step. Public AI
457
- validates that step against the organization's catalogue and uses its authored input
458
- definition when the `fieldKey` matches. Without `stepSlug`, field help can still use
459
- approved Knowledge content, but the client-provided label, task, and value never become
460
- authoritative evidence.
461
-
462
- Field-help requests continue through the standard Public AI architecture:
463
-
464
- - Flow or Task Assist handles guidance tied to an authored flow step.
465
- - Knowledge handles documented field meaning and requirements.
466
- - Troubleshooting handles rejected values and field-related errors.
467
- - Utility is not selected merely because a field contains a number, date, or currency.
468
- - The normal evidence, policy, multilingual, and response-composition stages remain active.
469
-
470
- The same context works with `chat`, `streamChat`, and `voiceChat`:
471
-
472
- ```ts
473
- const context: PublicChatContext = {
474
- stepSlug: "business-details",
475
- fieldHelp: {
476
- fieldKey: "annualRevenue",
477
- label: "Annual revenue",
478
- task: "Completing a business loan application",
479
- },
480
- };
481
-
482
- const answer = await client.chat({
483
- message: "What does this field mean?",
484
- context,
485
- });
486
-
487
- const stop = client.streamChat(
488
- { message: "What format should I use?", context },
489
- (event) => console.log(event),
490
- );
491
-
492
- const voice = await client.voiceChat({
493
- audioBase64,
494
- mimeType: "audio/webm",
495
- context,
496
- });
497
- ```
498
-
499
- Your application decides whether field help appears as an icon, tooltip, side panel,
500
- chat prompt, or another interface. Responses are guidance only: do not update a field
501
- unless your application explicitly validates the value and the customer accepts it.
502
-
503
- Security rules:
504
-
505
- - Omit `currentValue` unless it materially improves the answer.
506
- - Never send passwords, passcodes, tokens, API keys, PINs, OTPs, card details,
507
- government identifiers, private documents, or hidden form values.
508
- - Prefer a customer-safe error description through `context.error` when troubleshooting.
509
- - Keep stable user/session identity at the request top level, not inside `fieldHelp`.
510
-
511
- ## Onboarding Events
512
-
513
- Events power onboarding analytics, drop-off detection, and AI insights.
514
-
515
- Supported event types:
516
-
517
- 1. `FLOW_STARTED`
518
-
519
- - User begins a tracked onboarding flow.
520
- - Send once when the flow is entered.
521
-
522
- 2. `STEP_VIEWED`
523
-
524
- - User lands on or opens a specific step.
525
- - Send whenever the current step changes.
526
-
527
- 3. `STEP_COMPLETED`
528
-
529
- - User successfully completes a step.
530
- - Send only after verified completion signal.
531
-
532
- 4. `FLOW_COMPLETED`
533
-
534
- - User completes all required steps in the flow.
535
- - Send once at successful finish.
536
-
537
- 5. `ABANDONED`
538
-
539
- - User exits/stalls before completion.
540
- - Send when inactivity timeout or explicit exit indicates drop-off.
541
-
542
- Recommended minimum context for event quality:
543
-
544
- - `flowId`
545
- - `stepId` (for step-level events)
546
- - stable identity (`context.sessionId` or `context.userId`)
547
-
548
- Typical event sequence:
549
-
550
- 1. `FLOW_STARTED`
551
- 2. `STEP_VIEWED` (step 1)
552
- 3. `STEP_COMPLETED` (step 1)
553
- 4. `STEP_VIEWED` (step 2)
554
- 5. `STEP_COMPLETED` (step 2)
555
- 6. `FLOW_COMPLETED`
556
-
557
- Drop-off sequence example:
558
-
559
- 1. `FLOW_STARTED`
560
- 2. `STEP_VIEWED` (step 1)
561
- 3. `STEP_VIEWED` (step 2)
562
- 4. `ABANDONED`
563
-
564
- ```ts
565
- await client.sendEvent({
566
- type: "STEP_COMPLETED",
567
- context: {
568
- flowId: "premium-support-setup",
569
- stepId: "connect-database",
570
- userId: "customer_123",
571
- },
572
- });
573
- ```
574
-
575
- ## Feedback
576
-
577
- ```ts
578
- await client.submitFeedback({
579
- answerId: "ans_7xk9p2m",
580
- rating: "GOOD",
581
- comment: "Clear explanation and actionable advice",
582
- });
583
- ```
584
-
585
- ## React Usage (Recommended)
586
-
587
- ```tsx
588
- import { OnboardingAIClient } from "@fluxyte/sdk";
589
- import { OnboardingAIProvider } from "@fluxyte/sdk/react";
590
-
591
- const client = new OnboardingAIClient(import.meta.env.VITE_FLUXYTE_API_KEY);
592
-
593
- export function App() {
594
- return (
595
- <OnboardingAIProvider client={client}>
596
- {/* your app */}
597
- </OnboardingAIProvider>
598
- );
599
- }
600
- ```
601
-
602
- ### React Hooks
603
-
604
- ```tsx
605
- import { useChat } from "@fluxyte/sdk/react";
606
-
607
- const { send, data, loading, error } = useChat();
608
-
609
- await send({
610
- message: "How do I connect my database?",
611
- sessionId: "sess_123",
612
- context: { stepSlug: "connect_database" },
613
- });
614
- ```
615
-
616
- ```tsx
617
- import { useStreamingChat } from "@fluxyte/sdk/react";
618
-
619
- const { text, answerId, confidence, resources, commerce, streaming, start, stop } =
620
- useStreamingChat();
621
-
622
- start({
623
- message: "What's the next step?",
624
- context: { stepSlug: "connect_database" },
625
- });
626
- ```
627
-
628
- ```tsx
629
- import { useVoiceChat } from "@fluxyte/sdk/react";
630
-
631
- const { sendVoice, loading, data, error } = useVoiceChat();
632
-
633
- const res = await sendVoice({
634
- audioBase64: "BASE64_AUDIO",
635
- mimeType: "audio/webm",
636
- context: { stepSlug: "connect_database" },
637
- synthesize: true,
638
- voice: "alloy",
639
- audioFormat: "mp3",
640
- });
641
-
642
- console.log(res.transcript);
643
- console.log(res.reply);
644
- ```
645
-
646
- ## Multi-Target
647
-
648
- Use `context.product` or `context.service` to isolate onboarding targets under one account (apps, APIs, and services).
649
-
650
- ## Error Handling
651
-
652
- ```ts
653
- import { APIError } from "@fluxyte/sdk";
654
-
655
- try {
656
- await client.chat(/* ... */);
657
- } catch (err) {
658
- if (err instanceof APIError) {
659
- console.error(err.status, err.message);
660
- }
661
- }
662
- ```
663
-
664
- ## License
665
-
666
- Commercial. See `LICENSE.md`.
1
+ # Fluxyte SDK (TypeScript)
2
+
3
+ The official TypeScript SDK for the Fluxyte Onboarding AI API: a context-aware,
4
+ documentation-grounded assistant designed for onboarding and setup flows (not generic chat).
5
+
6
+ ## Unreleased
7
+
8
+ - Commerce cart SDK methods and typed server-authoritative cart contracts are available on this feature branch (not yet published).
9
+
10
+ ## Commerce cart SDK (unreleased)
11
+
12
+ The Commerce cart is **server-authoritative** and separate from the ordinary chat session. Use a public `pk_*` API key, and keep the server-minted cart credential private. Do not log, publish, or place it in URLs. The SDK does not automatically persist this bearer credential.
13
+
14
+ ```ts
15
+ import { OnboardingAIClient } from "@fluxyte/sdk";
16
+
17
+ const client = new OnboardingAIClient("pk_YOUR_PUBLIC_KEY");
18
+ const session = await client.createCommerceCartSession();
19
+ let credential = session.credential;
20
+ let cart = session.cart;
21
+
22
+ const result = await client.mutateCommerceCart({
23
+ credential,
24
+ expectedRevision: cart.revision,
25
+ requestKey: crypto.randomUUID(), // reuse this key when retrying the SAME mutation
26
+ mutation: { kind: "ADD", offeringId: "PUBLISHED_OFFERING_ID", quantity: 2 },
27
+ });
28
+ cart = result.cart;
29
+
30
+ const preflight = await client.preflightCommerceCheckout({
31
+ credential,
32
+ expectedRevision: cart.revision,
33
+ });
34
+ // Preflight is a server-side quote/eligibility check, NOT an order or payment confirmation.
35
+ ```
36
+
37
+ Available helpers: `createCommerceCartSession()`, `renewCommerceCartSession(credential)`, `readCommerceCart(credential)`, `mutateCommerceCart({ credential, expectedRevision, requestKey, mutation })`, and `preflightCommerceCheckout({ credential, expectedRevision })`. Mutations support `ADD`, `SET_QUANTITY`, and `REMOVE`. The backend validates offering availability, prices, currency, revision, and idempotency. On a stale revision, read the latest cart before deciding whether to issue a **new** mutation; do not blindly replay a different action under the same request key. Renewal requires an unexpired credential and does not revoke the prior credential.
38
+
39
+ These methods require a deployed backend with the Commerce cart routes and configured session signing/rate-limit infrastructure. They do not initiate payment or guarantee checkout success. Existing chat, streaming, voice and legacy Commerce action APIs remain available. No package release has been published for these unreleased changes.
40
+
41
+
42
+ - Chat and voice now use top-level `sessionId` or `userId`; their context is optional.
43
+ - `PublicChatContext` contains only validated catalogue/step hints, diagnostics, runtime metadata, locale, and timezone.
44
+ - Structured `error` and `attemptedAction` objects provide richer troubleshooting context.
45
+ - Named public context, request, response, diagnostic, and event types are exported from all entrypoints.
46
+
47
+ ## What's New In 0.7.0
48
+
49
+ - Product Catalogue offering cards now expose optional `imageAlt` and `videoUrl` fields.
50
+ - Use `imageAlt` when rendering catalogue images and open `videoUrl` as a validated hosted-video destination.
51
+ - Existing `imageUrl`, pricing, availability, and Commerce action fields remain backward compatible.
52
+
53
+ ```ts
54
+ const response = await client.chat({
55
+ message: "Show me your plans",
56
+ sessionId: "session_123",
57
+ });
58
+
59
+ for (const offering of response.commerce?.offerings ?? []) {
60
+ console.log(offering.imageUrl, offering.imageAlt, offering.videoUrl);
61
+ }
62
+ ```
63
+
64
+ ## What's New In 0.6.0
65
+
66
+ - Product Catalogue responses now include typed `commerce` presentation data.
67
+ - Commerce presentations contain products/services offerings, prices, availability, features, and safe action IDs.
68
+ - `openCommerceAction()` resolves an external checkout only after the API validates the organization, plan, catalogue publication, offering availability, and action.
69
+ - `useStreamingChat()` now exposes both `commerce` and `resources` from the completed response.
70
+
71
+ ## What's New In 0.5.0
72
+
73
+ - Structured resources are available on chat, streaming, and voice responses.
74
+ - Supported resource kinds: `VIDEO`, `AUDIO`, `STORE`, `REDIRECT`, `REFERRAL`, and `CHECKOUT`.
75
+ - Video and audio resources can include a safe `embedUrl` for inline rendering.
76
+ - `useStreamingChat()` now exposes the completed response's `resources`.
77
+
78
+ ## Install
79
+
80
+ ```bash
81
+ npm install @fluxyte/sdk
82
+ ```
83
+
84
+ ## Requirements
85
+
86
+ - Browser or Node.js 18+ (global `fetch` available)
87
+ - A Fluxyte API key (sent as `X-API-Key`)
88
+ - `pk_*` for browser/public SDK usage
89
+ - `sk_*` for backend/server usage only
90
+
91
+ ## Authentication (API Key)
92
+
93
+ Treat your API key like a password. Do not commit it to source control.
94
+
95
+ ### Create an API key
96
+
97
+ 1. Sign up at `https://fluxyte.com/signup`
98
+ 2. Verify your email
99
+ 3. Open the `Account` menu
100
+ 4. Select `API Keys`
101
+ 5. Click `Create API Key`
102
+ 6. Choose key type:
103
+ - `pk_*` (PUBLIC) for frontend/browser SDK usage
104
+ - `sk_*` (SECRET) for backend/server-only usage
105
+ 7. Copy your API key (shown once)
106
+
107
+ ### Use an API key
108
+
109
+ All integrations authenticate with an API key sent in the `X-API-Key` header.
110
+
111
+ - Use `pk_*` keys in browser/public SDK integrations
112
+ - Use `sk_*` keys only in backend/server environments
113
+ - For `pk_*` keys, configure allowed origins to restrict frontend domains
114
+ - The key controls organization access, docs scope, and analytics ownership
115
+
116
+ ### Environment examples
117
+
118
+ - Vite: `VITE_FLUXYTE_API_KEY=pk_...`
119
+ - Next.js: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...` (public key only)
120
+ - Server: `FLUXYTE_API_KEY=sk_...`
121
+
122
+ The SDK rejects `sk_*` keys in browser environments to prevent accidental exposure.
123
+
124
+ ## Key Management
125
+
126
+ ### Key Purpose
127
+
128
+ - `pk_*` (PUBLIC): for browser/frontend SDK usage.
129
+ - `sk_*` (SECRET): for backend/server usage only.
130
+
131
+ ### How to get keys
132
+
133
+ 1. Sign in to Fluxyte dashboard.
134
+ 2. Go to `Account` -> `API Keys`.
135
+ 3. Create a key and choose type:
136
+ - `PUBLIC (pk_*)` for frontend SDK traffic.
137
+ - `SECRET (sk_*)` for backend automation or server integrations.
138
+ 4. Copy the key immediately (shown once).
139
+
140
+ ### Allowed origins (PUBLIC keys)
141
+
142
+ For `pk_*` keys, configure allowed origins in dashboard (for example `https://app.example.com`).
143
+ This limits where browser-based SDK requests are accepted from.
144
+
145
+ ### Rotation and revocation
146
+
147
+ - Rotate keys on a regular schedule (recommended every 60-90 days).
148
+ - Keep a short overlap window where old and new keys are both valid during rollout.
149
+ - Revoke keys immediately if exposed.
150
+ - Update environment variables and redeploy clients after rotation.
151
+
152
+ ### Environment examples
153
+
154
+ - Frontend: `NEXT_PUBLIC_FLUXYTE_API_KEY=pk_...`
155
+ - Backend: `FLUXYTE_API_KEY=sk_...`
156
+
157
+ Never expose `sk_*` in browser bundles, client logs, or public repos.
158
+
159
+ ## Choose An Entrypoint
160
+
161
+ The SDK ships multiple entrypoints, all with the same API surface:
162
+
163
+ - `@fluxyte/sdk/react` (React hooks + provider)
164
+ - `@fluxyte/sdk/node` (server-side; validates `fetch` availability)
165
+ - `@fluxyte/sdk/vanilla` (browser usage without React)
166
+ - `@fluxyte/sdk` (base exports)
167
+
168
+ ## Create A Client (Node / Vanilla)
169
+
170
+ ```ts
171
+ import { OnboardingAIClient } from "@fluxyte/sdk/node";
172
+
173
+ const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!);
174
+ ```
175
+
176
+ ### Client Options (Session Handling)
177
+
178
+ The SDK enforces stable identity. Chat and voice accept top-level `sessionId` or
179
+ `userId`; onboarding events retain identity inside their event context.
180
+
181
+ - By default, it auto-generates and reuses `sessionId` when missing.
182
+ - You can disable this with `autoSessionId: false`.
183
+
184
+ ```ts
185
+ const client = new OnboardingAIClient(process.env.FLUXYTE_API_KEY!, {
186
+ autoSessionId: true, // default
187
+ sessionStorageKey: "fluxyte_onboarding_session_id", // browser storage key
188
+ });
189
+ ```
190
+
191
+ ## Chat Completions (Non-Streaming)
192
+
193
+ ```ts
194
+ const res = await client.chat({
195
+ message: "How do I connect my database?",
196
+ sessionId: "sess_123",
197
+ context: {
198
+ stepSlug: "connect_database",
199
+ pageUrl: "https://app.example.com/setup/database",
200
+ },
201
+ });
202
+
203
+ console.log(res.answerId);
204
+ console.log(res.reply);
205
+ console.log(res.confidence);
206
+ console.log(res.sources);
207
+ console.log(res.resources);
208
+ console.log(res.commerce);
209
+ ```
210
+
211
+ ### Product Catalogue and Commerce
212
+
213
+ When an organization has published catalogue items and its plan includes Commerce,
214
+ chat responses may include a structured presentation alongside the natural-language
215
+ reply:
216
+
217
+ ```ts
218
+ for (const offering of res.commerce?.offerings ?? []) {
219
+ console.log(offering.name);
220
+ console.log(offering.priceAmountMinor, offering.currency);
221
+ console.log(offering.availability);
222
+ console.log(offering.features);
223
+ console.log(offering.actions);
224
+ }
225
+
226
+ if (res.commerce?.hasMore) {
227
+ // Send a normal semantic continuation, such as "Show me more".
228
+ }
229
+ ```
230
+
231
+ The API returns at most five offerings for catalogue discovery at a time. Do not
232
+ implement client-side slicing or infer the next page; send the user's semantic
233
+ continuation so the server can advance durable Commerce state.
234
+
235
+ Catalogue actions are typed as:
236
+
237
+ - `EXTERNAL_CHECKOUT` — resolve through `openCommerceAction()`.
238
+ - `REQUEST_QUOTE` — send the action label/intention through chat.
239
+ - `CONTACT_SALES` — send the action label/intention through chat.
240
+
241
+ Only external checkout is a redirect. Never construct or cache checkout URLs from
242
+ catalogue data. Resolve the selected action immediately before navigation:
243
+
244
+ ```ts
245
+ const action = offering.actions.find(
246
+ (candidate) => candidate.kind === "EXTERNAL_CHECKOUT",
247
+ );
248
+
249
+ if (action) {
250
+ const redirect = await client.openCommerceAction(action.id, "sess_123");
251
+ if (redirect.kind === "REDIRECT") {
252
+ window.location.assign(redirect.url);
253
+ }
254
+ }
255
+ ```
256
+
257
+ ### Rich Content Resources
258
+
259
+ When approved knowledge or a flow step includes a related resource, the response
260
+ can contain:
261
+
262
+ ```ts
263
+ type ContentResource = {
264
+ id: string;
265
+ kind: "VIDEO" | "AUDIO" | "STORE" | "REDIRECT" | "REFERRAL" | "CHECKOUT";
266
+ url: string;
267
+ label: string;
268
+ embedUrl?: string | null;
269
+ };
270
+ ```
271
+
272
+ Use `url` for links and actions. Use `embedUrl` only when it is present; it is
273
+ the server-approved URL intended for an embedded video or audio player.
274
+
275
+ ```ts
276
+ for (const resource of res.resources ?? []) {
277
+ if (resource.embedUrl) {
278
+ renderEmbeddedMedia(resource.embedUrl, resource.label);
279
+ } else {
280
+ renderResourceLink(resource.url, resource.label);
281
+ }
282
+ }
283
+ ```
284
+
285
+ ## Chat Completions (Streaming via SSE)
286
+
287
+ Streaming is useful when you want:
288
+
289
+ - live typing UX
290
+ - progressive rendering
291
+ - early access to `answerId` (for feedback)
292
+
293
+ Streaming lifecycle:
294
+
295
+ 1. `meta` (contains `answerId`)
296
+ 2. `delta` (partial text; one or more)
297
+ 3. `done` (confidence, sources, resources, commerce)
298
+
299
+ ```ts
300
+ const stop = client.streamChat(
301
+ {
302
+ message: "What's the next step?",
303
+ sessionId: "sess_123",
304
+ context: { stepSlug: "connect_database" },
305
+ },
306
+ (event) => {
307
+ if (event.type === "meta") console.log("Answer ID:", event.answerId);
308
+ if (event.type === "delta") process.stdout.write(event.replyDelta);
309
+ if (event.type === "done") {
310
+ console.log("\nConfidence:", event.confidence);
311
+ console.log("Sources:", event.sources);
312
+ console.log("Resources:", event.resources);
313
+ console.log("Commerce:", event.commerce);
314
+ }
315
+ },
316
+ (err) => {
317
+ // Optional: handle auth/network/server errors.
318
+ console.error("Streaming error:", err);
319
+ },
320
+ );
321
+
322
+ // stop() cancels streaming
323
+ ```
324
+
325
+ ## Voice Chat (Speech In + Optional Speech Out)
326
+
327
+ ```ts
328
+ const voice = await client.voiceChat({
329
+ audioBase64: "BASE64_AUDIO",
330
+ mimeType: "audio/webm",
331
+ userId: "user_123",
332
+ context: { stepSlug: "connect_database", locale: "en-NG" },
333
+ synthesize: true,
334
+ voice: "alloy",
335
+ audioFormat: "mp3",
336
+ });
337
+
338
+ console.log(voice.transcript); // text recognized from audio
339
+ console.log(voice.reply); // AI text reply
340
+ console.log(voice.replyAudioBase64); // optional synthesized voice
341
+ console.log(voice.resources); // approved related links or embedded media
342
+ ```
343
+
344
+ ## Context
345
+
346
+ Context is optional for chat and voice requests. Identity is separate and required;
347
+ the SDK auto-generates a reusable `sessionId` unless `autoSessionId` is disabled.
348
+
349
+ ```ts
350
+ type PublicChatContext = {
351
+ catalogueSubjectId?: string;
352
+ stepSlug?: string;
353
+ endpoint?: string;
354
+ pageUrl?: string;
355
+ fieldHelp?: {
356
+ fieldKey: string;
357
+ label?: string;
358
+ task?: string;
359
+ currentValue?: string;
360
+ };
361
+ error?: {
362
+ code?: string;
363
+ description?: string;
364
+ field?: string;
365
+ source?: "client" | "server" | "network" | "third_party";
366
+ httpStatus?: number;
367
+ retryable?: boolean;
368
+ };
369
+ attemptedAction?: {
370
+ name: string;
371
+ description?: string;
372
+ target?: string;
373
+ status?: "started" | "blocked" | "failed" | "completed";
374
+ };
375
+ sdk?: "react" | "node" | "vanilla" | "rest";
376
+ sdkVersion?: string;
377
+ appVersion?: string;
378
+ environment?: "development" | "staging" | "production";
379
+ locale?: string;
380
+ timezone?: string;
381
+ };
382
+ ```
383
+
384
+ Best practices:
385
+
386
+ - Pass `catalogueSubjectId` when the customer is viewing a published product or service.
387
+ - Pass `stepSlug` when the customer is inside a published onboarding step.
388
+ - Pass `fieldHelp` for headless field-level guidance. Your application owns the button, layout, and any field update.
389
+ - Keep `sessionId` and `userId` at the request's top level, outside context.
390
+ - Treat context like application state (update it as the user moves)
391
+ - For task assistance or troubleshooting, pass non-secret diagnostic context when known
392
+
393
+ Identity guidance:
394
+
395
+ - Anonymous visitors: let SDK auto-manage `sessionId` or pass your own persisted ID.
396
+ - Logged-in users: pass a stable top-level `userId`.
397
+
398
+ ### Localization & Timezone
399
+
400
+ Pass optional localization and timezone fields so the API can tailor user-facing
401
+ formatting and time-aware utility responses:
402
+
403
+ ```ts
404
+ context: {
405
+ locale: "en-US", // browser/client locale for user-facing formatting
406
+ timezone: "Africa/Lagos", // IANA timezone for the current user
407
+ }
408
+ ```
409
+
410
+ - `locale` — browser/client locale (for example `en-US`, `en-NG`) used for user-facing formatting.
411
+ - `timezone` — IANA timezone for the current user (for example `America/New_York`, `Africa/Lagos`, `Europe/London`).
412
+
413
+ ### Task and troubleshooting context
414
+
415
+ The public assistant can use structured context to understand the user's current task
416
+ and troubleshoot with fewer clarification turns. Pass only non-secret values:
417
+
418
+ ```ts
419
+ await client.chat({
420
+ message: "I get a 401 when I send my first message",
421
+ userId: "user_123",
422
+ context: {
423
+ sdk: "react",
424
+ sdkVersion: "0.8.0",
425
+ appVersion: "2026.08.30",
426
+ environment: "production",
427
+ endpoint: "/v1/chat/completions",
428
+ pageUrl: "https://app.example.com/setup/chat",
429
+ error: {
430
+ code: "AUTHENTICATION_FAILED",
431
+ description: "The first chat request returned 401 after the user saved their public API key.",
432
+ source: "server",
433
+ httpStatus: 401,
434
+ retryable: false,
435
+ },
436
+ attemptedAction: {
437
+ name: "send_first_chat_message",
438
+ description: "Send the first message from the React setup screen.",
439
+ target: "Public AI chat",
440
+ status: "failed",
441
+ },
442
+ },
443
+ });
444
+ ```
445
+
446
+ - `sdk` identifies the client or entrypoint in use.
447
+ - `sdkVersion` identifies the Fluxyte SDK version and `appVersion` the integrating app.
448
+ - `environment` is `development`, `staging`, or `production`.
449
+ - `endpoint` identifies the affected API route or operation.
450
+ - `error` carries structured, customer-safe diagnostics, including a comprehensive description.
451
+ - `attemptedAction` describes the action, target, and outcome without duplicating the user's message.
452
+
453
+ Do not put API keys, access tokens, passwords, request bodies containing personal
454
+ data, or other secrets in context.
455
+
456
+ ### Headless field help
457
+
458
+ The SDK does not render field-help buttons or mutate form values. Build the UI that
459
+ fits your application and send the current field as context:
460
+
461
+ ```ts
462
+ await client.chat({
463
+ message: "What should I enter here?",
464
+ context: {
465
+ stepSlug: "business-details",
466
+ pageUrl: window.location.href,
467
+ fieldHelp: {
468
+ fieldKey: "annualRevenue",
469
+ label: "Annual revenue",
470
+ task: "Completing a business loan application",
471
+ currentValue: "50000",
472
+ },
473
+ },
474
+ });
475
+ ```
476
+
477
+ `fieldHelp` improves routing but is not evidence. When `stepSlug` resolves to an
478
+ authored input field, its server-owned definition remains authoritative. Never send
479
+ passwords, tokens, payment credentials, identity secrets, or other sensitive values.
480
+
481
+ #### Field-help properties
482
+
483
+ | Property | Required | Purpose |
484
+ | --- | --- | --- |
485
+ | `fieldKey` | Yes | Stable form or property key used to identify the field. |
486
+ | `label` | No | Customer-visible label that helps interpret unfamiliar field keys. |
487
+ | `task` | No | Short description of the form or task the customer is completing. |
488
+ | `currentValue` | No | Non-sensitive value useful for explaining formatting or validation. |
489
+
490
+ Use `stepSlug` whenever the field belongs to a published Fluxyte flow step. Public AI
491
+ validates that step against the organization's catalogue and uses its authored input
492
+ definition when the `fieldKey` matches. Without `stepSlug`, field help can still use
493
+ approved Knowledge content, but the client-provided label, task, and value never become
494
+ authoritative evidence.
495
+
496
+ Field-help requests continue through the standard Public AI architecture:
497
+
498
+ - Flow or Task Assist handles guidance tied to an authored flow step.
499
+ - Knowledge handles documented field meaning and requirements.
500
+ - Troubleshooting handles rejected values and field-related errors.
501
+ - Utility is not selected merely because a field contains a number, date, or currency.
502
+ - The normal evidence, policy, multilingual, and response-composition stages remain active.
503
+
504
+ The same context works with `chat`, `streamChat`, and `voiceChat`:
505
+
506
+ ```ts
507
+ const context: PublicChatContext = {
508
+ stepSlug: "business-details",
509
+ fieldHelp: {
510
+ fieldKey: "annualRevenue",
511
+ label: "Annual revenue",
512
+ task: "Completing a business loan application",
513
+ },
514
+ };
515
+
516
+ const answer = await client.chat({
517
+ message: "What does this field mean?",
518
+ context,
519
+ });
520
+
521
+ const stop = client.streamChat(
522
+ { message: "What format should I use?", context },
523
+ (event) => console.log(event),
524
+ );
525
+
526
+ const voice = await client.voiceChat({
527
+ audioBase64,
528
+ mimeType: "audio/webm",
529
+ context,
530
+ });
531
+ ```
532
+
533
+ Your application decides whether field help appears as an icon, tooltip, side panel,
534
+ chat prompt, or another interface. Responses are guidance only: do not update a field
535
+ unless your application explicitly validates the value and the customer accepts it.
536
+
537
+ Security rules:
538
+
539
+ - Omit `currentValue` unless it materially improves the answer.
540
+ - Never send passwords, passcodes, tokens, API keys, PINs, OTPs, card details,
541
+ government identifiers, private documents, or hidden form values.
542
+ - Prefer a customer-safe error description through `context.error` when troubleshooting.
543
+ - Keep stable user/session identity at the request top level, not inside `fieldHelp`.
544
+
545
+ ## Onboarding Events
546
+
547
+ Events power onboarding analytics, drop-off detection, and AI insights.
548
+
549
+ Supported event types:
550
+
551
+ 1. `FLOW_STARTED`
552
+
553
+ - User begins a tracked onboarding flow.
554
+ - Send once when the flow is entered.
555
+
556
+ 2. `STEP_VIEWED`
557
+
558
+ - User lands on or opens a specific step.
559
+ - Send whenever the current step changes.
560
+
561
+ 3. `STEP_COMPLETED`
562
+
563
+ - User successfully completes a step.
564
+ - Send only after verified completion signal.
565
+
566
+ 4. `FLOW_COMPLETED`
567
+
568
+ - User completes all required steps in the flow.
569
+ - Send once at successful finish.
570
+
571
+ 5. `ABANDONED`
572
+
573
+ - User exits/stalls before completion.
574
+ - Send when inactivity timeout or explicit exit indicates drop-off.
575
+
576
+ Recommended minimum context for event quality:
577
+
578
+ - `flowId`
579
+ - `stepId` (for step-level events)
580
+ - stable identity (`context.sessionId` or `context.userId`)
581
+
582
+ Typical event sequence:
583
+
584
+ 1. `FLOW_STARTED`
585
+ 2. `STEP_VIEWED` (step 1)
586
+ 3. `STEP_COMPLETED` (step 1)
587
+ 4. `STEP_VIEWED` (step 2)
588
+ 5. `STEP_COMPLETED` (step 2)
589
+ 6. `FLOW_COMPLETED`
590
+
591
+ Drop-off sequence example:
592
+
593
+ 1. `FLOW_STARTED`
594
+ 2. `STEP_VIEWED` (step 1)
595
+ 3. `STEP_VIEWED` (step 2)
596
+ 4. `ABANDONED`
597
+
598
+ ```ts
599
+ await client.sendEvent({
600
+ type: "STEP_COMPLETED",
601
+ context: {
602
+ flowId: "premium-support-setup",
603
+ stepId: "connect-database",
604
+ userId: "customer_123",
605
+ },
606
+ });
607
+ ```
608
+
609
+ ## Feedback
610
+
611
+ ```ts
612
+ await client.submitFeedback({
613
+ answerId: "ans_7xk9p2m",
614
+ rating: "GOOD",
615
+ comment: "Clear explanation and actionable advice",
616
+ });
617
+ ```
618
+
619
+ ## React Usage (Recommended)
620
+
621
+ ```tsx
622
+ import { OnboardingAIClient } from "@fluxyte/sdk";
623
+ import { OnboardingAIProvider } from "@fluxyte/sdk/react";
624
+
625
+ const client = new OnboardingAIClient(import.meta.env.VITE_FLUXYTE_API_KEY);
626
+
627
+ export function App() {
628
+ return (
629
+ <OnboardingAIProvider client={client}>
630
+ {/* your app */}
631
+ </OnboardingAIProvider>
632
+ );
633
+ }
634
+ ```
635
+
636
+ ### React Hooks
637
+
638
+ ```tsx
639
+ import { useChat } from "@fluxyte/sdk/react";
640
+
641
+ const { send, data, loading, error } = useChat();
642
+
643
+ await send({
644
+ message: "How do I connect my database?",
645
+ sessionId: "sess_123",
646
+ context: { stepSlug: "connect_database" },
647
+ });
648
+ ```
649
+
650
+ ```tsx
651
+ import { useStreamingChat } from "@fluxyte/sdk/react";
652
+
653
+ const { text, answerId, confidence, resources, commerce, streaming, start, stop } =
654
+ useStreamingChat();
655
+
656
+ start({
657
+ message: "What's the next step?",
658
+ context: { stepSlug: "connect_database" },
659
+ });
660
+ ```
661
+
662
+ ```tsx
663
+ import { useVoiceChat } from "@fluxyte/sdk/react";
664
+
665
+ const { sendVoice, loading, data, error } = useVoiceChat();
666
+
667
+ const res = await sendVoice({
668
+ audioBase64: "BASE64_AUDIO",
669
+ mimeType: "audio/webm",
670
+ context: { stepSlug: "connect_database" },
671
+ synthesize: true,
672
+ voice: "alloy",
673
+ audioFormat: "mp3",
674
+ });
675
+
676
+ console.log(res.transcript);
677
+ console.log(res.reply);
678
+ ```
679
+
680
+ ## Multi-Target
681
+
682
+ Use `context.product` or `context.service` to isolate onboarding targets under one account (apps, APIs, and services).
683
+
684
+ ## Error Handling
685
+
686
+ ```ts
687
+ import { APIError } from "@fluxyte/sdk";
688
+
689
+ try {
690
+ await client.chat(/* ... */);
691
+ } catch (err) {
692
+ if (err instanceof APIError) {
693
+ console.error(err.status, err.message);
694
+ }
695
+ }
696
+ ```
697
+
698
+ ## License
699
+
700
+ Commercial. See `LICENSE.md`.