Skip to content

A zero-dependency Go client library for the OpenRouter API with full streaming support, comprehensive error handling, and complete API coverage including chat completions, tool calling, structured outputs, and web search.

License

Notifications You must be signed in to change notification settings

hra42/openrouter-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

openrouter-go

Go Report Card GoDoc License: Unlicense Go Version

A zero-dependency Go package providing complete bindings for the OpenRouter API, supporting all available endpoints with full streaming capabilities.

Features

  • ✅ Complete API coverage (chat completions, legacy completions, models, model endpoints, and providers)
  • ✅ Full streaming support with Server-Sent Events (SSE)
  • ✅ Zero external dependencies
  • ✅ Go 1.25.1 support
  • ✅ Comprehensive error handling and retry logic
  • ✅ Context-aware cancellation
  • ✅ Thread-safe client operations
  • ✅ Extensive configuration options via functional options pattern
  • ✅ Per-request Zero Data Retention (ZDR) enforcement
  • ✅ Structured outputs with JSON schema validation
  • ✅ Tool/Function calling support with streaming
  • ✅ Message transforms for automatic context window management
  • ✅ Web Search plugin for real-time web data integration
  • ✅ Model listing and discovery with category filtering
  • ✅ Model endpoint inspection with pricing and uptime details
  • ✅ Provider listing with policy information
  • ✅ Credit balance and usage tracking
  • ✅ Activity analytics for usage monitoring and cost tracking
  • ✅ API key information retrieval with usage and rate limit details
  • ✅ API key management with listing, filtering, and creation capabilities

Installation

go get github.com/hra42/openrouter-go

Quick Start

package main

import (
    "context"
    "fmt"
    "github.com/hra42/openrouter-go"
)

func main() {
    client := openrouter.NewClient(
        openrouter.WithAPIKey("your-api-key"),
    )

    messages := []openrouter.Message{
        {Role: "user", Content: "Hello, how are you?"},
    }

    response, err := client.ChatComplete(context.Background(),
        openrouter.WithModel("openai/gpt-4o"),
        openrouter.WithMessages(messages),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(response.Choices[0].Message.Content)
}

API Design

Client Initialization

// Basic initialization
client := openrouter.NewClient("api-key")

// With options
client := openrouter.NewClient("api-key",
    openrouter.WithBaseURL("https://custom.openrouter.ai"),
    openrouter.WithHTTPClient(customHTTPClient),
    openrouter.WithTimeout(60 * time.Second),
    openrouter.WithRetry(3, time.Second),
    openrouter.WithAppName("MyApp"),
    openrouter.WithReferer("https://myapp.com"),
)

Chat Completions

// Non-streaming
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("anthropic/claude-3-opus"),
    openrouter.WithTemperature(0.7),
    openrouter.WithMaxTokens(1000),
)

// Streaming
stream, err := client.ChatCompleteStream(ctx, messages,
    openrouter.WithModel("anthropic/claude-3-opus"),
)

for event := range stream.Events() {
    fmt.Print(event.Choices[0].Delta.Content)
}

if err := stream.Err(); err != nil {
    // Handle streaming error
}

// With Zero Data Retention (ZDR)
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("anthropic/claude-3-opus"),
    openrouter.WithZDR(true), // Enforce ZDR for this request
)

Legacy Completions

// Non-streaming
response, err := client.Complete(ctx, "Once upon a time",
    openrouter.WithModel("openai/gpt-3.5-turbo-instruct"),
    openrouter.WithMaxTokens(100),
)

// Streaming
stream, err := client.CompleteStream(ctx, "Once upon a time",
    openrouter.WithModel("openai/gpt-3.5-turbo-instruct"),
)

// With Zero Data Retention (ZDR)
response, err := client.Complete(ctx, "Once upon a time",
    openrouter.WithModel("openai/gpt-3.5-turbo-instruct"),
    openrouter.WithCompletionZDR(true), // Enforce ZDR for this request
)

Listing Available Models

// List all available models
response, err := client.ListModels(ctx, nil)
if err != nil {
    log.Fatal(err)
}

for _, model := range response.Data {
    fmt.Printf("%s - %s\n", model.ID, model.Name)
    fmt.Printf("  Context: %.0f tokens\n", *model.ContextLength)
    fmt.Printf("  Pricing: $%s/M prompt, $%s/M completion\n",
        model.Pricing.Prompt, model.Pricing.Completion)
}

// Filter models by category (e.g., "programming")
response, err := client.ListModels(ctx, &openrouter.ListModelsOptions{
    Category: "programming",
})

Listing Model Endpoints

Get detailed information about the specific endpoints (providers) available for a model:

// List all endpoints for a specific model
response, err := client.ListModelEndpoints(ctx, "openai", "gpt-4")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Model: %s\n", response.Data.Name)
fmt.Printf("Total endpoints: %d\n\n", len(response.Data.Endpoints))

