fluxchat/sdk

Documentation

FluxChat SDK

Add an AI assistant — or a full in-app support experience — to any product. This kit ships a typed SDK, a CLI, and an embeddable widget that adapts to light & dark.

API version

Installation

Works with npm, pnpm or yarn. Requires Node.js 18+ (the widget runs in any browser).

terminal
# npm · pnpm · yarn
npm install @fluxchat_sdk/sdk
pnpm add @fluxchat_sdk/sdk
yarn add @fluxchat_sdk/sdk

Quickstart

1. Get your API key

Open the FluxChat dashboard → API Keys → Generate — or use the provision endpoint to get a dev and prod key at once. Add bot:write to manage the knowledge base.

2. Send your first message

Create a client and call ask. Use context for real-time, priority data.

first-call.ts
import { FluxChat } from '@fluxchat_sdk/sdk';

const fluxchat = new FluxChat({ apiKey: process.env.FLUXCHAT_API_KEY });

const { reply } = await fluxchat.ask({
  message: 'What is the status of my order?',
  context: 'Order #1234 — shipped on June 3rd.',   // v2: injected as priority truth
});

3. Add the widget (optional)

index.html
<script src="https://unpkg.com/@fluxchat_sdk/sdk/dist/widget.global.js"></script>
<script>
  FluxChatWidget.init({
    apiKey: 'fc_prod_xxx',
    autoContext: true,   // captures page title, URL and visible text automatically
    autoCrawl: true,     // indexes this page in your KB on first load
  });
</script>

In-app support

FluxChat is a complete in-app support layer, not just an assistant. Embed the widget as your help channel: it answers from your knowledge base, uses live context about the signed-in user, and stays on-brand — cutting ticket volume.

support.ts
const { reply } = await fluxchat.ask({
  message: userQuestion,
  context: `Plan: ${user.plan}. Open tickets: ${tickets.length}.`,
});

FluxChat AI assistant

Use the same kit as a general assistant: a persona you control, grounded on your knowledge base, with continuity when you pass a conversationId.

  • Stateless by default for public widgets — nothing stored unless you keep a conversationId.
  • Persona per org — assistantName, tone, styleRules.
  • Priority contextcontext always wins over the KB.
  • Strict mode — bot only answers from your KB, refuses anything outside it.
  • Auto-context — widget captures the current page automatically, no code needed.

Architecture

FluxChat is a three-layer system: your browser / app, the FluxChat Gateway (NestJS, multi-tenant PostgreSQL, Redis), and FluxChat AI that never surfaces to end-users.

Browser / App                  FluxChat Gateway              FluxChat AI
─────────────────────────────────────────────────────────────────────
1. User visits page
   SDK auto-captures DOM ──────► POST /bot/pages
   + fetch/XHR JSON responses       │
   + localStorage snapshot          │ 2. extractAndLearn ─────────►
                                     │    (async, rate-limited)    │
                                     │ ◄── structured KB entry ────┘
                                     │    stored in bot_knowledge
                                     │
3. User sends message ─────────────► POST /bot/ask
                                     │
                                     │ 4. SQL ILIKE search
                                     │    bot_knowledge + bot_session_page
                                     │
                                     │ 5. Build system prompt
                                     │    = persona + KB context
                                     │      + live context + history
                                     │
                                     │ 6. Call FluxChat AI ────────►
                                     │ ◄── reply ──────────────────┘
4. ◄── reply ◄─────────────────────┘

Key components

LayerWhat it does
Widget SDKEmbeddable JS bundle — captures context, sends messages, renders the chat UI.
Gateway /bot/pagesReceives captures (page DOM, API JSON, localStorage). Stores in bot_session_page (24h TTL) and triggers async KB extraction.
extractAndLearnBackground job per capture — FluxChat AI extracts structured knowledge (title, content, category, keywords) and upserts it permanently into bot_knowledge.
bot_knowledgePermanent, searchable knowledge base per tenant schema. Source of truth for the bot.
bot_session_pageShort-lived live captures (24h). Searched first — highest priority, most recent data.
Gateway /bot/askCombines KB search results + context + conversation history into a system prompt, then calls FluxChat AI.
Strict modeWhen enabled, the bot only answers from bot_knowledge. Off by default — bot falls back to general knowledge.

Auto-KB pipelinev2 only

When autoCapture: true (the default), the widget silently captures every page the user visits and every JSON API response the app makes. The Gateway processes each capture in two stages.

Stage 1 — Ingest (immediate)

The widget POSTs the raw content to /api/v2/public/bot/pages. The Gateway stores it in bot_session_page (24-hour TTL) and makes it instantly searchable — the bot can answer questions about it right away.

Stage 2 — Extract (async, FluxChat AI)

