Skip to main content

Go API Client

Glean's Go API client provides idiomatic Go interfaces for integrating enterprise search and AI capabilities into your Go applications.

api-client-go

Official Go client for Glean's Client API

Installation

go get github.com/gleanwork/api-client-go

Quick Start

package main

import (
"context"
"fmt"
"os"
glean "github.com/gleanwork/api-client-go"
)

func main() {
ctx := context.Background()

client := glean.New(
glean.WithAPIToken(os.Getenv("GLEAN_API_TOKEN")),
glean.WithServerURL(os.Getenv("GLEAN_SERVER_URL")),
)

message := glean.ChatMessageFragment{
Text: "What are our company values?",
}

res, err := client.Client.Chat.Create(ctx, &glean.ChatRequest{
Messages: []glean.ChatMessage{{
Fragments: []glean.ChatMessageFragment{message},
}},
})

if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Println(res.Text)
}

Core Features

Chat API

func chatExample(client *glean.Client) error {
ctx := context.Background()

response, err := client.Client.Chat.Create(ctx, &glean.ChatRequest{
Messages: []glean.ChatMessage{{
Fragments: []glean.ChatMessageFragment{{
Text: "Explain our Q4 strategy",
}},
}},
})

if err != nil {
return err
}

fmt.Println(response.Text)
return nil
}

Search API

func searchExample(client *glean.Client) error {
ctx := context.Background()

results, err := client.Client.Search.Search(ctx, &glean.SearchRequest{
Query: "quarterly business review",
PageSize: glean.Int(10),
})

if err != nil {
return err
}

for _, result := range results.Results {
fmt.Printf("Title: %s\n", result.Title)
fmt.Printf("URL: %s\n", result.URL)
}

return nil
}

Framework Integrations

Gin Web Framework

package main

import (
"net/http"
"github.com/gin-gonic/gin"
glean "github.com/gleanwork/api-client-go"
)

type ChatRequest struct {
Message string `json:"message"`
}

func setupRoutes(client *glean.Client) *gin.Engine {
r := gin.Default()

r.POST("/chat", func(c *gin.Context) {
var req ChatRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

response, err := client.Client.Chat.Create(c.Request.Context(), &glean.ChatRequest{
Messages: []glean.ChatMessage{{
Fragments: []glean.ChatMessageFragment{{
Text: req.Message,
}},
}},
})

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"response": response.Text})
})

return r
}

Echo Framework

import (
"net/http"
"github.com/labstack/echo/v4"
glean "github.com/gleanwork/api-client-go"
)

func chatHandler(client *glean.Client) echo.HandlerFunc {
return func(c echo.Context) error {
var req ChatRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}

response, err := client.Client.Chat.Create(c.Request().Context(), &glean.ChatRequest{
Messages: []glean.ChatMessage{{
Fragments: []glean.ChatMessageFragment{{
Text: req.Message,
}},
}},
})

if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}

return c.JSON(http.StatusOK, map[string]string{
"response": response.Text,
})
}
}

Concurrency Patterns

Batch Processing with Goroutines

func batchSearch(client *glean.Client, queries []string) ([]glean.SearchResponse, error) {
ctx := context.Background()
results := make([]glean.SearchResponse, len(queries))
errors := make([]error, len(queries))

var wg sync.WaitGroup

for i, query := range queries {
wg.Add(1)
go func(index int, q string) {
defer wg.Done()

result, err := client.Client.Search.Search(ctx, &glean.SearchRequest{
Query: q,
})

if err != nil {
errors[index] = err
return
}

results[index] = *result
}(i, query)
}

wg.Wait()

// Check for errors
for _, err := range errors {
if err != nil {
return nil, err
}
}

return results, nil
}

Authentication

User-Scoped Tokens

client := glean.New(
glean.WithAPIToken("your-user-token"),
glean.WithServerURL("https://your-server-id-be.glean.com"),
)

Global Tokens with ActAs

Per-request headers are passed with operations.WithSetHeaders:

import (
glean "github.com/gleanwork/api-client-go"
"github.com/gleanwork/api-client-go/models/components"
"github.com/gleanwork/api-client-go/models/operations"
)

