Skip to main content

Go SDK

Go Reference License: MIT The official Go SDK for interacting with Plugged.in’s Library API. Features context-aware operations, comprehensive error handling, and full type safety.

Installation

go get github.com/veriteknik/pluggedinkit-go

Requirements

  • Go 1.18 or higher
  • No external dependencies beyond the standard library

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    pluggedin "github.com/veriteknik/pluggedinkit-go"
)

func main() {
    // Initialize client
    client := pluggedin.NewClient("your-api-key")
    // For local development:
    // client := pluggedin.NewClientWithOptions("your-api-key", "http://localhost:12005", nil)

    ctx := context.Background()

    // List documents
    docs, err := client.Documents.List(ctx, &pluggedin.DocumentFilters{
        Source: pluggedin.SourceAll,
        Limit:  10,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Found %d documents\n", docs.Total)

    // Search documents
    results, err := client.Documents.Search(ctx, "machine learning", nil, 10, 0)
    if err != nil {
        log.Fatal(err)
    }
    for _, result := range results.Results {
        fmt.Printf("%s - Relevance: %.2f\n", result.Title, result.RelevanceScore)
    }

    // Query RAG
    answer, err := client.RAG.AskQuestion(ctx, "What are the main features?")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(answer)
}

Authentication

Get your API key from your Plugged.in Profile:
import (
    "os"
    pluggedin "github.com/veriteknik/pluggedinkit-go"
)

// Using environment variables (recommended)
client := pluggedin.NewClient(os.Getenv("PLUGGEDIN_API_KEY"))

// With custom configuration
import (
    "net/http"
    "time"
)

httpClient := &http.Client{
    Timeout: 120 * time.Second,
}

client := pluggedin.NewClientWithOptions(
    os.Getenv("PLUGGEDIN_API_KEY"),
    os.Getenv("PLUGGEDIN_BASE_URL"),
    httpClient,
)

// Update API key at runtime
client.SetAPIKey("new-api-key")

// Change base URL
client.SetBaseURL("http://localhost:12005")

Core Features

Document Management

List Documents

filters := &pluggedin.DocumentFilters{
    Source:        pluggedin.SourceAIGenerated,
    Tags:          []string{"report", "analysis"},
    Category:      pluggedin.CategoryDocumentation,
    Sort:          pluggedin.SortDateDesc,
    Limit:         20,
    Offset:        0,
    ModelProvider: "anthropic",
}

// Time filters
from := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
to := time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC)
filters.DateFrom = &from
filters.DateTo = &to

response, err := client.Documents.List(ctx, filters)
if err != nil {
    log.Fatal(err)
}

for _, doc := range response.Documents {
    fmt.Printf("%s (%d bytes)\n", doc.Title, doc.FileSize)
    fmt.Printf("  Created: %s\n", doc.CreatedAt)
    fmt.Printf("  Tags: %v\n", doc.Tags)
}

Get Document

// Get document metadata only
doc, err := client.Documents.Get(ctx, "document-id", false, false)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Title: %s\n", doc.Title)
fmt.Printf("Version: %d\n", doc.Version)

// Get document with content and version history
docWithContent, err := client.Documents.Get(ctx, "document-id", true, true)
if err != nil {
    log.Fatal(err)
}
fmt.Println(docWithContent.Content)

// Check last access time
if docWithContent.LastAccessAt != nil {
    fmt.Printf("Last accessed: %s\n", *docWithContent.LastAccessAt)
}

Search Documents

filters := map[string]interface{}{
    "modelProvider": "anthropic",
    "dateFrom":      "2024-01-01T00:00:00Z",
    "tags":          "finance,q4",
}

results, err := client.Documents.Search(ctx, "quarterly report", filters, 10, 0)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Found %d results for query: %s\n", results.Total, results.Query)

for _, result := range results.Results {
    fmt.Printf("\n%s\n", result.Title)
    fmt.Printf("  Document ID: %s\n", result.DocumentID)
    fmt.Printf("  Relevance: %.2f\n", result.RelevanceScore)
    fmt.Printf("  Snippet: %s\n", result.Snippet)
    fmt.Printf("  Tags: %v\n", result.Tags)
}

Create AI-Generated Document

request := &pluggedin.CreateDocumentRequest{
    Title:   "API Integration Guide",
    Content: "# API Integration Guide\n\n## Introduction\n\n...",
    Metadata: map[string]interface{}{
        "format":   "md",
        "category": "documentation",
        "tags":     []string{"api", "guide"},
        "model": map[string]string{
            "name":     "claude-3-opus",
            "provider": "anthropic",
            "version":  "20240229",
        },
        "prompt":     "Create an API integration guide",
        "visibility": "workspace",
    },
}

doc, err := client.Documents.Create(ctx, request)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Created document: %s\n", doc.ID)
fmt.Printf("Version: %d\n", doc.Version)

Update Document

request := &pluggedin.UpdateDocumentRequest{
    Operation: pluggedin.OpAppend,
    Content:   "\n\n## New Section\n\nAdditional content here.",
    Metadata: map[string]interface{}{
        "changeSummary": "Added implementation details section",
        "model": map[string]string{
            "name":     "gpt-4",
            "provider": "openai",
            "version":  "0613",
        },
    },
}

response, err := client.Documents.Update(ctx, "document-id", request)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Updated to version %d\n", response.Version)
fmt.Printf("Updated at: %s\n", response.UpdatedAt)

Delete Document

err := client.Documents.Delete(ctx, "document-id")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Document deleted successfully")

RAG Operations

Simple Query

// Basic question answering
answer, err := client.RAG.AskQuestion(ctx, "What are the deployment procedures?")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Answer:", answer)

// Check availability
status, err := client.RAG.CheckAvailability(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("RAG Available: %v\n", status["available"])

// Get storage statistics
stats, err := client.RAG.GetStorageStats(ctx, "user-id-from-dashboard")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Documents: %d\n", stats.DocumentsCount)
fmt.Printf("Estimated storage: %.2f MB\n", stats.EstimatedStorageMB)

Query with Sources

result, err := client.RAG.QueryWithSources(
    ctx,
    "Explain the authentication flow",
    "",
)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Answer: %s\n", result.Answer)
fmt.Println("\nSources:")
for index, source := range result.Sources {
    var documentID string
    if index < len(result.DocumentIDs) {
        documentID = result.DocumentIDs[index]
    }
    fmt.Printf("- %s (%s)\n", source, documentID)
}

Find Relevant Documents

documents, err := client.RAG.FindRelevantDocuments(
    ctx,
    "user authentication",
    "",
    5, // Top 5 documents
)
if err != nil {
    log.Fatal(err)
}

for _, doc := range documents {
    fmt.Printf("- %s (%s)\n", doc.Source, doc.DocumentID)
}

File Uploads

Important: The Go SDK no longer exposes binary upload helpers. All previous upload functions now return descriptive errors so legacy code fails fast. Continue using DocumentsService.Create for AI-generated content and the Plugged.in web interface (or forthcoming ingestion tools) for binary files.

Error Handling

The SDK provides typed error structs for comprehensive error handling:
doc, err := client.Documents.Get(ctx, "invalid-id", false, false)
if err != nil {
    switch e := err.(type) {
    case *pluggedin.AuthenticationError:
        fmt.Println("Invalid API key - please check your credentials")
        // Refresh API key logic

    case *pluggedin.RateLimitError:
        fmt.Printf("Rate limited. Retry after %d seconds\n", e.RetryAfter)
        // Implement retry with backoff
        time.Sleep(time.Duration(e.RetryAfter) * time.Second)

    case *pluggedin.NotFoundError:
        fmt.Printf("Document not found: %s\n", e.ID)
        // Handle missing document

    case *pluggedin.ValidationError:
        fmt.Printf("Validation error on field '%s': %s\n", e.Field, e.Message)
        // Fix validation issues

    case *pluggedin.APIError:
        fmt.Printf("API error (%d): %s\n", e.StatusCode, e.Message)
        if e.Details != nil {
            fmt.Printf("Details: %v\n", e.Details)
        }

    default:
        fmt.Printf("Unexpected error: %v\n", err)
    }
}

PAP Agents

The SDK provides full support for managing PAP (Plugged.in Agent Protocol) agents:

Creating Agents

result, err := client.Agents.Create(ctx, pluggedinkit.CreateAgentRequest{
    Name:        "my-agent",
    Description: stringPtr("My autonomous PAP agent"),
    Resources: &pluggedinkit.ResourceRequirements{
        CPURequest:    stringPtr("100m"),
        MemoryRequest: stringPtr("256Mi"),
        CPULimit:      stringPtr("1000m"),
        MemoryLimit:   stringPtr("1Gi"),
    },
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Agent created: %s\n", result.Agent.UUID)
fmt.Printf("DNS: %s\n", result.Agent.DNSName) // my-agent.is.plugged.in

func stringPtr(s string) *string { return &s }

Listing and Managing Agents

// List all agents
agents, err := client.Agents.List(ctx)

// Get agent details
details, err := client.Agents.Get(ctx, agentID)
fmt.Println(details.RecentHeartbeats)
fmt.Println(details.RecentMetrics)
fmt.Println(details.LifecycleEvents)

// Export agent data
exportData, err := client.Agents.Export(ctx, agentID, true, 1000)

// Delete agent
deleteResp, err := client.Agents.Delete(ctx, agentID)

Heartbeats and Metrics

CRITICAL: Heartbeats contain ONLY liveness data. Resource telemetry goes in metrics (separate channel). This separation is PAP’s zombie prevention superpower.
// Send heartbeat (liveness only)
_, err = client.Agents.Heartbeat(ctx, agentID, "IDLE", 330.0)

// Send metrics (resource telemetry)
customMetrics := map[string]interface{}{
    "queue_depth": 3,
    "cache_hit_rate": 0.85,
}
_, err = client.Agents.Metrics(ctx, agentID, 12.5, 128, 45, customMetrics)

Concurrent Agent Operations

func monitorAgents(client *pluggedinkit.Client, agentIDs []string) {
    ctx := context.Background()

    var wg sync.WaitGroup

    for _, agentID := range agentIDs {
        wg.Add(1)
        go func(id string) {
            defer wg.Done()

            // Send heartbeat and metrics concurrently
            errChan := make(chan error, 2)

            go func() {
                _, err := client.Agents.Heartbeat(ctx, id, "IDLE", 330.0)
                errChan <- err
            }()

            go func() {
                _, err := client.Agents.Metrics(ctx, id, 10.0, 100, 20, nil)
                errChan <- err
            }()

            // Wait for both to complete
            for i := 0; i < 2; i++ {
                if err := <-errChan; err != nil {
                    log.Printf("Error for agent %s: %v", id, err)
                }
            }
        }(agentID)
    }

    wg.Wait()
}

Agent States

Agents follow the PAP lifecycle state machine:
NEW → PROVISIONED → ACTIVE ↔ DRAINING → TERMINATED
                       ↓ (error)
                     KILLED
// Poll until agent is active
for {
    details, err := client.Agents.Get(ctx, agentID)
    if err != nil {
        return err
    }

    if details.Agent.State == "ACTIVE" {
        fmt.Println("Agent is ready!")
        break
    }

    time.Sleep(5 * time.Second)
}
See PAP Agents Documentation for complete details on the Plugged.in Agent Protocol.

Context and Cancellation

All SDK methods accept a context for cancellation and timeout control:
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

docs, err := client.Documents.List(ctx, nil)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        fmt.Println("Request timed out")
    }
}

// With cancellation
ctx, cancel := context.WithCancel(context.Background())

// Cancel from another goroutine
go func() {
    time.Sleep(5 * time.Second)
    cancel()
}()

// This will be cancelled after 5 seconds
answer, err := client.RAG.AskQuestion(ctx, "Long running query")
if err != nil {
    if errors.Is(err, context.Canceled) {
        fmt.Println("Request was cancelled")
    }
}

Concurrent Operations

Leverage Go’s concurrency for parallel operations:
func fetchDocumentsConcurrently(client *pluggedin.Client) {
    ctx := context.Background()
    documentIDs := []string{"doc1", "doc2", "doc3", "doc4", "doc5"}

    type result struct {
        doc *pluggedin.Document
        err error
    }

    results := make(chan result, len(documentIDs))

    // Fetch documents concurrently
    for _, id := range documentIDs {
        go func(docID string) {
            doc, err := client.Documents.Get(ctx, docID, true, false)
            results <- result{doc, err}
        }(id)
    }

    // Collect results
    for i := 0; i < len(documentIDs); i++ {
        r := <-results
        if r.err != nil {
            fmt.Printf("Error fetching document: %v\n", r.err)
        } else {
            fmt.Printf("Fetched: %s\n", r.doc.Title)
        }
    }
}

Testing

Unit Testing with Interfaces

// Define interface for testing
type DocumentService interface {
    List(ctx context.Context, filters *DocumentFilters) (*ListDocumentsResponse, error)
    Get(ctx context.Context, id string, includeContent, includeVersions bool) (*Document, error)
}

// Test function
func TestDocumentList(t *testing.T) {
    // Mock implementation
    mockService := &MockDocumentService{
        ListFunc: func(ctx context.Context, filters *DocumentFilters) (*ListDocumentsResponse, error) {
            return &ListDocumentsResponse{
                Documents: []Document{{Title: "Test Doc"}},
                Total:     1,
            }, nil
        },
    }

    // Test
    response, err := mockService.List(context.Background(), nil)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }

    if response.Total != 1 {
        t.Errorf("expected 1 document, got %d", response.Total)
    }
}

Integration Testing

func TestDocumentLifecycle(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }

    client := pluggedin.NewClient(os.Getenv("PLUGGEDIN_API_KEY"))
    ctx := context.Background()

    // Create document
    doc, err := client.Documents.Create(ctx, &CreateDocumentRequest{
        Title:   "Test Document",
        Content: "Test content",
        Metadata: map[string]interface{}{
            "tags": []string{"test"},
        },
    })
    if err != nil {
        t.Fatalf("failed to create document: %v", err)
    }

    // Cleanup
    defer func() {
        _ = client.Documents.Delete(ctx, doc.ID)
    }()

    // Update document
    updateResp, err := client.Documents.Update(ctx, doc.ID, &UpdateDocumentRequest{
        Operation: OpAppend,
        Content:   "\nUpdated content",
    })
    if err != nil {
        t.Fatalf("failed to update document: %v", err)
    }

    if updateResp.Version <= doc.Version {
        t.Errorf("expected version to increase, got %d", updateResp.Version)
    }
}