For each new or changed capture, FluxChat AI extracts structured knowledge:

extracted-entry.json
{
  "title": "Bengali Chicken Curry",
  "content": "Chicken curry from Bengal. Ingredients: 4 chicken breasts…",
  "category": "recipe",
  "keywords": ["chicken", "curry", "bengal", "spices"]
}

This entry is written to bot_knowledge (permanent, no TTL). From that point on, the bot answers from the KB even after the session page expires. Extraction is rate-limited to 2 concurrent jobs per org to avoid spikes.

What gets captured

TypeSourceSkipped when
pageDOM innerText of <main> or <body> + visible linksUnder 80 chars (not yet rendered)
apiGET JSON responses from fetch() or XHRAuth endpoints, uploads, binary, > 80 KB
cachelocalStorage key/value snapshotToken, auth, JWT, session, password keys
Privacy: Auth tokens, passwords, JWTs, session keys and binary data are never captured. The widget filters by key name for localStorage and by URL pattern for API calls.

The embeddable widget

A floating chat bubble, great out of the box and fully themeable. Drop it in with a script tag, or import it in your framework.

widget.ts
import { init } from '@fluxchat_sdk/sdk/widget';

const widget = init({
  apiKey: 'fc_prod_xxx',
  clientName: 'Acme Bank',
  assistantName: 'Léa',
  primaryColor: '#5b6ef0',
  autoEnvDetect: true,  // DEV badge on localhost, prod badge in production
  autoContext: true,    // captures page context automatically
  autoCrawl: true,      // indexes pages in your KB silently
});
// widget.open() · widget.toggleTheme() · widget.destroy()

Quick replies

Show a row of tap-to-send chips below the greeting message. Great for onboarding — users see what the bot can answer before typing anything. The chips disappear as soon as the first message is sent.

widget-quick-replies.ts
import { init } from '@fluxchat_sdk/sdk/widget';

init({
  apiKey: 'fc_prod_xxx',
  greeting: 'Bonjour ! Comment puis-je vous aider ?',
  quickReplies: [
    'Quelles sont vos horaires ?',
    'Comment vous contacter ?',
    'Voir les tarifs',
    'Suivre ma commande',
  ],
});
Chips are horizontally scrollable — no limit on the number of items, though 3–5 is optimal for UX. Clicking a chip sends the message immediately and hides the row.

Smart input correctionv2 only

The widget automatically detects typos and grammar errors as the user types (debounced 900 ms). When a correction is found, a suggestion chip appears above the input — no configuration needed.

How it works

  1. User types a message with errors (minimum 7 characters).
  2. After 900 ms of inactivity, the widget sends the raw text to the bot with a correction prompt.
  3. If the bot returns a corrected version (normalized comparison — case, trailing punctuation), the suggestion chip appears above the input.
  4. User accepts with Tab / / chip click, or sends corrected text directly.
  ┌─────────────────────────────────────────────────┐
  │ Correction suggérée :  [quel est le curry ?]  Envoyer  ×  │
  ├─────────────────────────────────────────────────┤
  │  kelle es le cury ?                      [Send] │
  └─────────────────────────────────────────────────┘

The correction is powered by FluxChat AI — the UI label reads "Correction suggérée". No configuration required; the feature is always active in the widget.

Ghost text: If the corrected text starts with what the user already typed, the missing suffix is shown inline as grey ghost text — same UX as mobile autocomplete. Accept with Tab or at the end of the input.

Floating & inline

Two display modes via the mode option.

Floating (default) — a launcher bubble

floating.ts
init({ apiKey: 'fc_prod_xxx', mode: 'floating', position: 'right' });

Inline — a full support chat in your page

In inline mode the chat renders directly inside a container (no launcher, always open, fills its parent). Perfect for a support / help page. Give the container a height.

support-page.html
<div id="support" style="height: 600px; max-width: 460px"></div>

<script src="https://unpkg.com/@fluxchat_sdk/sdk/dist/widget.global.js"></script>
<script>
  FluxChatWidget.init({ apiKey: 'fc_prod_xxx', mode: 'inline', target: '#support' });
</script>

Light & dark

The widget ships with both themes and an in-header sun/moon toggle (themeToggle, default true). Set the initial theme with theme, or hide the switch with themeToggle: false.

Customization

OptionDefaultDescription
clientNameBrand name in the header.
assistantNameAssistantDisplay name & avatar initial.
primaryColor#4f46e5Header, bubbles & buttons color.
themelightInitial theme: light / dark.
themeToggletrueIn-header light/dark switch.
modefloatingfloating bubble or inline support chat.
positionrightLauncher corner (floating).
contextStatic context sent with every message.
showBrandingtrueShow the Benflux footer.
greetingBonjour…First bot message shown when the panel opens.
quickRepliesArray of tap-to-send chips shown below the greeting.
autoEnvDetectv2trueDEV badge + header on localhost / fc_dev_ keys.
autoContextv2trueAuto-capture page title, URL and visible text as context.
autoCapturev2truePassively capture every page + API call into the KB.
autoCrawlv2falseSilently index the current page in your KB on first load.