// Examine each provider endpoint
for _, endpoint := range response.Data.Endpoints {
    fmt.Printf("Provider: %s\n", endpoint.ProviderName)
    fmt.Printf("  Status: %s\n", endpoint.Status)
    fmt.Printf("  Context Length: %.0f tokens\n", endpoint.ContextLength)
    fmt.Printf("  Pricing - Prompt: $%s/M, Completion: $%s/M\n",
        endpoint.Pricing.Prompt, endpoint.Pricing.Completion)

    if endpoint.UptimeLast30m != nil {
        fmt.Printf("  Uptime (30m): %.2f%%\n", *endpoint.UptimeLast30m*100)
    }

    if endpoint.Quantization != nil {
        fmt.Printf("  Quantization: %s\n", *endpoint.Quantization)
    }

    fmt.Printf("  Supported Parameters: %v\n\n", endpoint.SupportedParameters)
}

This endpoint is useful for:

  • Comparing pricing across different providers for the same model
  • Checking provider availability and uptime
  • Finding endpoints with specific quantization levels
  • Discovering which parameters are supported by each provider

Listing Available Providers

Get information about all providers available through OpenRouter:

// List all providers
response, err := client.ListProviders(ctx)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Total providers: %d\n\n", len(response.Data))

// Display provider information
for _, provider := range response.Data {
    fmt.Printf("Provider: %s (%s)\n", provider.Name, provider.Slug)

    if provider.PrivacyPolicyURL != nil {
        fmt.Printf("  Privacy Policy: %s\n", *provider.PrivacyPolicyURL)
    }

    if provider.TermsOfServiceURL != nil {
        fmt.Printf("  Terms of Service: %s\n", *provider.TermsOfServiceURL)
    }

    if provider.StatusPageURL != nil {
        fmt.Printf("  Status Page: %s\n", *provider.StatusPageURL)
    }
}

This endpoint is useful for:

  • Reviewing provider policies and terms
  • Finding provider status pages for uptime monitoring
  • Understanding which providers are available
  • Checking provider compliance information

Getting Credit Balance

Retrieve your current credit balance and usage for the authenticated user:

// Get credit balance
response, err := client.GetCredits(ctx)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Total Credits: $%.2f\n", response.Data.TotalCredits)
fmt.Printf("Total Usage: $%.2f\n", response.Data.TotalUsage)

// Calculate remaining balance
remaining := response.Data.TotalCredits - response.Data.TotalUsage
fmt.Printf("Remaining: $%.2f\n", remaining)

// Check usage percentage
if response.Data.TotalCredits > 0 {
    usagePercent := (response.Data.TotalUsage / response.Data.TotalCredits) * 100
    fmt.Printf("Usage: %.2f%%\n", usagePercent)
}

This endpoint is useful for:

  • Monitoring credit consumption in real-time
  • Setting up alerts for low balance
  • Tracking API usage costs
  • Budget management and forecasting

Getting Activity Data

Retrieve daily user activity data grouped by model endpoint for the last 30 (completed) UTC days:

// Get all activity data
response, err := client.GetActivity(ctx, nil)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Total activity records: %d\n\n", len(response.Data))

// Calculate summary statistics
totalUsage := 0.0
totalRequests := 0.0

for _, data := range response.Data {
    totalUsage += data.Usage
    totalRequests += data.Requests
}

fmt.Printf("Total usage: $%.4f\n", totalUsage)
fmt.Printf("Total requests: %.0f\n", totalRequests)

// Filter by specific date
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
dateActivity, err := client.GetActivity(ctx, &openrouter.ActivityOptions{
    Date: yesterday,
})
if err != nil {
    log.Fatal(err)
}

// Display activity for specific date
for _, data := range dateActivity.Data {
    fmt.Printf("Date: %s\n", data.Date)
    fmt.Printf("Model: %s\n", data.Model)
    fmt.Printf("Provider: %s\n", data.ProviderName)
    fmt.Printf("Requests: %.0f\n", data.Requests)
    fmt.Printf("Usage: $%.4f\n", data.Usage)
    fmt.Printf("Tokens: %.0f prompt, %.0f completion, %.0f reasoning\n",
        data.PromptTokens, data.CompletionTokens, data.ReasoningTokens)
}

Important: This endpoint requires a provisioning key (not a regular inference API key). Create one at: https://openrouter.ai/settings/provisioning-keys

This endpoint is useful for:

  • Daily usage analytics and cost tracking
  • Model performance comparison
  • Provider usage distribution analysis
  • Historical cost analysis and forecasting
  • BYOK (Bring Your Own Key) usage tracking

Getting API Key Information

Retrieve information about your current API key including usage, limits, and rate limits:

// Get API key information
response, err := client.GetKey(ctx)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("API Key Label: %s\n", response.Data.Label)

// Display limit information
if response.Data.Limit != nil {
    fmt.Printf("Credit Limit: $%.2f\n", *response.Data.Limit)
} else {
    fmt.Printf("Credit Limit: Unlimited\n")
}

fmt.Printf("Usage: $%.4f\n", response.Data.Usage)

// Display remaining balance
if response.Data.LimitRemaining != nil {
    fmt.Printf("Remaining: $%.4f\n", *response.Data.LimitRemaining)

    // Calculate usage percentage
    if response.Data.Limit != nil && *response.Data.Limit > 0 {
        usagePercent := (response.Data.Usage / *response.Data.Limit) * 100
        fmt.Printf("Usage: %.2f%%\n", usagePercent)
    }
}