Benchmarking

func BenchmarkDocumentSearch(b *testing.B) {
    client := pluggedin.NewClient(os.Getenv("PLUGGEDIN_API_KEY"))
    ctx := context.Background()

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _, err := client.Documents.Search(ctx, "test query", nil, 10, 0)
        if err != nil {
            b.Fatalf("search failed: %v", err)
        }
    }
}

Advanced Configuration

Custom HTTP Transport

import (
    "net/http"
    "net/url"
    "time"
)

// Custom transport with proxy
proxyURL, _ := url.Parse("http://proxy.example.com:8080")
transport := &http.Transport{
    Proxy:           http.ProxyURL(proxyURL),
    MaxIdleConns:    100,
    IdleConnTimeout: 90 * time.Second,
}

httpClient := &http.Client{
    Transport: transport,
    Timeout:   2 * time.Minute,
}

client := pluggedin.NewClientWithOptions(
    "your-api-key",
    "https://plugged.in",
    httpClient,
)

Environment Variables

// Load from environment
type Config struct {
    APIKey  string
    BaseURL string
    Timeout time.Duration
}

func LoadConfig() *Config {
    timeout, _ := time.ParseDuration(os.Getenv("PLUGGEDIN_TIMEOUT"))
    if timeout == 0 {
        timeout = 60 * time.Second
    }

    return &Config{
        APIKey:  os.Getenv("PLUGGEDIN_API_KEY"),
        BaseURL: os.Getenv("PLUGGEDIN_BASE_URL"),
        Timeout: timeout,
    }
}

