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.
Installation
Works with npm, pnpm or yarn. Requires Node.js 18+ (the widget runs in any browser).
# npm · pnpm · yarn
npm install @fluxchat_sdk/sdk
pnpm add @fluxchat_sdk/sdk
yarn add @fluxchat_sdk/sdkQuickstart
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.
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)
<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.
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 context —
contextalways 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
| Layer | What it does |
|---|---|
| Widget SDK | Embeddable JS bundle — captures context, sends messages, renders the chat UI. |
| Gateway /bot/pages | Receives captures (page DOM, API JSON, localStorage). Stores in bot_session_page (24h TTL) and triggers async KB extraction. |
| extractAndLearn | Background job per capture — FluxChat AI extracts structured knowledge (title, content, category, keywords) and upserts it permanently into bot_knowledge. |
| bot_knowledge | Permanent, searchable knowledge base per tenant schema. Source of truth for the bot. |
| bot_session_page | Short-lived live captures (24h). Searched first — highest priority, most recent data. |
| Gateway /bot/ask | Combines KB search results + context + conversation history into a system prompt, then calls FluxChat AI. |
| Strict mode | When 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:
{
"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
| Type | Source | Skipped when |
|---|---|---|
page | DOM innerText of <main> or <body> + visible links | Under 80 chars (not yet rendered) |
api | GET JSON responses from fetch() or XHR | Auth endpoints, uploads, binary, > 80 KB |
cache | localStorage key/value snapshot | Token, auth, JWT, session, password keys |
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.
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.
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',
],
});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
- User types a message with errors (minimum 7 characters).
- After 900 ms of inactivity, the widget sends the raw text to the bot with a correction prompt.
- If the bot returns a corrected version (normalized comparison — case, trailing punctuation), the suggestion chip appears above the input.
- 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.
Tab or → at the end of the input.Floating & inline
Two display modes via the mode option.
Floating (default) — a launcher bubble
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.
<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
| Option | Default | Description |
|---|---|---|
clientName | — | Brand name in the header. |
assistantName | Assistant | Display name & avatar initial. |
primaryColor | #4f46e5 | Header, bubbles & buttons color. |
theme | light | Initial theme: light / dark. |
themeToggle | true | In-header light/dark switch. |
mode | floating | floating bubble or inline support chat. |
position | right | Launcher corner (floating). |
context | — | Static context sent with every message. |
showBranding | true | Show the Benflux footer. |
greeting | Bonjour… | First bot message shown when the panel opens. |
quickReplies | — | Array of tap-to-send chips shown below the greeting. |
autoEnvDetectv2 | true | DEV badge + header on localhost / fc_dev_ keys. |
autoContextv2 | true | Auto-capture page title, URL and visible text as context. |
autoCapturev2 | true | Passively capture every page + API call into the KB. |
autoCrawlv2 | false | Silently 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
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:
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.
init({
apiKey: 'fc_dev_xxx', // fc_dev_ prefix → dev mode
autoEnvDetect: true, // default — can be set to false to disable
});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
window.fluxchatContext— set by your app at runtime (highest priority).data-fluxchat="..."attributes on DOM elements.- Page
title+ currentURL. - 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.
// 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.
// 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]);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.
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.
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();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.
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();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:
await fluxchat.config.update({ strictMode: true });Knowledge base
Writes work with a bot:write API key; reads need a JWT.
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(); // JWTAuto-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
// 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
fluxchat kb crawl --url https://acme.com/faq
fluxchat kb crawl --url https://acme.com/sitemap.xml --sitemap --max-pages 20Via 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.
init({ apiKey: 'fc_prod_xxx', autoCrawl: true });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.
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 20Auth & scopes
| Operation | Auth |
|---|---|
ask, testKey, widget | API key (X-API-Key) |
| Knowledge write, crawl | API key with bot:write |
| Knowledge read, persona config | JWT (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.
| Key | Prefix | Behaviour |
|---|---|---|
| Dev | fc_dev_xxx | Relaxed rate limits, verbose API logs, DEV badge in widget. |
| Prod | fc_prod_xxx | Full enforcement, no extra logging, no DEV badge. |
Provision both keys at once for a new organisation:
curl -X POST /api/v2/organizations/$ORG_ID/api-keys/provision \
-H "Authorization: Bearer $ADMIN_JWT"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.
| Version | Base URL | Notes |
|---|---|---|
| v2 Latest | …/api/v2 | Context, stateless asks, strict mode, auto-crawl, dev/prod keys. |
| v1 Stable | …/api/v1 | Stable. 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
| Stack | Status | README | Issue | Package |
|---|---|---|---|---|
| JS / TypeScript | available | README → | — | npm → |
| Python | open | README → | Claim → | — |
| PHP | open | README → | Claim → | — |
| Dart / Flutter | available | README → | — | pub add → |
| Go | available | README → | — | go get → |
| C# / .NET | open | README → | Claim → | — |
| Swift (iOS / macOS) | open | README → | Claim → | — |
| Kotlin (Android) | open | README → | Claim → | — |
| React Native | open | README → | Claim → | — |
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
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.
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:
HTTP/1.1 204 No Content204 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.
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):
{
"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:
// 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)
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:
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.
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"
}// 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
| Feature | Auth | Required | Description |
|---|---|---|---|
| ask(message, context?, conversationId?) | API key | Yes | Send a message, return reply + conversationId |
| testKey() | API key | Yes | Verify the key, return organizationId + scopes |
| capturePage(url, title, content) | API key | Recommended | POST /public/bot/pages — passive capture for mobile/desktop SDKs |
| knowledge.create(title, content, ...) | API key (bot:write) | Yes | Add a KB article |
| knowledge.update(id, patch) | API key (bot:write) | Yes | Update a KB article |
| knowledge.delete(id) | API key (bot:write) | Yes | Delete a KB article |
| knowledge.list() | JWT (admin) | Yes | List all KB articles |
| knowledge.get(id) | JWT (admin) | Yes | Get one KB article |
| FluxChatNetworkError | — | Yes | Connection refused, timeout, DNS failure |
| FluxChatApiError(status, message) | — | Yes | HTTP 4xx / 5xx — include status code |
| FluxChatConfigError(message) | — | Yes | Missing 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/ ← 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:
✓ 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
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)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
}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
# 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-sdkBranch 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:
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 statelessContext priority order (highest → lowest)
| # | Source | What it is |
|---|---|---|
| 1 | Action data | Live DB/API result from intent detection — absolute source of truth |
| 2 | Per-request context | Injected by SDK per message — page content, user info, live platform data |
| 3 | KB articles | Curated by admins — permanent, structured knowledge |
| 4 | Session pages | Passively captured by SDK autoCapture — immediate, no admin needed |
| 5 | FluxChat AI general knowledge | Used 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.
// 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
);
}// 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:
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 onlyHTTP status codes — what your SDK must handle:
| Status | Meaning | Throw |
|---|---|---|
| 401 | Invalid or missing API key | FluxChatApiError(401) |
| 403 | Valid key, missing scope (e.g. bot:write for KB writes) | FluxChatApiError(403) |
| 404 | Resource not found (knowledge article ID doesn't exist) | FluxChatApiError(404) |
| 422 | Validation error (message too long, missing required field) | FluxChatApiError(422) |
| 5xx | Server error | FluxChatApiError(status) |
| network | Connection refused, timeout, DNS failure | FluxChatNetworkError |
Contributing to the JS SDK
FluxChat SDK is open source (MIT) on GitHub. Bug fixes and improvements are welcome.
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 typecheckSource structure
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 stylesContributors
People who built and maintain FluxChat. Want to join? Open an issue or submit a PR on GitHub.