// Display key type
fmt.Printf("Free Tier: %v\n", response.Data.IsFreeTier)
fmt.Printf("Provisioning Key: %v\n", response.Data.IsProvisioningKey)

// Display rate limit if available
if response.Data.RateLimit != nil {
    fmt.Printf("Rate Limit: %.0f requests per %s\n",
        response.Data.RateLimit.Requests,
        response.Data.RateLimit.Interval)
}

This endpoint is useful for:

  • Monitoring API key usage and limits
  • Checking remaining credits
  • Understanding rate limit constraints
  • Identifying key type (free tier vs paid, inference vs provisioning)
  • Building usage alerts and notifications

Listing All API Keys

Retrieve a list of all API keys associated with your account. Requires a Provisioning API key (not a regular inference API key):

// List all API keys
response, err := client.ListKeys(ctx, nil)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Total API keys: %d\n\n", len(response.Data))

// Display key information
for i, key := range response.Data {
    status := "Active"
    if key.Disabled {
        status = "Disabled"
    }

    fmt.Printf("%d. %s [%s]\n", i+1, key.Label, status)
    fmt.Printf("   Name: %s\n", key.Name)
    fmt.Printf("   Limit: $%.2f\n", key.Limit)
    fmt.Printf("   Created: %s\n", key.CreatedAt)
    fmt.Printf("   Updated: %s\n", key.UpdatedAt)
}

// Example with pagination and filtering
offset := 10
includeDisabled := true
filteredKeys, err := client.ListKeys(ctx, &openrouter.ListKeysOptions{
    Offset:          &offset,
    IncludeDisabled: &includeDisabled,
})

Important: This endpoint requires a provisioning key (not a regular inference API key). Create one at: https://openrouter.ai/settings/provisioning-keys

This endpoint is useful for:

  • Managing multiple API keys programmatically
  • Auditing key usage and creation dates
  • Identifying and managing disabled keys
  • Implementing key rotation strategies
  • Building API key management dashboards

Getting API Key by Hash

Retrieve details about a specific API key by its hash. Requires a Provisioning API key:

// Get key details by hash (hash obtained from ListKeys or key creation)
hash := "abc123hash"
keyDetails, err := client.GetKeyByHash(ctx, hash)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Label: %s\n", keyDetails.Data.Label)
fmt.Printf("Name: %s\n", keyDetails.Data.Name)
fmt.Printf("Limit: $%.2f\n", keyDetails.Data.Limit)
fmt.Printf("Disabled: %v\n", keyDetails.Data.Disabled)
fmt.Printf("Created: %s\n", keyDetails.Data.CreatedAt)
fmt.Printf("Updated: %s\n", keyDetails.Data.UpdatedAt)

// Example: Get hash from list and retrieve details
keys, err := client.ListKeys(ctx, nil)
if err != nil {
    log.Fatal(err)
}

if len(keys.Data) > 0 {
    firstHash := keys.Data[0].Hash
    details, err := client.GetKeyByHash(ctx, firstHash)
    // ...
}

Important: This endpoint requires a provisioning key (not a regular inference API key). Create one at: https://openrouter.ai/settings/provisioning-keys

This endpoint is useful for:

  • Inspecting individual API key details
  • Verifying key status and configuration
  • Monitoring specific key usage patterns
  • Building key detail views in dashboards
  • Auditing key configuration changes

Creating API Keys

Create new API keys programmatically with custom limits and settings. Requires a Provisioning API key:

// Create an API key with a credit limit
limit := 100.0
keyResp, err := client.CreateKey(ctx, &openrouter.CreateKeyRequest{
    Name:  "Production API Key",
    Limit: &limit,
})
if err != nil {
    log.Fatal(err)
}

// ⚠️ IMPORTANT: Save this value immediately!
// This is the ONLY time the full API key will be returned
fmt.Printf("New API Key: %s\n", keyResp.Key)
fmt.Printf("Label: %s\n", keyResp.Data.Label)
fmt.Printf("Limit: $%.2f\n", keyResp.Data.Limit)

// Create a key with BYOK limit inclusion
includeBYOK := true
keyResp2, err := client.CreateKey(ctx, &openrouter.CreateKeyRequest{
    Name:               "BYOK Key",
    Limit:              &limit,
    IncludeBYOKInLimit: &includeBYOK,
})

// Create a key without a specific limit (uses account limit)
keyResp3, err := client.CreateKey(ctx, &openrouter.CreateKeyRequest{
    Name: "Unlimited Key",
})

Critical Security Note: The Key field in the response contains the actual API key value. This is the ONLY time this value will ever be returned. Store it securely immediately!

Important: This endpoint requires a provisioning key (not a regular inference API key). Create one at: https://openrouter.ai/settings/provisioning-keys

This endpoint is useful for:

  • Automated API key provisioning
  • Implementing key rotation workflows
  • Creating keys with custom credit limits
  • Setting up BYOK (Bring Your Own Key) configurations
  • Building self-service key management systems

Deleting API Keys

Delete an API key by its hash. Requires a Provisioning API key:

// Delete a key by hash (hash obtained from ListKeys or key creation)
hash := "abc123hash"
result, err := client.DeleteKey(ctx, hash)
if err != nil {
    log.Fatal(err)
}