Dev / prod detectionv2 only

The widget auto-detects whether it is running in a development environment using two signals — the API key prefix and the current hostname — and adjusts its behavior automatically.

API key prefixes

terminal
fc_dev_xxx   → development (relaxed rate limits, verbose logs)
fc_prod_xxx  → production (full enforcement)

Use the provision endpoint to generate both keys at once for a new organisation:

provision.ts
POST /api/v2/organizations/:orgId/api-keys/provision
// returns { dev: { key: 'fc_dev_xxx' }, prod: { key: 'fc_prod_xxx' } }

Hostname detection

Even with a prod key, the widget detects local hostnames (localhost, 127.0.0.1, *.local, *.dev, *.test) and shows the DEV badge. The X-FluxChat-Env: development header is also sent with every request so the API can apply dev-mode rules.

widget.ts
init({
  apiKey: 'fc_dev_xxx',     // fc_dev_ prefix → dev mode
  autoEnvDetect: true,      // default — can be set to false to disable
});
Override: Pass X-FluxChat-Env: development as a header to force dev mode even with a prod key — useful in staging environments.

Auto-contextv2 only

When autoContext: true (the default in v2), the widget automatically captures the current page context and injects it into every message — no code required on your side.

Capture priority

  1. window.fluxchatContext — set by your app at runtime (highest priority).
  2. data-fluxchat="..." attributes on DOM elements.
  3. Page title + current URL.
  4. Visible text from <main> (or <body>), truncated to 3 000 chars.

The auto-captured context is merged with any static context option and truncated to 8 000 chars before sending.

Injecting app data at runtime

Set window.fluxchatContext once (or update it whenever state changes) so the bot always has access to your live data — user profile, cart, subscription, current record, etc. The widget reads it at send time, not at init.

runtime-context.js
// Basic — string or object, both work
window.fluxchatContext = "User: Marie, Plan: Pro, Cart: 2 items";

// Recommended — structured object (auto-serialised to JSON)
window.fluxchatContext = {
  user: { name: "Marie", email: "marie@example.com", plan: "Pro" },
  cart: [{ id: 1, name: "T-shirt", qty: 2, price: 29.99 }],
};

// Merging sections (e.g. set user at root, add billing on billing page)
window.fluxchatContext = {
  ...window.fluxchatContext,
  billing: { plan: "Enterprise", messages_used: 255, messages_limit: 500000 },
};

React / Next.js pattern

Set it in a useEffect at the root of your authenticated layout so the bot has full profile context on every page. Merge additional data (billing, cart, record details) deeper in the tree — each page just spreads the existing value and adds its own key.

AuthLayout.tsx
// Root authenticated layout — runs once after login
useEffect(() => {
  if (!user) return;
  window.fluxchatContext = {
    user: {
      name: user.displayName,
      email: user.email,
      role: user.role,
      phone: user.phone,
      location: user.location,
    },
    org: { name: currentOrg.name },
  };
}, [user, currentOrg]);

// Billing page — adds quota data on top, removes on unmount
useEffect(() => {
  window.fluxchatContext = {
    ...window.fluxchatContext,
    billing: { plan: "Enterprise", messages_used: 255, messages_limit: 500000 },
  };
  return () => {
    const c = { ...window.fluxchatContext };
    delete c.billing;
    window.fluxchatContext = c;
  };
}, [subscription, usage]);
The bot reads window.fluxchatContext at send time — update it at any point and the next message will include the latest value. No re-init needed.

Works with your stack

Pick your framework — copy, paste, done.

FluxChat.tsx
import { useEffect } from 'react';
import { init } from '@fluxchat_sdk/sdk/widget';

export function FluxChat() {
  useEffect(() => {
    const w = init({ apiKey: import.meta.env.VITE_FLUXCHAT_API_KEY });
    return () => w.destroy();
  }, []);
  return null;
}

SDK & ask

The FluxChat client authenticates with an API key (or a JWT for admin operations). The organizationId is optional — only for knowledge & config.

client.ts
const fluxchat = new FluxChat({
  apiKey: 'fc_prod_xxx',
  organizationId: 'org-uuid', // only needed for knowledge & config
});

const res = await fluxchat.ask({ message, context, conversationId });
const info = await fluxchat.testKey();
When do I need the Organization ID? Only for knowledge.* and config.* (org-scoped URLs). ask, testKey and the widget don't need it — the API key already identifies your org.