response, err := client.Client.Chat.Create(ctx, components.ChatRequest{
Messages: []components.ChatMessage{{
Fragments: []components.ChatMessageFragment{{
Text: glean.String("Hello"),
}},
}},
}, nil, operations.WithSetHeaders(map[string]string{
"X-Glean-ActAs": "user@company.com",
}))

OAuth Access Tokens

An OAuth access token is a bearer credential, so it goes in the same WithSecurity option:

client := glean.New(
glean.WithSecurity(oauthAccessToken),
glean.WithServerURL("https://your-server-id-be.glean.com"),
)

Tokens issued by the Glean OAuth Authorization Server (including tokens obtained via Dynamic Client Registration) are detected automatically. Tokens issued by an external identity provider (Google, Okta, Azure, etc.) additionally require the X-Glean-Auth-Type: OAUTH header on each request:

import (
"github.com/gleanwork/api-client-go/models/components"
"github.com/gleanwork/api-client-go/models/operations"
)

results, err := client.Client.Search.Query(ctx, &components.SearchRequest{
Query: "quarterly reports",
}, operations.WithSetHeaders(map[string]string{
"X-Glean-Auth-Type": "OAUTH",
}))

See the OAuth authentication guide for identity-provider setup.

Complete Example: Authorization Code with PKCE

This example uses golang.org/x/oauth2. It runs the Authorization Code flow with PKCE (S256ChallengeOption / VerifierOption) and AccessTypeOffline for a refresh token, then passes the access token to the Glean client. Read AuthURL/TokenURL from the issuer's metadata document.

package main

import (
"context"
"encoding/json"
"net/http"
"os"

"golang.org/x/oauth2"
glean "github.com/gleanwork/api-client-go"
"github.com/gleanwork/api-client-go/models/components"
"github.com/gleanwork/api-client-go/models/operations"
)

var (
conf = &oauth2.Config{
ClientID: os.Getenv("OAUTH_CLIENT_ID"),
ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"), // omit for a public client
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{"openid", "offline_access", "SEARCH"}, // SEARCH lets the token call /search; offline_access → refresh token
Endpoint: oauth2.Endpoint{
AuthURL: os.Getenv("OAUTH_AUTH_URL"),
TokenURL: os.Getenv("OAUTH_TOKEN_URL"),
},
}
// Demo only: generate and store the verifier per request (e.g. in session) in production.
verifier = oauth2.GenerateVerifier()
)

func main() {
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline,
oauth2.S256ChallengeOption(verifier))
http.Redirect(w, r, url, http.StatusFound)
})

http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tok, err := conf.Exchange(ctx, r.URL.Query().Get("code"), oauth2.VerifierOption(verifier))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

client := glean.New(
glean.WithSecurity(tok.AccessToken),
glean.WithServerURL(os.Getenv("GLEAN_SERVER_URL")),
)

results, err := client.Client.Search.Query(ctx, &components.SearchRequest{
Query: "quarterly reports",
}, // Omit this option when the token is from the Glean Authorization Server.
operations.WithSetHeaders(map[string]string{"X-Glean-Auth-Type": "OAUTH"}))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(results)
})

http.ListenAndServe(":8080", nil)
}
tip

Access tokens expire. Wrap the token in conf.TokenSource(ctx, tok) to refresh automatically when you requested offline_access.

Error Handling

func safeChat(client *glean.Client, message string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

response, err := client.Client.Chat.Create(ctx, &glean.ChatRequest{
Messages: []glean.ChatMessage{{
Fragments: []glean.ChatMessageFragment{{
Text: message,
}},
}},
})

if err != nil {
return "", fmt.Errorf("chat error: %w", err)
}

return response.Text, nil
}

Testing

import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestChatService(t *testing.T) {
// Mock implementation
mockClient := &MockGleanClient{}

expectedResponse := &glean.ChatResponse{
Text: "Test response",
}

mockClient.On("Create", mock.Anything, mock.Anything).Return(expectedResponse, nil)

// Test your service here
assert.NotNil(t, expectedResponse)
}

Additional Resources