Go SDK

The official Go SDK for ModelRelay. Provides idiomatic Go patterns with strong typing, context support, and streaming capabilities.

Installation

go get github.com/modelrelay/sdk-go

Quick Start

package main

import (
    "context"
    "fmt"
    "os"

    sdk "github.com/modelrelay/sdk-go"
)

func main() {
    client, err := sdk.NewClientFromAPIKey(os.Getenv("MODELRELAY_API_KEY"))
    if err != nil {
        panic(err)
    }

    answer, err := client.Responses.Text(
        context.Background(),
        sdk.NewModelID("claude-sonnet-4-5"),
        "You are a helpful assistant.",
        "What is the capital of France?",
    )
    if err != nil {
        panic(err)
    }

    fmt.Println(answer)
    // "The capital of France is Paris."
}

Convenience API

The simplest way to get started. Three methods cover the most common use cases:

Ask — Get a Quick Answer

client, _ := sdk.NewClientFromAPIKey(os.Getenv("MODELRELAY_API_KEY"))

answer, _ := client.Ask(ctx, "claude-sonnet-4-5", "What is 2 + 2?", nil)
fmt.Println(answer) // "4"

Chat — Full Response with Metadata

response, _ := client.Chat(ctx, "claude-sonnet-4-5", "Explain quantum computing", &sdk.ChatOptions{
    System: "You are a physics professor",
})

fmt.Println(response.AssistantText())
fmt.Println("Tokens:", response.Usage.TotalTokens)

Agent — Agentic Tool Loops

Run an agent that automatically executes tools until completion:

type ReadFileArgs struct {
    Path string `json:"path" description:"File path to read"`
}

tools := sdk.NewToolBuilder()
sdk.AddFunc(tools, "read_file", "Read a file", func(args ReadFileArgs) (any, error) {
    content, err := os.ReadFile(args.Path)
    return string(content), err
})

result, _ := client.Agent(ctx, "claude-sonnet-4-5", sdk.AgentOptions{
    Tools:  tools,
    Prompt: "Read config.json and summarize it",
    System: "You are a helpful file assistant",
})

fmt.Println(result.Output)
fmt.Println("Tool calls:", result.Usage.ToolCalls)

Configuration

From API Key

import sdk "github.com/modelrelay/sdk-go"

// From secret key (backend use)
client, err := sdk.NewClientFromSecretKey("mr_sk_...")

// Auto-detect key type
client, err := sdk.NewClientFromAPIKey(os.Getenv("MODELRELAY_API_KEY"))

// Parse and validate first
secret, err := sdk.ParseSecretKey("mr_sk_...")
client, err := sdk.NewClientWithKey(secret)

From Bearer Token

For customer-scoped access using a minted token:

client, err := sdk.NewClientWithToken("eyJ...")

Configuration Options

Use functional options to configure the client:

client, err := sdk.NewClientFromSecretKey("mr_sk_...",
    sdk.WithBaseURL("https://custom.api.com"),
    sdk.WithConnectTimeout(5*time.Second),
    sdk.WithRequestTimeout(30*time.Second),
    sdk.WithRetryConfig(sdk.RetryConfig{MaxAttempts: 3}),
)
Option Default Description
WithBaseURL(url) https://api.modelrelay.ai/api/v1 API base URL
WithConnectTimeout(d) 5s Connection timeout
WithRequestTimeout(d) 60s Request timeout
WithRetryConfig(cfg) No retry Retry/backoff policy
WithHTTPClient(c) Default Custom HTTP client
WithUserAgent(ua) SDK version Custom User-Agent

Making Requests

Simple Text Response

For the common “system prompt + user message → text” pattern:

ctx := context.Background()

answer, err := client.Responses.Text(
    ctx,
    sdk.NewModelID("claude-sonnet-4-5"),
    "You are a helpful assistant.",
    "What is 2 + 2?",
)
if err != nil {
    return err
}

fmt.Println(answer)

Request Builder

For more control over request parameters:

ctx := context.Background()

req, callOpts, err := client.Responses.New().
    Model(sdk.NewModelID("claude-sonnet-4-5")).
    System("You are a helpful assistant.").
    User("What is 2 + 2?").
    MaxOutputTokens(256).
    Temperature(0.7).
    Build()
if err != nil {
    return err
}

resp, err := client.Responses.Create(ctx, req, callOpts...)
if err != nil {
    return err
}