Persona & config

Give the assistant its own identity per organization — a name, a tone, style rules and extra instructions. Applied to every answer, no knowledge-base entry needed.

persona.ts
await fluxchat.config.update({
  assistantName: 'Léa',
  tone: 'chaleureux et concis',
  styleRules: 'Tutoie le client, évite le jargon.',
  captureTrainingData: false,
});

const current = await fluxchat.config.get();
In the FluxChat dashboard, the persona is editable in Bot IA → Personnalité using your admin session — no manual JWT needed.

Strict KB modev2 only

When strict mode is enabled for your organisation, the bot only answers questions covered by your knowledge base. General knowledge questions (politics, geography, unrelated topics…) are politely refused in the user's own language. Greetings, thanks and small talk are always allowed.

Strict mode is configured per-organisation in the dashboard (Bot IA → Configuration → Strict mode) or via the config API:

strict.ts
await fluxchat.config.update({ strictMode: true });
Language-aware: The bot detects the user's language automatically and replies — including refusal messages — in the same language. All world languages are supported.

Knowledge base

Writes work with a bot:write API key; reads need a JWT.

kb.ts
await fluxchat.knowledge.create({ title: 'Horaires', content: 'Lun–Ven 9h–18h.' });
await fluxchat.knowledge.update(id, { content: 'Lun–Ven 8h–19h.' });
await fluxchat.knowledge.remove(id);
const all = await fluxchat.knowledge.list(); // JWT

Auto-crawlv2 only

Populate the knowledge base directly from your website — no manual article creation. Submit a URL (or a sitemap.xml) and the API fetches each page, extracts its content, and creates KB articles automatically. Duplicate URLs are skipped.

Via the SDK

crawl.ts
// Single page
await fluxchat.knowledge.crawl({ url: 'https://acme.com/faq' });

// From a sitemap (up to 20 pages)
await fluxchat.knowledge.crawl({
  url: 'https://acme.com/sitemap.xml',
  isSitemap: true,
  maxPages: 20,
});

Via the CLI

terminal
fluxchat kb crawl --url https://acme.com/faq
fluxchat kb crawl --url https://acme.com/sitemap.xml --sitemap --max-pages 20

Via the widget (autoCrawl)

Set autoCrawl: true in the widget options and it silently indexes the current page URL on first load — zero backend code required.

widget.ts
init({ apiKey: 'fc_prod_xxx', autoCrawl: true });
The crawl API requires a bot:write API key. Errors are swallowed in the widget — the chat works normally even if the crawl fails.

CLI

Credentials via flags or FLUXCHAT_* env vars.

terminal
export FLUXCHAT_API_KEY="fc_prod_xxx"
fluxchat test
fluxchat ask "Bonjour !" --context "Client VIP"
fluxchat kb create --title "FAQ" --content "…"

# v2 — crawl a URL or sitemap
fluxchat kb crawl --url https://acme.com/faq
fluxchat kb crawl --url https://acme.com/sitemap.xml --sitemap --max-pages 20

Auth & scopes

OperationAuth
ask, testKey, widgetAPI key (X-API-Key)
Knowledge write, crawlAPI key with bot:write
Knowledge read, persona configJWT (admin)

Dev & prod keysv2 only

v2 introduces two key types for the same organisation. Use your dev key locally and your prod key in production — same codebase, no environment switching required.

KeyPrefixBehaviour
Devfc_dev_xxxRelaxed rate limits, verbose API logs, DEV badge in widget.
Prodfc_prod_xxxFull enforcement, no extra logging, no DEV badge.

Provision both keys at once for a new organisation:

provision.sh
curl -X POST /api/v2/organizations/$ORG_ID/api-keys/provision \
  -H "Authorization: Bearer $ADMIN_JWT"
The widget detects the key prefix automatically (autoEnvDetect: true by default) and shows a DEV badge in the header when a dev key is used, or when the hostname is local.

API versions

The FluxChat API is URI-versioned. Every endpoint is available on both /api/v1 and /api/v2. This SDK and the widget target v2 by default.

VersionBase URLNotes
v2 Latest…/api/v2Context, stateless asks, strict mode, auto-crawl, dev/prod keys.
v1 Stable…/api/v1Stable. Conversations always persisted. No context field.

Sending context to v1 returns 400 — use v2 for context-aware answers.

For Developers — Build an SDK

The JS/TypeScript SDK is the reference implementation. Community SDKs for other languages are tracked as open GitHub issues. Pick one, fork the repo, work inside sdk/<language>/, and submit a PR. All SDKs call the same REST API — no framework dependency required. Documentation lives in each SDK's README.md; this table is updated when a PR is merged.

Community SDKs