if result.Data.Success {
    fmt.Println("API key successfully deleted")
}

// Example: Delete a key created in the same session
keyResp, err := client.CreateKey(ctx, &openrouter.CreateKeyRequest{
    Name: "Temporary Key",
})
if err != nil {
    log.Fatal(err)
}

// Later... delete it
deleteResult, err := client.DeleteKey(ctx, keyResp.Data.Hash)
if err != nil {
    log.Fatal(err)
}

⚠️ WARNING: This operation is irreversible! Once deleted, the API key cannot be recovered and any applications using it will immediately lose access.

Important: This endpoint requires a provisioning key (not a regular inference API key). Create one at: https://openrouter.ai/settings/provisioning-keys

This endpoint is useful for:

  • Automated key rotation and cleanup
  • Removing compromised or unused keys
  • Implementing temporary key workflows
  • Building key lifecycle management systems
  • Programmatic key revocation

Updating API Keys

Update an existing API key's properties (name, limit, disabled status) by its hash. Requires a Provisioning API key:

// Update just the key name
hash := "abc123hash"
newName := "Updated Production Key"
result, err := client.UpdateKey(ctx, hash, &openrouter.UpdateKeyRequest{
    Name: &newName,
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Updated key: %s\n", result.Data.Label)

// Disable a key
disabled := true
result, err := client.UpdateKey(ctx, hash, &openrouter.UpdateKeyRequest{
    Disabled: &disabled,
})

// Update credit limit
newLimit := 200.0
result, err := client.UpdateKey(ctx, hash, &openrouter.UpdateKeyRequest{
    Limit: &newLimit,
})

// Update multiple fields at once
result, err := client.UpdateKey(ctx, hash, &openrouter.UpdateKeyRequest{
    Name:               &newName,
    Limit:              &newLimit,
    IncludeBYOKInLimit: &[]bool{true}[0],
})

Important: This endpoint requires a provisioning key (not a regular inference API key). Create one at: https://openrouter.ai/settings/provisioning-keys

All fields in UpdateKeyRequest are optional - only include the fields you want to update:

  • Name: Display name for the API key
  • Disabled: Set to true to disable the key (prevents usage)
  • Limit: Credit limit in dollars
  • IncludeBYOKInLimit: Whether BYOK (Bring Your Own Key) usage counts toward the limit

This endpoint is useful for:

  • Rotating key names for better organization
  • Adjusting credit limits based on usage patterns
  • Temporarily disabling keys without deletion
  • Managing BYOK limit policies
  • Implementing dynamic key management workflows

Code Quality

The library is built with a focus on code quality and maintainability:

  • Named Constants: Magic numbers have been extracted as named constants for better readability and maintainability

    • defaultJitterFactor (0.25): Default jitter factor for retry backoff (±25%)
    • maxReconnectBackoff (10s): Maximum backoff duration for stream reconnection attempts
    • defaultMaxDelay (30s): Default maximum delay for retry backoff
    • defaultMultiplier (2.0): Default multiplier for exponential backoff
  • Generic Options Pattern: Uses Go 1.18+ generics to reduce code duplication in functional options

    • Type-safe option setters with RequestConfig interface constraint
    • Shared implementation for common fields across ChatCompletion and Completion requests
    • Eliminates ~400 lines of duplicate code while maintaining type safety
  • Comprehensive Testing: Extensive unit test coverage with table-driven tests

  • Race Detection: All code is tested for race conditions

  • Thread Safety: Client is safe for concurrent use across goroutines

  • Error Handling: Rich error types with detailed context

Package Structure

openrouter-go/
├── client.go            # Main client implementation
├── completions.go       # Completion endpoint methods
├── chat.go              # Chat completion endpoint methods
├── models_endpoint.go   # Models listing endpoint methods
├── model_endpoints.go   # Model endpoints inspection methods
├── providers_endpoint.go # Providers listing endpoint methods
├── credits_endpoint.go  # Credits balance endpoint methods
├── activity_endpoint.go # Activity analytics endpoint methods
├── key_endpoint.go      # API key information endpoint methods
├── models.go            # Request/response type definitions
├── options.go           # Functional options for configuration
├── stream.go            # SSE streaming with generic Stream[T] implementation
├── errors.go            # Custom error types
├── retry.go             # Retry and backoff logic with named constants
├── examples/
│   ├── basic/             # Basic usage examples
│   ├── streaming/         # Streaming examples
│   ├── structured-output/ # Structured outputs with JSON schema
│   ├── tool-calling/      # Tool/function calling examples
│   ├── web_search/        # Web search plugin examples
│   ├── list-models/       # Model listing examples
│   ├── model-endpoints/   # Model endpoints inspection examples
│   ├── list-providers/    # Provider listing examples
│   ├── get-credits/       # Credit balance tracking examples
│   ├── activity/          # Activity analytics examples
│   ├── key/               # API key information examples
│   ├── list-keys/         # API key listing examples
│   ├── create-key/        # API key creation examples
│   └── advanced/          # Advanced configuration examples
└── internal/
    └── sse/               # Internal SSE parser implementation

App Attribution

Get your app featured in OpenRouter rankings and analytics by including attribution headers:

client := openrouter.NewClient(
    openrouter.WithAPIKey("your-api-key"),
    // Your app's URL (primary identifier)
    openrouter.WithReferer("https://myapp.com"),
    // Your app's display name
    openrouter.WithAppName("My AI Assistant"),
)

Benefits

When you use app attribution, your app will:

  • Appear in OpenRouter's public rankings
  • Be featured on individual model pages in the "Apps" tab
  • Get detailed analytics at openrouter.ai/apps?url=<your-app-url>
  • Gain visibility in the OpenRouter developer community

Localhost Development

For localhost development, always include a title:

client := openrouter.NewClient(
    openrouter.WithAPIKey("your-api-key"),
    openrouter.WithReferer("http://localhost:3000"),
    openrouter.WithAppName("Development App"), // Required for localhost
)

See the app attribution example for more details.

Requirements

  • Go 1.25.1
  • No external dependencies

Status

Production Ready - All 5 phases complete! The library is now ready for production use with:

  • ✅ Full foundation with all types and error handling
  • ✅ Robust HTTP communication with retry logic
  • ✅ Complete API implementation for chat and completions
  • ✅ Zero-dependency SSE streaming with reconnection support
  • ✅ Comprehensive test coverage and documentation
  • ✅ Production-ready examples for all use cases

Testing

Unit Tests

Run the unit test suite:

# Run all tests
go test ./...

# Run tests with coverage
go test -cover ./...

# Run tests with race detection
go test -race ./...

# Run specific test
go test -run TestChatComplete

E2E Tests

The project includes a comprehensive end-to-end test suite in cmd/openrouter-test/ that tests against the live OpenRouter API. The test suite is organized into logical modules:

Test Structure:

cmd/openrouter-test/
├── main.go              # Entry point and CLI
└── tests/
    ├── helpers.go       # Shared utilities
    ├── chat.go          # Chat, streaming, completion tests
    ├── routing.go       # Provider routing, ZDR, model suffixes
    ├── structured.go    # Structured output tests
    ├── tools.go         # Tool/function calling tests
    ├── transforms.go    # Message transforms tests
    ├── search.go        # Web search tests
    ├── models.go        # Models, endpoints, providers tests
    ├── account.go       # Credits, activity tests
    └── apikeys.go       # API key management tests

Running E2E Tests:

# Set your API key
export OPENROUTER_API_KEY="your-api-key"

# Run all tests (excluding web search)
go run cmd/openrouter-test/main.go -test all

# Run specific test categories
go run cmd/openrouter-test/main.go -test chat
go run cmd/openrouter-test/main.go -test streaming
go run cmd/openrouter-test/main.go -test tools
go run cmd/openrouter-test/main.go -test websearch  # Run separately on demand

# Run with custom model
go run cmd/openrouter-test/main.go -test all -model anthropic/claude-3-haiku

# Run with verbose output
go run cmd/openrouter-test/main.go -test chat -v

# Available tests:
# all, chat, stream, completion, error, provider, zdr, suffix,
# price, structured, tools, transforms, websearch, models,
# endpoints, providers, credits, activity, key, listkeys,
# createkey, updatekey, deletekey

Message Transforms

The library supports message transforms to automatically handle prompts that exceed a model's context window. This feature uses "middle-out" compression to remove content from the middle of long prompts where models typically pay less attention.

Basic Transform Usage

// Enable middle-out compression for chat completions
response, err := client.ChatComplete(ctx,
    openrouter.WithModel("meta-llama/llama-3.1-8b-instruct"),
    openrouter.WithMessages(messages),
    openrouter.WithTransforms("middle-out"), // Auto-compress if exceeds context
)

// Enable for legacy completions
response, err := client.Complete(ctx, prompt,
    openrouter.WithModel("openai/gpt-3.5-turbo-instruct"),
    openrouter.WithCompletionTransforms("middle-out"),
)

How It Works

When middle-out transform is enabled:

  1. OpenRouter finds models with at least half of your required tokens (input + completion)
  2. If your prompt exceeds the model's context, content is removed from the middle
  3. For models with message count limits (e.g. Anthropic's Claude), messages are compressed to stay within limits

Default Behavior

All OpenRouter endpoints with 8K (8,192 tokens) or less context length automatically use middle-out by default. To disable:

// Explicitly disable transforms for smaller models
response, err := client.ChatComplete(ctx,
    openrouter.WithModel("some-8k-model"),
    openrouter.WithMessages(messages),
    openrouter.WithTransforms(), // Empty array disables transforms
)

When to Use

Message transforms are useful when:

  • Perfect recall is not required
  • You want automatic fallback for long conversations
  • Working with models that have smaller context windows
  • Handling variable-length user inputs that might exceed limits

Important Notes

  • Middle content is compressed because LLMs pay less attention to the middle of sequences
  • The transform handles both token limits and message count limits
  • Without transforms, requests exceeding limits will fail with an error
  • Consider using models with larger context windows if perfect recall is critical

Provider Routing

The library supports comprehensive provider routing options to control how your requests are handled across different providers.

Basic Provider Routing

// Specify provider order
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("meta-llama/llama-3.1-70b-instruct"),
    openrouter.WithProviderOrder("together", "openai", "anthropic"),
)

// Disable fallbacks (only use specified providers)
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("mistralai/mixtral-8x7b-instruct"),
    openrouter.WithProviderOrder("together"),
    openrouter.WithAllowFallbacks(false),
)

// Sort providers by throughput or price
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("meta-llama/llama-3.1-70b-instruct"),
    openrouter.WithProviderSort("throughput"), // or "price", "latency"
)