fmt.Println(resp.Text())  // Extract text from response
fmt.Println(resp.Usage)   // {InputTokens, OutputTokens, TotalTokens}

Customer-Attributed Requests

For metered billing, attribute requests to customers:

ctx := context.Background()

// Option 1: Use CustomerID in the builder (tier determines model)
req, callOpts, err := client.Responses.New().
    CustomerID("customer-123").
    System("You are a helpful assistant.").
    User("Hello!").
    Build()
if err != nil {
    return err
}

resp, err := client.Responses.Create(ctx, req, callOpts...)

// Option 2: Use the convenience method
answer, err := client.Responses.TextForCustomer(
    ctx,
    "customer-123",
    "You are a helpful assistant.",
    "Hello!",
)

Streaming

Stream Text Deltas

For real-time response streaming with just the text:

ctx := context.Background()

stream, err := client.Responses.StreamTextDeltas(
    ctx,
    sdk.NewModelID("claude-sonnet-4-5"),
    "You are a helpful assistant.",
    "Write a haiku about programming.",
)
if err != nil {
    return err
}
defer stream.Close()

for {
    delta, ok, err := stream.Next()
    if err != nil {
        return err
    }
    if !ok {
        break
    }
    fmt.Print(delta)
}

Full Event Stream

For access to all streaming events:

ctx := context.Background()

req, callOpts, err := client.Responses.New().
    Model(sdk.NewModelID("claude-sonnet-4-5")).
    System("You are helpful.").
    User("Hello!").
    Build()
if err != nil {
    return err
}

stream, err := client.Responses.Stream(ctx, req, callOpts...)
if err != nil {
    return err
}
defer stream.Close()

for {
    event, ok, err := stream.Next()
    if err != nil {
        return err
    }
    if !ok {
        break
    }
    fmt.Print(event.TextDelta)
}

Structured Output

Parse to Typed Struct

Use generics to parse responses into typed Go structs:

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

ctx := context.Background()

req, callOpts, err := client.Responses.New().
    Model(sdk.NewModelID("claude-sonnet-4-5")).
    User("Extract: John Doe is 30 years old").
    Build()
if err != nil {
    return err
}

result, err := sdk.Structured[Person](
    ctx,
    client.Responses,
    req,
    sdk.StructuredOptions{MaxRetries: 2},
    callOpts...,
)
if err != nil {
    return err
}

fmt.Printf("Name: %s, Age: %d\n", result.Value.Name, result.Value.Age)

Streaming Structured Output

Build progressive UIs that render fields as they complete:

type Article struct {
    Title   string `json:"title"`
    Summary string `json:"summary"`
    Body    string `json:"body"`
}

ctx := context.Background()

req, callOpts, err := client.Responses.New().
    Model(sdk.NewModelID("claude-sonnet-4-5")).
    User("Write an article about Go").
    Build()
if err != nil {
    return err
}

stream, err := sdk.StreamStructured[Article](ctx, client.Responses, req, "", callOpts...)
if err != nil {
    return err
}
defer stream.Close()

for {
    event, ok, err := stream.Next()
    if err != nil {
        return err
    }
    if !ok {
        break
    }
    if event.Payload == nil {
        continue
    }

    // Render fields as soon as they're complete
    if event.CompleteFields["title"] {
        renderTitle(event.Payload.Title)  // Safe to display
    }
    if event.CompleteFields["summary"] {
        renderSummary(event.Payload.Summary)
    }

    // Show streaming preview of incomplete fields
    if !event.CompleteFields["body"] && event.Payload.Body != "" {
        renderBodyPreview(event.Payload.Body + "▋")
    }
}

Error Handling

Error Types

The SDK provides typed errors for different failure modes:

import (
    sdk "github.com/modelrelay/sdk-go"
    "errors"
)

