SDK · Stable
Go SDK
Official Go SDK for FluxChat. Zero external dependencies (stdlib only) — works with any Go backend, CLI tool, or serverless function. Idiomatic functional-options API with full context propagation.
Installation
Requires Go 1.21+. No external dependencies — only the Go standard library.
go get github.com/benflux-company/fluxchat-sdk/sdk/go@v1.0.4module your-app
go 1.21
require github.com/benflux-company/fluxchat-sdk/sdk/go v1.0.4Quickstart
package main
import (
"context"
"fmt"
"log"
fluxchat "github.com/benflux-company/fluxchat-sdk/sdk/go"
)
func main() {
client, err := fluxchat.NewClient(os.Getenv("FLUXCHAT_API_KEY"))
if err != nil {
log.Fatal(err) // *fluxchat.ConfigError if key is empty
}
resp, err := client.Ask(context.Background(), "What are your opening hours?")
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Reply)
}Authentication
The SDK uses two auth mechanisms: X-API-Key for public bot operations (Ask, CapturePage, Knowledge write) and Bearer JWT for admin-only operations (Knowledge list / get).
Verify your API key
Call TestKey() at startup to verify the key is valid. As a side effect it caches theOrganizationID on the client — required for Knowledge Base operations.
info, err := client.TestKey(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(info.OrganizationID) // "92c6e8ab-..."
fmt.Println(info.Scopes) // ["bot:read", "bot:write"]Login (JWT — required for Knowledge list/get)
Knowledge list and get require an admin JWT. Call Login() once at startup and the JWT is stored automatically on the client for all subsequent Knowledge read operations.
// After NewClient + TestKey
loginResp, err := client.Login(ctx, "admin@example.com", "password")
if err != nil {
log.Fatal(err)
}
// JWT is now cached — knowledge.List() and knowledge.Get() work automatically
fmt.Println(loginResp.ExpiresIn) // "24h"Ask (chat)
Stateless message
Send a message without a conversation — the bot replies but does not retain session state.
resp, err := client.Ask(ctx, "What are your opening hours?")
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Reply)
fmt.Println(resp.ConversationID) // "" for statelessContinue a conversation
Pass the ConversationID from a previous reply to continue the same thread.
// First message
resp, _ := client.Ask(ctx, "Hello!")
// Continue the thread
resp2, _ := client.Ask(ctx, "And what about weekends?",
fluxchat.WithConversationID(resp.ConversationID),
)Session ID and context
resp, err := client.Ask(ctx, "Show me the pricing",
fluxchat.WithSessionID("user-alice-123"), // ties multiple calls to one session
fluxchat.WithContext("Plan: Pro, Locale: fr"), // priority context for the bot
)CapturePage
Passively capture a page's content so the bot can reference it when answering questions. This is the Go equivalent of the JS SDK's autoCapture. Returns no error on a 204 No Content response.
err := client.CapturePage(
ctx,
"https://yoursite.com/pricing", // canonical URL
"Pricing — Your App", // human-readable title
"Starter: $0/mo · Pro: $29/mo · ...", // visible text content
)
if err != nil {
log.Printf("capture failed: %v", err)
}In a web server, call CapturePage after rendering each page — the content is indexed by FluxChat and used as a high-priority knowledge source for bot replies.
IndexRoutes
IndexRoutes registers your API surface as permanent Knowledge Base articles at startup — before any request is made. The bot immediately knows what each endpoint does, its parameters, and expected responses, without waiting for real traffic to populate the KB.
Each RouteInfo becomes one KB article: title = METHOD /path (or your custom Title), content = description text.
// At startup, after TestKey + Login
err := client.IndexRoutes(ctx, []fluxchat.RouteInfo{
{
Method: "GET",
Path: "/api/products",
Title: "List products",
Description: "Returns all active products with id, name, price, and stock. Sorted by name.",
},
{
Method: "POST",
Path: "/api/orders",
Title: "Create order",
Description: "Creates a new order. Body: {productId string, quantity int, userId string}. Returns: {orderId, status, total}.",
},
{
Method: "GET",
Path: "/api/users/:id",
Title: "Get user by ID",
Description: "Returns user profile: name, email, role, plan, createdAt. Requires admin JWT.",
},
})
if err != nil {
log.Printf("IndexRoutes: %v", err) // non-fatal — continue
}IndexRoutes calls CreateKnowledge once per route. It stops and returns the first error — partial indexing is normal if one route fails. Check and log the error but do not treat it as fatal, since the bot still works with the articles that were created.
Knowledge base
Full CRUD for your organization's knowledge base. Write operations (create/update/delete) use the API key — no JWT needed. Read operations (list/get) require a JWT obtained via Login().
TestKey() must be called before any Knowledge operation (it caches the OrganizationID), or pass the org ID explicitly with WithOrgID(id).
List all articles
// Requires JWT (call Login first)
items, err := client.GetKnowledge(ctx)
if err != nil {
log.Fatal(err)
}
for _, item := range items {
fmt.Printf("%s: %s
", item.ID, item.Title)
}Create an article
created, err := client.CreateKnowledge(ctx, fluxchat.KnowledgeItem{
Title: "Opening hours",
Content: "We are open Mon–Fri 9am–6pm, Sat 10am–4pm.",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(created.ID)Update an article
updated, err := client.UpdateKnowledge(ctx, "article-id", fluxchat.KnowledgeItem{
Content: "Updated content — now open until 7pm on Fridays.",
})
if err != nil {
log.Fatal(err)
}Get a single article
// Requires JWT (call Login first)
item, err := client.GetKnowledgeItem(ctx, "article-id")
if err != nil {
log.Fatal(err)
}
fmt.Println(item.Title, item.Content)Delete an article
err := client.DeleteKnowledge(ctx, "article-id")
if err != nil {
log.Fatal(err)
}Error handling
The SDK returns three typed errors. Use errors.As() to inspect them — never string-match on err.Error().
import "errors"
_, err := client.Ask(ctx, "Hello")
if err != nil {
var apiErr *fluxchat.APIError
var netErr *fluxchat.NetworkError
var cfgErr *fluxchat.ConfigError
switch {
case errors.As(err, &apiErr):
// HTTP 4xx / 5xx from the FluxChat API
fmt.Printf("API error %d: %s
", apiErr.StatusCode, apiErr.Message)
case errors.As(err, &netErr):
// Connection refused, DNS failure, timeout, etc.
fmt.Printf("network error: %v
", netErr.Cause)
case errors.As(err, &cfgErr):
// Empty API key, missing orgID, etc.
fmt.Printf("config error: %s
", cfgErr.Message)
}
}| Type | Fields | When returned |
|---|---|---|
| *APIError | StatusCode int, Message string | HTTP 4xx/5xx from the API |
| *NetworkError | Cause error (unwrappable) | Connection refused, timeout, DNS failure |
| *ConfigError | Message string | Empty API key, missing orgID |
Options reference
Client options (passed to NewClient)
| Option | Default | Description |
|---|---|---|
| WithBaseURL(url string) | dev-api.fluxchat-corp.com/api/v2 | Override the API base URL |
| WithHTTPClient(hc *http.Client) | 30s timeout client | Inject a custom HTTP client (testing, proxies) |
| WithJWT(token string) | "" | Pre-load a JWT instead of calling Login() |
| WithOrgID(id string) | "" | Pre-set orgID instead of calling TestKey() |
Ask options (passed to Ask)
| Option | Description |
|---|---|
| WithConversationID(id string) | Continue an existing conversation thread |
| WithSessionID(id string) | Tie multiple stateless calls to one session (analytics / context) |
| WithContext(ctx string) | Free-text context injected with highest priority (user info, page data, etc.) |
Full example — Go HTTP server
A production-ready Go server with IndexRoutes at startup and the full Knowledge Base CRUD API. The bot learns your API surface immediately at startup.
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"strings"
fluxchat "github.com/benflux-company/fluxchat-sdk/sdk/go"
)
var (
fc *fluxchat.Client
ctx = context.Background()
)
func main() {
var err error
fc, err = fluxchat.NewClient(os.Getenv("FLUXCHAT_API_KEY"),
fluxchat.WithBaseURL("https://dev-api.fluxchat-corp.com/api/v2"),
)
if err != nil {
log.Fatalf("init: %v", err)
}
fc.TestKey(ctx) // caches orgID
fc.Login(ctx, os.Getenv("ADMIN_EMAIL"), os.Getenv("ADMIN_PASS")) // JWT for KB read
// Register API surface in KB at startup
fc.IndexRoutes(ctx, []fluxchat.RouteInfo{
{Method: "GET", Path: "/api/products", Title: "List products", Description: "Returns all active products with price and stock."},
{Method: "POST", Path: "/api/orders", Title: "Create order", Description: "Body: {productId, quantity, userId}. Returns: {orderId, status, total}."},
{Method: "DELETE", Path: "/api/orders/:id", Title: "Cancel order", Description: "Cancels an order by ID. Requires admin."},
})
mux := http.NewServeMux()
mux.HandleFunc("/api/chat", handleChat) // POST
mux.HandleFunc("/api/capture", handleCapture) // POST
mux.HandleFunc("/api/knowledge", handleKnowledge) // GET / POST
mux.HandleFunc("/api/knowledge/", handleKBItem) // DELETE /{id}
mux.Handle("/", http.FileServer(http.Dir("./static")))
log.Fatal(http.ListenAndServe(":8080", mux))
}
// POST /api/chat { message, conversationId? }
func handleChat(w http.ResponseWriter, r *http.Request) {
var body struct {
Message string `json:"message"`
ConversationID string `json:"conversationId"`
}
json.NewDecoder(r.Body).Decode(&body)
opts := []fluxchat.AskOption{}
if body.ConversationID != "" {
opts = append(opts, fluxchat.WithConversationID(body.ConversationID))
}
resp, err := fc.Ask(r.Context(), body.Message, opts...)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
json.NewEncoder(w).Encode(map[string]any{
"reply": resp.Reply,
"conversationId": resp.ConversationID,
})
}
// POST /api/capture { url, title, content }
func handleCapture(w http.ResponseWriter, r *http.Request) {
var body struct{ URL, Title, Content string }
json.NewDecoder(r.Body).Decode(&body)
if err := fc.CapturePage(r.Context(), body.URL, body.Title, body.Content); err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
}
// GET /api/knowledge → list all
// POST /api/knowledge → create { title, content }
func handleKnowledge(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
items, err := fc.GetKnowledge(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
json.NewEncoder(w).Encode(items)
case http.MethodPost:
var item fluxchat.KnowledgeItem
json.NewDecoder(r.Body).Decode(&item)
created, err := fc.CreateKnowledge(r.Context(), item)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(created)
}
}
// DELETE /api/knowledge/{id}
func handleKBItem(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/api/knowledge/")
if err := fc.DeleteKnowledge(r.Context(), id); err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
}Types
// ── Client ───────────────────────────────────────────────────
type Client struct { /* opaque */ }
func NewClient(apiKey string, opts ...Option) (*Client, error)
// ── Ask ──────────────────────────────────────────────────────
type AskResponse struct {
Reply string `json:"reply"`
ConversationID string `json:"conversationId"`
}
// ── TestKey ──────────────────────────────────────────────────
type KeyInfo struct {
OrganizationID string `json:"organizationId"`
Scopes []string `json:"scopes"`
}
// ── Login ────────────────────────────────────────────────────
type LoginResponse struct {
AccessToken string `json:"accessToken"`
RefreshToken string `json:"refreshToken"`
ExpiresIn string `json:"expiresIn"`
}
// ── Knowledge ────────────────────────────────────────────────
type KnowledgeItem struct {
ID string `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Content string `json:"content,omitempty"`
Category string `json:"category,omitempty"`
Keywords []string `json:"keywords,omitempty"`
IsActive *bool `json:"isActive,omitempty"`
CreatedAt string `json:"createdAt,omitempty"`
}
// ── IndexRoutes ──────────────────────────────────────────────
type RouteInfo struct {
Method string // HTTP method (GET, POST, ...)
Path string // URL path pattern (e.g. "/api/products")
Title string // Human-readable name (optional — defaults to "METHOD /path")
Description string // What this endpoint does, its parameters, example responses
}
func (c *Client) IndexRoutes(ctx context.Context, routes []RouteInfo) error
// ── Errors ───────────────────────────────────────────────────
type APIError struct {
StatusCode int
Message string
}
type NetworkError struct {
Cause error // unwrappable via errors.Unwrap
}
type ConfigError struct {
Message string
}