Model Suffixes

// Use :nitro suffix for throughput optimization
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("meta-llama/llama-3.1-70b-instruct:nitro"),
)

// Use :floor suffix for lowest price
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("meta-llama/llama-3.1-70b-instruct:floor"),
)

Provider Filtering

// Only use specific providers
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithOnlyProviders("azure", "openai"),
)

// Ignore specific providers
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("meta-llama/llama-3.3-70b-instruct"),
    openrouter.WithIgnoreProviders("deepinfra"),
)

// Filter by quantization levels
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("meta-llama/llama-3.1-8b-instruct"),
    openrouter.WithQuantizations("fp8", "fp16"),
)

Price Constraints

// Set maximum pricing constraints
maxPrice := openrouter.MaxPrice{
    Prompt: 1.0,     // Max $1 per million prompt tokens
    Completion: 2.0, // Max $2 per million completion tokens
}
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("meta-llama/llama-3.1-70b-instruct"),
    openrouter.WithMaxPrice(maxPrice),
    openrouter.WithProviderSort("throughput"), // Use fastest provider under price limit
)

Data Policies

// Require providers that don't collect data
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("anthropic/claude-3-opus"),
    openrouter.WithDataCollection("deny"), // or "allow"
)

// Require providers that support all parameters
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithRequireParameters(true),
    openrouter.WithResponseFormat(openrouter.ResponseFormat{Type: "json_object"}),
)