// Use configuration
config := LoadConfig()
httpClient := &http.Client{Timeout: config.Timeout}
client := pluggedin.NewClientWithOptions(config.APIKey, config.BaseURL, httpClient)

Rate Limiting

The SDK handles rate limiting automatically:
  • API Endpoints: 60 requests per minute
  • Document Search: 10 requests per hour for AI documents
  • RAG Queries: Subject to plan limits
// Implement retry with exponential backoff
func retryWithBackoff(ctx context.Context, fn func() error) error {
    maxRetries := 5
    baseDelay := 1 * time.Second

    for i := 0; i < maxRetries; i++ {
        err := fn()
        if err == nil {
            return nil
        }

        // Check if it's a rate limit error
        if rateLimitErr, ok := err.(*pluggedin.RateLimitError); ok {
            delay := time.Duration(rateLimitErr.RetryAfter) * time.Second
            fmt.Printf("Rate limited, waiting %v\n", delay)
            time.Sleep(delay)
            continue
        }

        // Exponential backoff for other errors
        delay := baseDelay * time.Duration(1<<i)
        fmt.Printf("Error: %v, retrying in %v\n", err, delay)
        time.Sleep(delay)
    }

    return fmt.Errorf("max retries exceeded")
}

Examples

Complete working examples are available in the GitHub repository:

API Reference

Client Functions

FunctionDescription
NewClient(apiKey)Create a new client with default settings
NewClientWithOptions(apiKey, baseURL, httpClient)Create a client with custom options

Client Methods

MethodDescription
SetAPIKey(apiKey)Update the API key
SetBaseURL(baseURL)Update the base URL

Document Service Methods

MethodDescription
List(ctx, filters)List documents with optional filters
Get(ctx, id, includeContent, includeVersions)Get a specific document
Search(ctx, query, filters, limit, offset)Search documents
Create(ctx, request)Create a new document
Update(ctx, id, request)Update an existing document
Delete(ctx, id)Delete a document

RAG Service Methods

MethodDescription
Query(ctx, query)Returns RAGResponse with answer and metadata
AskQuestion(ctx, query)Simple RAG query (answer text only)
QueryWithSources(ctx, query, projectUUID)Query with source metadata (project argument optional)
FindRelevantDocuments(ctx, query, projectUUID, limit)Find relevant documents
CheckAvailability(ctx)Check RAG service availability
GetStorageStats(ctx, userID)Get storage statistics

Upload Service Methods

Upload helpers now emit descriptive errors because binary ingestion is no longer exposed via the public API.

Clipboard Service Methods

MethodDescription
List(ctx)List all clipboard entries
Get(ctx, filters)Get entry by name or index
GetByName(ctx, name)Get entry by name
GetByIndex(ctx, idx)Get entry by stack index
Set(ctx, request)Create or update named entry
Push(ctx, request)Push value to indexed stack
Pop(ctx)Pop and remove most recent entry
Delete(ctx, request)Delete specific entry
DeleteByName(ctx, name)Delete entry by name
DeleteByIndex(ctx, idx)Delete entry by index
ClearAll(ctx)Delete all entries