answer, err := client.Responses.Text(ctx, model, system, user)
if err != nil {
    var apiErr sdk.APIError
    if errors.As(err, &apiErr) {
        // Server returned an error response
        fmt.Printf("API error %d: %s\n", apiErr.Status, apiErr.Message)
        fmt.Printf("Code: %s\n", apiErr.Code)
        fmt.Printf("Request ID: %s\n", apiErr.RequestID)

        // Check specific error types
        if apiErr.IsRateLimit() {
            fmt.Println("Rate limited, retry later")
        }
        if apiErr.IsUnauthorized() {
            fmt.Println("Invalid API key")
        }
        if apiErr.IsValidation() {
            for _, field := range apiErr.Fields {
                fmt.Printf("Field %s: %s\n", field.Field, field.Message)
            }
        }
        return
    }

    var transportErr sdk.TransportError
    if errors.As(err, &transportErr) {
        // Network or connection error
        fmt.Printf("Transport error: %s\n", transportErr.Message)
        return
    }

    var configErr sdk.ConfigError
    if errors.As(err, &configErr) {
        // Invalid configuration
        fmt.Printf("Config error: %s\n", configErr.Reason)
        return
    }

    // Unknown error
    return err
}

Error Codes

Use typed error codes for programmatic handling:

var apiErr sdk.APIError
if errors.As(err, &apiErr) {
    switch apiErr.Code {
    case sdk.ErrCodeRateLimit:
        // Back off and retry
    case sdk.ErrCodeUnauthorized:
        // Re-authenticate
    case sdk.ErrCodeNotFound:
        // Resource doesn't exist
    case sdk.ErrCodeValidation, sdk.ErrCodeInvalidInput:
        // Check request parameters
    case sdk.ErrCodePaymentRequired:
        // Customer quota exceeded
    }
}

Stream Timeout Errors

Handle streaming-specific timeouts:

var timeoutErr sdk.StreamTimeoutError
if errors.As(err, &timeoutErr) {
    switch timeoutErr.Kind {
    case sdk.StreamTimeoutTTFT:
        fmt.Println("No response within time-to-first-token limit")
    case sdk.StreamTimeoutIdle:
        fmt.Println("Stream went idle")
    case sdk.StreamTimeoutTotal:
        fmt.Println("Total stream time exceeded")
    }
}

Context Usage

All SDK methods accept context.Context for cancellation and timeouts:

// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

answer, err := client.Responses.Text(ctx, model, system, user)

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

go func() {
    time.Sleep(5 * time.Second)
    cancel()  // Cancel the request
}()

answer, err := client.Responses.Text(ctx, model, system, user)
if errors.Is(err, context.Canceled) {
    fmt.Println("Request was cancelled")
}

Customer Management

Manage customers for billing and usage tracking:

ctx := context.Background()

// Create or update a customer
customer, err := client.Customers.Upsert(ctx, sdk.CustomerUpsertRequest{
    ExternalID: sdk.NewCustomerExternalID("your-user-id"),
    Email:      "user@example.com",
})
if err != nil {
    return err
}

// Create a Stripe checkout session for subscription billing
session, err := client.Customers.Subscribe(ctx, customer.Customer.ID, sdk.CustomerSubscribeRequest{
    TierID:     uuid.MustParse("tier-uuid"),
    SuccessURL: "https://myapp.com/success",
    CancelURL:  "https://myapp.com/cancel",
})
if err != nil {
    return err
}

// Redirect user to session.URL for payment

// Check subscription status
status, err := client.Customers.GetSubscription(ctx, customer.Customer.ID)
if err != nil {
    return err
}

fmt.Printf("Active: %v\n", status.Active)

Token Providers

Use token providers for automatic bearer token management:

Mint Customer Tokens

Backend mints tokens for frontend use:

ctx := context.Background()
secret, _ := sdk.ParseSecretKey(os.Getenv("MODELRELAY_API_KEY"))

customerID := uuid.MustParse(os.Getenv("MODELRELAY_CUSTOMER_ID"))

provider, err := sdk.NewCustomerTokenProvider(sdk.CustomerTokenProviderConfig{
    SecretKey: secret,
    Request:   sdk.NewCustomerTokenRequestForCustomerID(customerID),
})
if err != nil {
    return err
}

client, err := sdk.NewClientWithTokenProvider(provider)
if err != nil {
    return err
}

// Token is automatically obtained and refreshed
text, err := client.Responses.Text(ctx, sdk.NewModelID("claude-sonnet-4-5"), "You are helpful.", "Hello!")

Service Clients

The SDK organizes functionality into service clients:

Client Description
client.Responses Create AI responses, streaming
client.Customers Customer CRUD, checkout sessions
client.Tiers List pricing tiers
client.Models List available models
client.Auth Token minting
client.Usage Usage statistics
client.Runs Workflow execution
client.Workflows Workflow management

Next Steps