fluxchat/sdk

SDK · Stable

Go SDK

v1.0.4 · Stable

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.

terminal
go get github.com/benflux-company/fluxchat-sdk/sdk/go@v1.0.4
go.mod
module your-app

go 1.21

require github.com/benflux-company/fluxchat-sdk/sdk/go v1.0.4

Quickstart

main.go
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.

auth.go
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.

login.go
// 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.

ask.go
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 stateless

Continue a conversation

Pass the ConversationID from a previous reply to continue the same thread.

conversation.go
// 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

session.go
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.

capture.go
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.

indexroutes.go
// 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

kb-list.go
// 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

kb-create.go
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

kb-update.go
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

kb-get.go
// 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

kb-delete.go
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().

errors.go
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)
    }
}
TypeFieldsWhen returned
*APIErrorStatusCode int, Message stringHTTP 4xx/5xx from the API
*NetworkErrorCause error (unwrappable)Connection refused, timeout, DNS failure
*ConfigErrorMessage stringEmpty API key, missing orgID

Options reference

Client options (passed to NewClient)

OptionDefaultDescription
WithBaseURL(url string)dev-api.fluxchat-corp.com/api/v2Override the API base URL
WithHTTPClient(hc *http.Client)30s timeout clientInject 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)

OptionDescription
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.

main.go
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

types.go
// ── 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
}