Clipboard (Memory) Service

The Clipboard service provides persistent key-value storage for MCP tools and AI agents.

Named Access

ctx := context.Background()

// Set a named entry
entry, err := client.Clipboard.Set(ctx, &pluggedin.ClipboardSetRequest{
    Name:        "user_preferences",
    Value:       `{"theme": "dark", "lang": "en"}`,
    ContentType: "application/json",
    Encoding:    pluggedin.EncodingUTF8,
    Visibility:  pluggedin.ClipboardVisibilityPrivate,
    TTLSeconds:  86400, // 24 hours
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Created entry: %s\n", entry.UUID)

// Get by name
entry, err = client.Clipboard.GetByName(ctx, "user_preferences")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Value: %s\n", entry.Value)

// Delete by name
deleted, err := client.Clipboard.DeleteByName(ctx, "user_preferences")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Deleted: %v\n", deleted)

Indexed (Stack) Access

ctx := context.Background()

// Push to stack
_, err := client.Clipboard.Push(ctx, &pluggedin.ClipboardPushRequest{
    Value:       "Step 1 result",
    ContentType: "text/plain",
})

_, err = client.Clipboard.Push(ctx, &pluggedin.ClipboardPushRequest{
    Value:       "Step 2 result",
    ContentType: "text/plain",
})

// Get by index (0 = most recent)
latest, err := client.Clipboard.GetByIndex(ctx, 0)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Latest: %s\n", latest.Value) // "Step 2 result"

// Pop removes and returns the most recent
popped, err := client.Clipboard.Pop(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Popped: %s\n", popped.Value) // "Step 2 result"

List All Entries

ctx := context.Background()

entries, err := client.Clipboard.List(ctx)
if err != nil {
    log.Fatal(err)
}

for _, entry := range entries {
    var nameOrIdx string
    if entry.Name != nil {
        nameOrIdx = *entry.Name
    } else if entry.Idx != nil {
        nameOrIdx = fmt.Sprintf("idx:%d", *entry.Idx)
    }
    fmt.Printf("%s: %s\n", nameOrIdx, entry.Value)
    if entry.ExpiresAt != nil {
        fmt.Printf("  Expires: %s\n", entry.ExpiresAt.Format(time.RFC3339))
    }
}

Type Definitions

// ClipboardEntry represents a clipboard entry
type ClipboardEntry struct {
    UUID           string              `json:"uuid"`
    Name           *string             `json:"name,omitempty"`
    Idx            *int                `json:"idx,omitempty"`
    Value          string              `json:"value"`
    ContentType    string              `json:"contentType"`
    Encoding       ClipboardEncoding   `json:"encoding"`
    SizeBytes      int                 `json:"sizeBytes"`
    Visibility     ClipboardVisibility `json:"visibility"`
    Source         ClipboardSource     `json:"source"`
    CreatedByTool  *string             `json:"createdByTool,omitempty"`
    CreatedByModel *string             `json:"createdByModel,omitempty"`
    CreatedAt      time.Time           `json:"createdAt"`
    UpdatedAt      time.Time           `json:"updatedAt"`
    ExpiresAt      *time.Time          `json:"expiresAt,omitempty"`
}

// Source options - tracks where entries originate
const (
    ClipboardSourceUI  ClipboardSource = "ui"   // Created via web interface
    ClipboardSourceSDK ClipboardSource = "sdk"  // Created via SDK (automatically set)
    ClipboardSourceMCP ClipboardSource = "mcp"  // Created via MCP tools
)

// The SDK automatically sets Source to ClipboardSourceSDK for all entries.
// DefaultClipboardSource is ClipboardSourceUI for backward compatibility.

// Encoding options
const (
    EncodingUTF8   ClipboardEncoding = "utf-8"
    EncodingBase64 ClipboardEncoding = "base64"
    EncodingHex    ClipboardEncoding = "hex"
)

// Visibility options
const (
    ClipboardVisibilityPrivate   ClipboardVisibility = "private"
    ClipboardVisibilityWorkspace ClipboardVisibility = "workspace"
    ClipboardVisibilityPublic    ClipboardVisibility = "public"
)

Support