Zero Data Retention (ZDR)

The library supports per-request Zero Data Retention enforcement. When enabled, requests will only be routed to endpoints with Zero Data Retention policies.

// For chat completions
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("anthropic/claude-3-opus"),
    openrouter.WithZDR(true), // Enforce ZDR for this specific request
)

// For legacy completions
response, err := client.Complete(ctx, prompt,
    openrouter.WithModel("openai/gpt-3.5-turbo-instruct"),
    openrouter.WithCompletionZDR(true), // Enforce ZDR for this specific request
)

// With custom provider configuration
provider := openrouter.Provider{
    ZDR: &[]bool{true}[0], // Enable ZDR
}
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("anthropic/claude-3-opus"),
    openrouter.WithProvider(provider),
)

Note: The request-level zdr parameter operates as an "OR" with your account-wide ZDR setting. If either is enabled, ZDR enforcement will be applied.

Structured Outputs

The library supports structured outputs for compatible models, ensuring responses follow a specific JSON Schema format. This feature is useful when you need consistent, well-formatted responses that can be reliably parsed by your application.

Basic Structured Output

// Define a JSON schema for the expected response
weatherSchema := map[string]interface{}{
    "type": "object",
    "properties": map[string]interface{}{
        "location": map[string]interface{}{
            "type": "string",
            "description": "City or location name",
        },
        "temperature": map[string]interface{}{
            "type": "number",
            "description": "Temperature in Celsius",
        },
        "conditions": map[string]interface{}{
            "type": "string",
            "description": "Weather conditions",
        },
    },
    "required": []string{"location", "temperature", "conditions"},
    "additionalProperties": false,
}

// Use structured output with chat completion
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithJSONSchema("weather", true, weatherSchema),
    openrouter.WithRequireParameters(true), // Ensure model supports structured outputs
)

// The response will be valid JSON matching your schema
var weatherData map[string]interface{}
json.Unmarshal([]byte(response.Choices[0].Message.Content.(string)), &weatherData)

Simplified JSON Mode

// For simpler cases, use JSON mode without a strict schema
response, err := client.ChatComplete(ctx, messages,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithJSONMode(), // Returns JSON without enforcing a schema
)

Streaming with Structured Output

// Structured outputs work with streaming too
stream, err := client.ChatCompleteStream(ctx, messages,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithJSONSchema("response", true, schema),
)

var fullContent string
for event := range stream.Events() {
    if len(event.Choices) > 0 && event.Choices[0].Delta != nil {
        if content, ok := event.Choices[0].Delta.Content.(string); ok {
            fullContent += content
        }
    }
}

// Parse the complete JSON response
var result map[string]interface{}
json.Unmarshal([]byte(fullContent), &result)

Model Support

Not all models support structured outputs. To ensure compatibility:

  1. Check the models page for support
  2. Use WithRequireParameters(true) to route only to compatible providers
  3. Models known to support structured outputs include:
    • OpenAI models (GPT-4o and later)
    • Many Fireworks-provided models

Best Practices

  • Always set strict: true in your JSON schema for exact compliance
  • Include clear descriptions in schema properties to guide the model
  • Use WithRequireParameters(true) to ensure routing to compatible providers
  • Test your schemas with the specific models you plan to use
  • Handle parsing errors gracefully as a fallback