StackStatusREADMEIssuePackage
JS / TypeScriptavailableREADME →npm
PythonopenREADME →Claim →
PHPopenREADME →Claim →
Dart / FlutteravailableREADME →pub add
GoavailableREADME →go get
C# / .NETopenREADME →Claim →
Swift (iOS / macOS)openREADME →Claim →
Kotlin (Android)openREADME →Claim →
React NativeopenREADME →Claim →
This table is updated when a PR is merged. Contributors write their documentation in sdk/<language>/README.md — no need to modify this site.

Developer Sandbox

The sandbox is a real FluxChat organization reserved for contributors. It has an active 1-year subscription — all features unlocked, no rate limit surprises. Use it to test your SDK against the live API without creating your own account.

Sandbox credentials — public, do not use for production

Loginheyakaf832@ocuser.com
PasswordTest1234567890@
Org IDba134db3-993d-4076-8431-bb2c922d4db2
API Keyfc_prod_f45868df738ddbec537c6c929570f1dad830fb0ca0cd5f82652e9eb7db4ede16
Base URLhttps://dev-api.fluxchat-corp.com/api/v2
The sandbox is shared — do not store personal data in it. Reset the KB anytime from the dashboard: Bot → Knowledge base → Delete all.

Verifying your capture works

Testing capturePage is not just about getting a 204 back. You need to prove the full pipeline ran end-to-end: the page arrived, FluxChat AI extracted the knowledge, and the bot uses it when answering. Follow these 5 steps every time you test a capture.

Step 1 — Send a capture with a unique phrase

Use a phrase that could not possibly come from the AI general knowledge — something invented and specific. This eliminates false positives.

test-capture.http
POST https://dev-api.fluxchat-corp.com/api/v2/public/bot/pages
Content-Type: application/json
X-API-Key: fc_prod_f45868df738ddbec537c6c929570f1dad830fb0ca0cd5f82652e9eb7db4ede16

{
  "url": "https://test.example.com/about",
  "title": "About FluxTest",
  "content": "FluxTest is a fictional company founded in 2099 by Zara Kowalski. Our slogan is: code never lies, only humans do."
}

Expected response:

response
HTTP/1.1 204 No Content

204 means the capture was received and queued for extraction. The AI extraction runs async — wait ~5 seconds before the next step.

Step 2 — Ask the bot about the unique phrase

Now ask the bot something only answerable from that captured content.

test-ask.http
POST https://dev-api.fluxchat-corp.com/api/v2/public/bot/ask
Content-Type: application/json
X-API-Key: fc_prod_f45868df738ddbec537c6c929570f1dad830fb0ca0cd5f82652e9eb7db4ede16

{
  "message": "Who founded FluxTest and what is their slogan?"
}

Expected response (proof that extraction worked):

response.json
{
  "success": true,
  "data": {
    "reply": "FluxTest was founded in 2099 by Zara Kowalski. Their slogan is: code never lies, only humans do.",
    "conversationId": "",
    "confidence": 1
  }
}

If the bot answers with details from your captured page — your SDK works. If it says "I don't have this information", either the capture did not reach the server (check your request) or the extraction is still running (retry after 10 seconds).

Step 3 — Confirm in the dashboard

For a definitive proof, log in to the dashboard and check the knowledge base directly.

1Go to fluxchat-corp.com and log in with the sandbox credentials above.

2In the left sidebar, click Bot then Base de connaissances.

3You should see an entry titled "About FluxTest" with the extracted content. If it appears — the full pipeline worked.

4If the entry is NOT there after 30 seconds, the extraction failed. Check that content was not empty and was under 6000 characters.

Step 4 — Test context maintenance

A correct SDK must also pass the same sessionId on every request. Without it, the bot loses context between messages. Verify it with two consecutive calls:

test-context.http
// Message 1 — introduce something
POST /public/bot/ask
{ "message": "My name is Zara.", "sessionId": "test-session-001" }
→ bot: "Nice to meet you, Zara!"

// Message 2 — same sessionId — test context retention
POST /public/bot/ask
{ "message": "What is my name?", "sessionId": "test-session-001" }
→ bot: "Your name is Zara."   ← PASS

// Message 2 — NO sessionId — test that context is correctly absent
POST /public/bot/ask
{ "message": "What is my name?", "sessionId": "test-session-002" }
→ bot: "I don't have your name."   ← PASS (different session, no context)
If the second call with the same sessionId does NOT remember "Zara", your SDK is generating a new session ID per request instead of reusing the same one. Fix: generate the sessionId once (store in memory or localStorage) and reuse it for the entire session.

Step 5 — What a failing capture looks like

Know the difference between a bug in your SDK and an expected API error:

error-cases.txt
400 Bad Request   → missing required field (url, title, or content is empty)
401 Unauthorized  → API key header missing or malformed
403 Forbidden     → API key is valid but lacks bot:write scope (use the sandbox key above — it has all scopes)
413               → content exceeds 6000 chars — truncate before sending
204 but bot doesn't answer → content was received but extraction is still running — wait 10s and retry
204 but entry missing from KB → content was too short or had no extractable facts (min ~50 words recommended)

REST API — base URL

Base URL: https://dev-api.fluxchat-corp.com/api/v2. All public endpoints require X-API-Key: fc_prod_your_key in the request header.

ask.http
POST /public/bot/ask
Content-Type: application/json
X-API-Key: fc_prod_your_key

{
  "message": "What are your opening hours?",
  "context": "User: Alice, Plan: Pro",    // optional — highest priority
  "sessionId": "sess_abc123",             // optional — stateful Redis session
  "conversationId": ""                    // optional — omit for stateless
}

// Success response (every endpoint follows this envelope)
{
  "success": true,
  "data": {
    "reply": "We are open Mon–Fri 9am–6pm.",
    "conversationId": "conv-uuid",        // empty string if stateless
    "intent": null,
    "confidence": 1
  },
  "timestamp": "2026-06-11T00:00:00.000Z"
}

// Error response
{
  "success": false,
  "statusCode": 403,
  "message": "API key missing required scope(s): bot:write",
  "timestamp": "2026-06-11T00:00:00.000Z"
}
other-endpoints.http
// Verify API key
GET /public/bot/test
X-API-Key: fc_prod_your_key
→ { "data": { "organizationId": "uuid", "scopes": ["bot:write"] } }

// Passive page capture (no bot:write needed)
POST /public/bot/pages
{ "url": "https://app.com/about", "title": "About", "content": "…max 6000 chars…" }
→ 204 No Content

// KB article — requires bot:write scope
POST   /bot/knowledge          { title, content, category, keywords }
PATCH  /bot/knowledge/:id      { content: "updated" }
DELETE /bot/knowledge/:id      → 204

// Admin (JWT required)
GET    /bot/knowledge           list all articles
GET    /bot/knowledge/:id       get one article
GET    /bot/config              get persona config
PATCH  /bot/config              { assistantName, tone, styleRules, strictMode }

What every SDK must implement

FeatureAuthRequiredDescription
ask(message, context?, conversationId?)API keyYesSend a message, return reply + conversationId
testKey()API keyYesVerify the key, return organizationId + scopes
capturePage(url, title, content)API keyRecommendedPOST /public/bot/pages — passive capture for mobile/desktop SDKs
knowledge.create(title, content, ...)API key (bot:write)YesAdd a KB article
knowledge.update(id, patch)API key (bot:write)YesUpdate a KB article
knowledge.delete(id)API key (bot:write)YesDelete a KB article
knowledge.list()JWT (admin)YesList all KB articles
knowledge.get(id)JWT (admin)YesGet one KB article
FluxChatNetworkErrorYesConnection refused, timeout, DNS failure
FluxChatApiError(status, message)YesHTTP 4xx / 5xx — include status code
FluxChatConfigError(message)YesMissing API key, invalid baseUrl

Repository structure — monorepo layout

The fluxchat-sdk repo is a monorepo. The root contains the official JS/TS SDK. Each community SDK lives in its own sdk/<language>/ subdirectory — self-contained with its own package config, README and tests.

fluxchat-sdk/
fluxchat-sdk/               ← root = official JS/TS SDK (published to npm)
├── src/
├── dist/
├── package.json
├── README.md
│
├── sdk/                    ← community SDKs live here
│   ├── python/             ← your contribution goes here
│   │   ├── README.md       # install + quickstart for Python
│   │   ├── src/
│   │   ├── tests/
│   │   └── pyproject.toml
│   │
│   ├── flutter/
│   │   ├── README.md
│   │   ├── lib/
│   │   ├── test/
│   │   └── pubspec.yaml
│   │
│   ├── go/
│   │   ├── README.md
│   │   ├── fluxchat.go
│   │   ├── fluxchat_test.go
│   │   └── go.mod
│   │
│   └── <your-stack>/       ← same pattern for every other language
│       ├── README.md
│       ├── src/
│       ├── tests/
│       └── <package config>

Test coverage required

Every SDK PR must include tests covering these cases:

test-cases.txt
✓ ask — successful response parsing
✓ ask — stateless (no conversationId → empty string returned)
✓ testKey — parse organizationId + scopes
✓ knowledge.create — create + parse response
✓ knowledge.list — list parsing
✓ knowledge.delete — 204 handling
✓ Network error (connection refused) → FluxChatNetworkError
✓ 401 Unauthorized → FluxChatApiError(401)
✓ 403 Forbidden (missing scope) → FluxChatApiError(403)

