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.WithInstance(os.Getenv("GLEAN_INSTANCE")),
)
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 (Recommended)
client := glean.New(
glean.WithAPIToken("your-user-token"),
glean.WithInstance("your-company"),
)
Global Tokens with ActAs
ctx := context.Background()
// Add ActAs header to context
ctx = context.WithValue(ctx, "X-Glean-ActAs", "user@company.com")
response, err := client.Client.Chat.Create(ctx, &glean.ChatRequest{
Messages: []glean.ChatMessage{{
Fragments: []glean.ChatMessageFragment{{
Text: "Hello",
}},
}},
})
OAuth Authentication
OAuth allows you to use access tokens from your identity provider (Google, Azure, Okta, etc.) instead of Glean-issued tokens.
Prerequisites
- OAuth enabled in Glean Admin > Third-Party OAuth
- Your OAuth Client ID registered with Glean
- See OAuth Setup Guide for admin configuration
OAuth requests require these headers:
| Header | Value |
|---|---|
Authorization | Bearer <oauth_access_token> |
X-Glean-Auth-Type | OAUTH |
Example: Authorization Code Flow
This example uses golang.org/x/oauth2:
package main
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"net/http"
"os"
"sync"
"golang.org/x/oauth2"
glean "github.com/gleanwork/api-client-go"
)
var oauthConfig = &oauth2.Config{
ClientID: os.Getenv("OAUTH_CLIENT_ID"),
ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"),
RedirectURL: "http://localhost:8080/callback",
Scopes: []string{"openid", "email"},
Endpoint: oauth2.Endpoint{
AuthURL: os.Getenv("OAUTH_AUTH_URL"),
TokenURL: os.Getenv("OAUTH_TOKEN_URL"),
},
}
// In-memory state store (use Redis/database in production)
var (
stateStore = make(map[string]bool)
stateMu sync.Mutex
)
func generateState() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// oauthTransport adds OAuth headers to all requests
type oauthTransport struct {
token string
transport http.RoundTripper
}
func (t *oauthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("Authorization", "Bearer "+t.token)
req.Header.Set("X-Glean-Auth-Type", "OAUTH")
return t.transport.RoundTrip(req)
}
func main() {
http.HandleFunc("/login", handleLogin)
http.HandleFunc("/callback", handleCallback)
http.ListenAndServe(":8080", nil)
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
state, err := generateState()
if err != nil {
http.Error(w, "Failed to generate state", http.StatusInternalServerError)
return
}
// Store state for CSRF validation
stateMu.Lock()
stateStore[state] = true
stateMu.Unlock()
url := oauthConfig.AuthCodeURL(state)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
// Validate state to prevent CSRF attacks
state := r.URL.Query().Get("state")
stateMu.Lock()
valid := stateStore[state]
delete(stateStore, state)
stateMu.Unlock()
if !valid {
http.Error(w, "Invalid state parameter", http.StatusBadRequest)
return
}
code := r.URL.Query().Get("code")
token, err := oauthConfig.Exchange(context.Background(), code)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Create HTTP client with OAuth headers
httpClient := &http.Client{
Transport: &oauthTransport{
token: token.AccessToken,
transport: http.DefaultTransport,
},
}
// Create Glean client with custom HTTP client
client := glean.New(
glean.WithInstance(os.Getenv("GLEAN_INSTANCE")),
glean.WithClient(httpClient),
)
results, err := client.Client.Search.Query(r.Context(), "quarterly reports", nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(results)
}
tip
Access tokens typically expire after ~1 hour. For production use, use oauth2.Config.TokenSource for automatic refresh.
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)
}