Tool/Function Calling

The library provides full support for tool/function calling, allowing models to use external tools and functions during generation. This feature enables building powerful AI agents and assistants.

Basic Tool Calling

// Define a tool
tools := []openrouter.Tool{
    {
        Type: "function",
        Function: openrouter.Function{
            Name:        "get_weather",
            Description: "Get the current weather for a location",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "location": map[string]interface{}{
                        "type":        "string",
                        "description": "City name or zip code",
                    },
                    "unit": map[string]interface{}{
                        "type":        "string",
                        "enum":        []string{"celsius", "fahrenheit"},
                        "description": "Temperature unit",
                    },
                },
                "required": []string{"location"},
            },
        },
    },
}

// Make a request with tools
messages := []openrouter.Message{
    {Role: "user", Content: "What's the weather in San Francisco?"},
}

response, err := client.ChatComplete(ctx,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithMessages(messages),
    openrouter.WithTools(tools),
)

// Check for tool calls in the response
if len(response.Choices[0].Message.ToolCalls) > 0 {
    // Process tool calls
    for _, toolCall := range response.Choices[0].Message.ToolCalls {
        // Parse arguments
        var args map[string]interface{}
        json.Unmarshal([]byte(toolCall.Function.Arguments), &args)

        // Execute the tool (your implementation)
        result := executeWeatherTool(args)

        // Add tool result to messages
        messages = append(messages, response.Choices[0].Message)
        messages = append(messages, openrouter.Message{
            Role:       "tool",
            Content:    result,
            ToolCallID: toolCall.ID,
        })
    }

    // Get final response with tool results
    finalResponse, _ := client.ChatComplete(ctx,
        openrouter.WithModel("openai/gpt-4o"),
        openrouter.WithMessages(messages),
        openrouter.WithTools(tools),
    )
}

Tool Choice Control

// Let the model decide (default)
response, _ := client.ChatComplete(ctx,
    openrouter.WithMessages(messages),
    openrouter.WithTools(tools),
    openrouter.WithToolChoice("auto"),
)

// Disable tool usage
response, _ := client.ChatComplete(ctx,
    openrouter.WithMessages(messages),
    openrouter.WithTools(tools),
    openrouter.WithToolChoice("none"),
)

// Force specific tool usage
response, _ := client.ChatComplete(ctx,
    openrouter.WithMessages(messages),
    openrouter.WithTools(tools),
    openrouter.WithToolChoice(map[string]interface{}{
        "type": "function",
        "function": map[string]interface{}{
            "name": "get_weather",
        },
    }),
)

Parallel Tool Calls

Control whether multiple tools can be called simultaneously:

// Disable parallel tool calls (sequential only)
parallelCalls := false
response, _ := client.ChatComplete(ctx,
    openrouter.WithMessages(messages),
    openrouter.WithTools(tools),
    openrouter.WithParallelToolCalls(&parallelCalls),
)

Streaming with Tool Calls

Tool calls are fully supported in streaming mode:

stream, err := client.ChatCompleteStream(ctx,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithMessages(messages),
    openrouter.WithTools(tools),
)

var toolCalls []openrouter.ToolCall
for event := range stream.Events() {
    // Parse streaming data
    var data map[string]interface{}
    json.Unmarshal([]byte(event.Data), &data)

    if choices, ok := data["choices"].([]interface{}); ok && len(choices) > 0 {
        choice := choices[0].(map[string]interface{})

        // Check for tool calls in delta
        if delta, ok := choice["delta"].(map[string]interface{}); ok {
            if toolCallsDelta, ok := delta["tool_calls"].([]interface{}); ok {
                // Accumulate tool call information
                // See examples/tool-calling/streaming.go for complete implementation
            }
        }

        // Check finish reason
        if finishReason, ok := choice["finish_reason"].(string); ok {
            if finishReason == "tool_calls" {
                // Process accumulated tool calls
            }
        }
    }
}

Multi-Tool Workflows

Design tools that work well together:

tools := []openrouter.Tool{
    {
        Type: "function",
        Function: openrouter.Function{
            Name:        "search_products",
            Description: "Search for products in the catalog",
            // Parameters...
        },
    },
    {
        Type: "function",
        Function: openrouter.Function{
            Name:        "check_inventory",
            Description: "Check inventory for a product",
            // Parameters...
        },
    },
    {
        Type: "function",
        Function: openrouter.Function{
            Name:        "place_order",
            Description: "Place an order for a product",
            // Parameters...
        },
    },
}

// The model can chain these tools naturally:
// search → check inventory → place order

Model Support

Tool calling is supported by many models. You can find compatible models by filtering on openrouter.ai/models?supported_parameters=tools.

Popular models with tool support include:

  • OpenAI GPT-4o and GPT-4o-mini
  • Anthropic Claude 3.5 Sonnet
  • Google Gemini models
  • Many open-source models via various providers

Best Practices for Tool Calling

  • Clear Descriptions: Provide detailed descriptions for tools and parameters
  • Error Handling: Always validate tool arguments before execution
  • Tool Results: Return structured, informative results from tools
  • Context Preservation: Maintain full conversation history including tool calls
  • Streaming: Handle tool calls appropriately when streaming responses
  • Testing: Test tool interactions with different models as behavior may vary