Minimal ask() — language examples

sdk/python/fluxchat.py
import httpx
from dataclasses import dataclass

BASE_URL = "https://dev-api.fluxchat-corp.com/api/v2"

@dataclass
class AskResponse:
    reply: str
    conversation_id: str

class FluxChatApiError(Exception):
    def __init__(self, status: int, message: str):
        self.status = status
        super().__init__(f"FluxChat API {status}: {message}")

class FluxChat:
    def __init__(self, api_key: str, base_url: str = BASE_URL):
        self._headers = {"X-API-Key": api_key, "Content-Type": "application/json"}
        self.base_url = base_url.rstrip("/")

    def ask(self, message: str, *, context: str | None = None,
            conversation_id: str | None = None) -> AskResponse:
        payload: dict = {"message": message}
        if context:     payload["context"] = context
        if conversation_id: payload["conversationId"] = conversation_id
        with httpx.Client() as c:
            r = c.post(f"{self.base_url}/public/bot/ask",
                       json=payload, headers=self._headers)
        if not r.is_success:
            raise FluxChatApiError(r.status_code, r.text)
        data = r.json()["data"]
        return AskResponse(reply=data["reply"],
                           conversation_id=data.get("conversationId", ""))

    def capture_page(self, url: str, title: str, content: str) -> None:
        with httpx.Client() as c:
            c.post(f"{self.base_url}/public/bot/pages",
                   json={"url": url, "title": title, "content": content[:6000]},
                   headers=self._headers)
sdk/go/client.go
package fluxchat

import (
    "bytes"; "encoding/json"; "fmt"; "net/http"
)

const defaultBaseURL = "https://dev-api.fluxchat-corp.com/api/v2"

type Client struct { apiKey, baseURL string; http *http.Client }

type AskOptions struct {
    Message        string `json:"message"`
    Context        string `json:"context,omitempty"`
    ConversationID string `json:"conversationId,omitempty"`
}
type AskResponse struct {
    Reply          string `json:"reply"`
    ConversationID string `json:"conversationId"`
}

func New(apiKey string) *Client {
    return &Client{apiKey: apiKey, baseURL: defaultBaseURL, http: &http.Client{}}
}

func (c *Client) Ask(opts AskOptions) (*AskResponse, error) {
    body, _ := json.Marshal(opts)
    req, _ := http.NewRequest("POST", c.baseURL+"/public/bot/ask", bytes.NewReader(body))
    req.Header.Set("X-API-Key", c.apiKey)
    req.Header.Set("Content-Type", "application/json")
    resp, err := c.http.Do(req)
    if err != nil { return nil, fmt.Errorf("network: %w", err) }
    defer resp.Body.Close()
    if resp.StatusCode >= 400 { return nil, fmt.Errorf("api error %d", resp.StatusCode) }
    var env struct{ Data AskResponse `json:"data"` }
    json.NewDecoder(resp.Body).Decode(&env)
    return &env.Data, nil
}
sdk/dart/fluxchat.dart
import 'dart:convert';
import 'package:http/http.dart' as http;

class FluxChat {
  final String apiKey;
  final String baseUrl;
  FluxChat({required this.apiKey,
            this.baseUrl = 'https://dev-api.fluxchat-corp.com/api/v2'});

  Future<Map<String, dynamic>> ask(String message,
      {String? context, String? conversationId}) async {
    final body = <String, dynamic>{'message': message};
    if (context != null) body['context'] = context;
    if (conversationId != null) body['conversationId'] = conversationId;
    final r = await http.post(
      Uri.parse('$baseUrl/public/bot/ask'),
      headers: {'X-API-Key': apiKey, 'Content-Type': 'application/json'},
      body: jsonEncode(body),
    );
    if (r.statusCode >= 400) throw Exception('FluxChat ${r.statusCode}: ${r.body}');
    final data = jsonDecode(r.body)['data'];
    return {'reply': data['reply'], 'conversationId': data['conversationId'] ?? ''};
  }

  Future<void> capturePage(String url, String title, String content) =>
    http.post(Uri.parse('$baseUrl/public/bot/pages'),
      headers: {'X-API-Key': apiKey, 'Content-Type': 'application/json'},
      body: jsonEncode({'url': url, 'title': title,
                        'content': content.substring(0, content.length.clamp(0, 6000))}));
}

How to contribute — workflow

terminal
# 1. Fork on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/fluxchat-sdk.git
cd fluxchat-sdk

# 2. Create your branch (sdk/<language> for new SDKs)
git checkout -b sdk/python

# 3. Create sdk/python/ with:
#    - README.md (install + quickstart)
#    - src/       (your implementation)
#    - tests/     (required test coverage — see above)
#    - pyproject.toml / go.mod / pubspec.yaml / etc.

# 4. Push and open a PR against main
git push origin sdk/python
# → open PR at github.com/benflux-company/fluxchat-sdk

Branch naming: sdk/<language> for new SDKs, feat/<description> for features, fix/<description> for bugs. All PRs require one review by @benbaruka before merge.

The bot pipeline — how a message becomes a reply

Understanding this pipeline is required to build a correct SDK. Here is exactly what happens on every POST /public/bot/ask:

pipeline.txt
User message → POST /public/bot/ask
        │
        ▼
1. Authenticate X-API-Key → resolve organizationId + org config (strictMode, persona)
        │
        ▼
2. Upsert per-request context into bot_session_page (BEFORE stateless check)
        │
        ▼
3. Stateless check: no conversationId → skip DB load, reply won't be persisted
        │
        ▼
4. Build knowledge context
   a. ILIKE search in bot_knowledge  (permanent KB articles)
   b. ILIKE search in bot_session_page  (passively captured pages)
        │
        ▼
5. Intent detection (pattern match) → if matched, call org API/DB for live data
        │
        ▼
6. Build system prompt
   = persona (name, tone, style rules)
   + anti-hallucination rule (conditional on strictMode)
   + KB context (step 4a)
   + session page context (step 4b)
   + live action data (step 5)  ← highest priority
   + per-request context (from SDK)
        │
        ▼
7. FluxChat AI call
        │
        ▼
8. Return { reply, conversationId }   ← conversationId is "" if stateless

Context priority order (highest → lowest)

#SourceWhat it is
1Action dataLive DB/API result from intent detection — absolute source of truth
2Per-request contextInjected by SDK per message — page content, user info, live platform data
3KB articlesCurated by admins — permanent, structured knowledge
4Session pagesPassively captured by SDK autoCapture — immediate, no admin needed
5FluxChat AI general knowledgeUsed only when nothing above matches AND strictMode is off

Zero-config features — implementing in non-JS SDKs

autoCapture on mobile / desktop (Flutter, Swift, Kotlin, React Native): instead of DOM interception, expose a manual API and call it from screen lifecycle hooks.

mobile-capture.dart
// Flutter — call when a new screen loads
@override
void initState() {
  super.initState();
  FluxChat.capturePage(
    url: 'app://my-app/${widget.routeName}',
    title: widget.title,
    content: extractVisibleText(),   // serialize the screen's visible text
  );
}
mobile-capture-rn.js
// React Native — call on screen focus
useEffect(() => {
  fluxchat.capturePage({
    url: `app://my-app/${route.name}`,
    title: route.params?.title ?? route.name,
    content: extractScreenText(),
  });
}, [route]);

autoContext — what to inject per request:

context-contract.txt
Priority order when building the context string:
1. window.fluxchatContext (or platform equivalent) — user, org, any runtime data
2. data-fluxchat="…" attributes on DOM / screen elements
3. Screen title + current URL / route
4. Visible text from the main content area (first message only, max 3000 chars)

Rules:
- Set persistent data (user, org) once at app root, not per screen
- Always MERGE page-specific data — never overwrite the full object
- Clean up page-specific keys on screen unmount
- DOM / screen scraping only on first message — subsequent messages use context only

HTTP status codes — what your SDK must handle:

StatusMeaningThrow
401Invalid or missing API keyFluxChatApiError(401)
403Valid key, missing scope (e.g. bot:write for KB writes)FluxChatApiError(403)
404Resource not found (knowledge article ID doesn't exist)FluxChatApiError(404)
422Validation error (message too long, missing required field)FluxChatApiError(422)
5xxServer errorFluxChatApiError(status)
networkConnection refused, timeout, DNS failureFluxChatNetworkError

Contributing to the JS SDK

FluxChat SDK is open source (MIT) on GitHub. Bug fixes and improvements are welcome.

terminal
git clone https://github.com/benflux-company/fluxchat-sdk
cd fluxchat-sdk && npm install
npm run build   # tsup → ESM + CJS + types + CLI + IIFE widget
npm test        # vitest
npm run typecheck

Source structure

src/
src/
├── index.ts            # main SDK entry (FluxChatClient, errors)
├── client.ts           # HTTP client
├── knowledge.ts        # KB CRUD
├── config.ts           # persona config
├── cli/
│   └── index.ts        # CLI commands (ask, test, kb, config)
└── widget/
    ├── widget.ts        # embeddable widget — DOM, SPA capture, autocorrect
    ├── types.ts         # WidgetOptions, WidgetInstance interfaces
    └── styles.ts        # CSS-in-JS widget styles

Contributors

People who built and maintain FluxChat. Want to join? Open an issue or submit a PR on GitHub.