Web Search

The library supports OpenRouter's web search feature for augmenting model responses with real-time web data. Web search can be enabled using the :online model suffix or by configuring the web plugin.

Quick Start with :online Suffix

// Simple web search using :online suffix
response, err := client.ChatComplete(ctx,
    openrouter.WithModel("openai/gpt-4o:online"),
    openrouter.WithMessages([]openrouter.Message{
        {Role: "user", Content: "What are the latest AI developments this week?"},
    }),
)

Using the Web Plugin

// Configure web search with the plugin
webPlugin := openrouter.NewWebPlugin() // Uses defaults: auto engine, 5 results

response, err := client.ChatComplete(ctx,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithPlugins(webPlugin),
    openrouter.WithMessages(messages),
)

// Custom web plugin configuration
webPlugin := openrouter.NewWebPluginWithOptions(
    openrouter.WebSearchEngineExa,    // Force Exa search
    10,                                // Get 10 results
    "Recent web results for context:", // Custom prompt
)

response, err := client.ChatComplete(ctx,
    openrouter.WithModel("anthropic/claude-3.5-sonnet"),
    openrouter.WithPlugins(webPlugin),
    openrouter.WithMessages(messages),
)

Search Engine Options

  • Native: Uses the provider's built-in web search (OpenAI, Anthropic)
  • Exa: Uses Exa's neural search API (works with all models)
  • Auto (default): Automatically selects the best available engine
// Force native search for supported models
webPlugin := openrouter.Plugin{
    ID:     "web",
    Engine: string(openrouter.WebSearchEngineNative),
}

// Force Exa search for all models
webPlugin := openrouter.Plugin{
    ID:         "web",
    Engine:     string(openrouter.WebSearchEngineExa),
    MaxResults: 3,
}

Search Context Size (Native Only)

For models with native search support, control the search context depth:

response, err := client.ChatComplete(ctx,
    openrouter.WithModel("openai/gpt-4o"),
    openrouter.WithPlugins(openrouter.NewWebPlugin()),
    openrouter.WithWebSearchOptions(&openrouter.WebSearchOptions{
        SearchContextSize: string(openrouter.WebSearchContextHigh), // low, medium, high
    }),
    openrouter.WithMessages(messages),
)

Parsing Search Annotations

Web search results are included in the response annotations:

response, err := client.ChatComplete(ctx,
    openrouter.WithModel("openai/gpt-4o:online"),
    openrouter.WithMessages(messages),
)

// Extract URL citations from the response
citations := openrouter.ParseAnnotations(response.Choices[0].Message.Annotations)
for _, citation := range citations {
    fmt.Printf("Source: %s\n", citation.Title)
    fmt.Printf("URL: %s\n", citation.URL)
    fmt.Printf("Content: %s\n\n", citation.Content)
}

Pricing

  • Exa Search: $4 per 1000 results (default 5 results = $0.02 per request)
  • Native Search (OpenAI):
    • GPT-4o models: $30-50 per 1000 requests depending on context size
    • GPT-4o-mini models: $25-30 per 1000 requests
  • Native Search (Perplexity):
    • Sonar models: $5-12 per 1000 requests
    • SonarPro models: $6-14 per 1000 requests

Best Practices

  • Use :online suffix for simple cases with default settings
  • Configure the web plugin for fine-grained control over search behavior
  • Consider search costs when choosing between native and Exa engines
  • Parse annotations to display sources and improve transparency
  • Use higher search context for research tasks, lower for quick facts

Examples

The examples/ directory contains comprehensive examples:

  • basic/ - Simple usage examples for common tasks
  • streaming/ - Real-time streaming response handling
  • list-models/ - List and discover available models with filtering
  • model-endpoints/ - Inspect model endpoints with pricing and provider details
  • list-providers/ - List available providers with policy information
  • structured-output/ - JSON schema validation and structured responses
  • tool-calling/ - Complete tool/function calling examples with streaming
  • transforms/ - Message transforms for context window management
  • web_search/ - Web search plugin examples with various configurations
  • advanced/ - Advanced features like rate limiting and custom configuration

To run an example:

# Set your API key
export OPENROUTER_API_KEY="your-api-key"

# Run basic examples
go run examples/basic/main.go

# Run streaming examples
go run examples/streaming/main.go

# Run list models examples
go run examples/list-models/main.go

# Run model endpoints examples
go run examples/model-endpoints/main.go

# Run list providers examples
go run examples/list-providers/main.go

# Run advanced examples
go run examples/advanced/main.go

# Run structured output examples
go run examples/structured-output/main.go

# Run tool calling examples
go run examples/tool-calling/main.go

# Run streaming tool calling example
go run examples/tool-calling/streaming.go

# Run transforms examples
go run examples/transforms/main.go

# Run web search examples
go run examples/web_search/main.go

Documentation

For detailed API documentation and usage examples, see DOCUMENTATION.md.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

About

A zero-dependency Go client library for the OpenRouter API with full streaming support, comprehensive error handling, and complete API coverage including chat completions, tool calling, structured outputs, and web search.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •  

Languages