Skip to content
This repository was archived by the owner on Aug 17, 2026. It is now read-only.

Latest commit

History

350 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

openrouter-go

Go Report CardGoDocLicense: UnlicenseGo Version

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

📚 Docs site:https://openrouter-go.hra42.lol · Recipes:docs/recipes/ · Building with an AI coding agent? Start with AGENTS.md.

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.26 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
  • ✅ MCP (Model Context Protocol) tool conversion utilities
  • ✅ Message transforms for automatic context window management
  • ✅ Web Search plugin for real-time web data integration
  • ✅ Image inputs (multimodal) with URL and base64 support
  • ✅ Audio inputs with base64 encoding support (WAV, MP3)
  • ✅ PDF inputs with configurable parsing engines and file annotation reuse
  • ✅ 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
  • ✅ Workspaces management (create/list/update/delete, bulk member add/remove)
  • ✅ Organization member listing
  • ✅ Guardrails for spend caps, allowed model/provider lists, and ZDR enforcement
  • ✅ Video generation with async job submission, polling, and content download
  • ✅ Text-to-speech (/audio/speech) with mp3/pcm output
  • ✅ Rerank endpoint for relevance scoring (e.g. Cohere rerank-v3.5)
  • ✅ Broadcast webhook parsing (OTLP JSON traces)
  • ✅ OAuth PKCE authorization-code exchange helper
  • ⚠️[BETA] Responses API with reasoning, tool calling, web search, and streaming

Installation

go get github.com/hra42/openrouter-go

Quick Start

package main
import (
"context""fmt""github.com/hra42/openrouter-go"
)
funcmain() {
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),
)
iferr!=nil {
panic(err)
}
fmt.Println(response.Choices[0].Message.Content)
}

API Design

Client Initialization

// Basic initializationclient:=openrouter.NewClient("api-key")
// With optionsclient:=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-streamingresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("anthropic/claude-3-opus"),
openrouter.WithTemperature(0.7),
openrouter.WithMaxTokens(1000),
)
// Streamingstream, err:=client.ChatCompleteStream(ctx, messages,
openrouter.WithModel("anthropic/claude-3-opus"),
)
forevent:=rangestream.Events() {
fmt.Print(event.Choices[0].Delta.Content)
}
iferr:=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-streamingresponse, err:=client.Complete(ctx, "Once upon a time",
openrouter.WithModel("openai/gpt-3.5-turbo-instruct"),
openrouter.WithMaxTokens(100),
)
// Streamingstream, 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 modelsresponse, err:=client.ListModels(ctx, nil)
iferr!=nil {
log.Fatal(err)
}
for_, model:=rangeresponse.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 modelresponse, err:=client.ListModelEndpoints(ctx, "openai", "gpt-4")
iferr!=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 endpointfor_, endpoint:=rangeresponse.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)
ifendpoint.UptimeLast30m!=nil {
fmt.Printf(" Uptime (30m): %.2f%%\n", *endpoint.UptimeLast30m*100)
}
ifendpoint.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 providersresponse, err:=client.ListProviders(ctx)
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Total providers: %d\n\n", len(response.Data))
// Display provider informationfor_, provider:=rangeresponse.Data {
fmt.Printf("Provider: %s (%s)\n", provider.Name, provider.Slug)
ifprovider.PrivacyPolicyURL!=nil {
fmt.Printf(" Privacy Policy: %s\n", *provider.PrivacyPolicyURL)
}
ifprovider.TermsOfServiceURL!=nil {
fmt.Printf(" Terms of Service: %s\n", *provider.TermsOfServiceURL)
}
ifprovider.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 balanceresponse, err:=client.GetCredits(ctx)
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Total Credits: $%.2f\n", response.Data.TotalCredits)
fmt.Printf("Total Usage: $%.2f\n", response.Data.TotalUsage)
// Calculate remaining balanceremaining:=response.Data.TotalCredits-response.Data.TotalUsagefmt.Printf("Remaining: $%.2f\n", remaining)
// Check usage percentageifresponse.Data.TotalCredits>0 {
usagePercent:= (response.Data.TotalUsage/response.Data.TotalCredits) *100fmt.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 dataresponse, err:=client.GetActivity(ctx, nil)
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Total activity records: %d\n\n", len(response.Data))
// Calculate summary statisticstotalUsage:=0.0totalRequests:=0.0for_, data:=rangeresponse.Data {
totalUsage+=data.UsagetotalRequests+=data.Requests
}
fmt.Printf("Total usage: $%.4f\n", totalUsage)
fmt.Printf("Total requests: %.0f\n", totalRequests)
// Filter by specific dateyesterday:=time.Now().AddDate(0, 0, -1).Format("2006-01-02")
dateActivity, err:=client.GetActivity(ctx, &openrouter.ActivityOptions{
Date: yesterday,
})
iferr!=nil {
log.Fatal(err)
}
// Display activity for specific datefor_, data:=rangedateActivity.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 informationresponse, err:=client.GetKey(ctx)
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("API Key Label: %s\n", response.Data.Label)
// Display limit informationifresponse.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 balanceifresponse.Data.LimitRemaining!=nil {
fmt.Printf("Remaining: $%.4f\n", *response.Data.LimitRemaining)
// Calculate usage percentageifresponse.Data.Limit!=nil&&*response.Data.Limit>0 {
usagePercent:= (response.Data.Usage/*response.Data.Limit) *100fmt.Printf("Usage: %.2f%%\n", usagePercent)
}
}
// Display key typefmt.Printf("Free Tier: %v\n", response.Data.IsFreeTier)
fmt.Printf("Provisioning Key: %v\n", response.Data.IsProvisioningKey)
// Display rate limit if availableifresponse.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 keysresponse, err:=client.ListKeys(ctx, nil)
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Total API keys: %d\n\n", len(response.Data))
// Display key informationfori, key:=rangeresponse.Data {
status:="Active"ifkey.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 filteringoffset:=10includeDisabled:=truefilteredKeys, 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)
iferr!=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 detailskeys, err:=client.ListKeys(ctx, nil)
iferr!=nil {
log.Fatal(err)
}
iflen(keys.Data) >0 {
firstHash:=keys.Data[0].Hashdetails, 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 limitlimit:=100.0keyResp, err:=client.CreateKey(ctx, &openrouter.CreateKeyRequest{
Name: "Production API Key",
Limit: &limit,
})
iferr!=nil {
log.Fatal(err)
}
// ⚠️ IMPORTANT: Save this value immediately!// This is the ONLY time the full API key will be returnedfmt.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 inclusionincludeBYOK:=truekeyResp2, 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)
iferr!=nil {
log.Fatal(err)
}
ifresult.Data.Success {
fmt.Println("API key successfully deleted")
}
// Example: Delete a key created in the same sessionkeyResp, err:=client.CreateKey(ctx, &openrouter.CreateKeyRequest{
Name: "Temporary Key",
})
iferr!=nil {
log.Fatal(err)
}
// Later... delete itdeleteResult, err:=client.DeleteKey(ctx, keyResp.Data.Hash)
iferr!=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 namehash:="abc123hash"newName:="Updated Production Key"result, err:=client.UpdateKey(ctx, hash, &openrouter.UpdateKeyRequest{
Name: &newName,
})
iferr!=nil {
log.Fatal(err)
}
fmt.Printf("Updated key: %s\n", result.Data.Label)
// Disable a keydisabled:=trueresult, err:=client.UpdateKey(ctx, hash, &openrouter.UpdateKeyRequest{
Disabled: &disabled,
})
// Update credit limitnewLimit:=200.0result, err:=client.UpdateKey(ctx, hash, &openrouter.UpdateKeyRequest{
Limit: &newLimit,
})
// Update multiple fields at onceresult, 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
├── mcp.go # MCP tool conversion utilities
├── responses.go # [BETA] Responses API endpoint methods
├── responses_models.go # [BETA] Responses API type definitions
├── responses_options.go # [BETA] Responses API functional options
├── examples/
│ ├── basic/ # Basic usage examples
│ ├── streaming/ # Streaming examples
│ ├── structured-output/ # Structured outputs with JSON schema
│ ├── tool-calling/ # Tool/function calling examples
│ ├── mcp-tools/ # MCP tool conversion 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
│ ├── responses/ # [BETA] Responses API 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 nameopenrouter.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.26
  • 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 keyexport 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 completionsresponse, 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 completionsresponse, 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 modelsresponse, 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 orderresponse, 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 priceresponse, 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 optimizationresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("meta-llama/llama-3.1-70b-instruct:nitro"),
)
// Use :floor suffix for lowest priceresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("meta-llama/llama-3.1-70b-instruct:floor"),
)

Provider Filtering

// Only use specific providersresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("openai/gpt-4o"),
openrouter.WithOnlyProviders("azure", "openai"),
)
// Ignore specific providersresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("meta-llama/llama-3.3-70b-instruct"),
openrouter.WithIgnoreProviders("deepinfra"),
)
// Filter by quantization levelsresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("meta-llama/llama-3.1-8b-instruct"),
openrouter.WithQuantizations("fp8", "fp16"),
)

Price Constraints

// Set maximum pricing constraintsmaxPrice:= openrouter.MaxPrice{
Prompt: 1.0, // Max $1 per million prompt tokensCompletion: 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 dataresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("anthropic/claude-3-opus"),
openrouter.WithDataCollection("deny"), // or "allow"
)
// Require providers that support all parametersresponse, 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 completionsresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("anthropic/claude-3-opus"),
openrouter.WithZDR(true), // Enforce ZDR for this specific request
)
// For legacy completionsresponse, 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 configurationprovider:= 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.

Image Inputs (Multimodal)

The library provides comprehensive support for sending images to vision models through the OpenRouter API. You can send images via URLs or base64-encoded data.

Single Image with URL

// Send a single image with textmessages:= []openrouter.Message{
openrouter.CreateUserMessageWithImage(
"What's in this image?",
"https://example.com/image.jpg",
),
}
response, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("google/gemini-2.0-flash-thinking-exp:free"),
)

Multiple Images

// Send multiple images in a single requestmessages:= []openrouter.Message{
openrouter.CreateUserMessageWithImages(
"Compare these images. What are the similarities?",
"https://example.com/image1.jpg",
"https://example.com/image2.jpg",
"https://example.com/image3.jpg",
),
}
response, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("google/gemini-2.0-flash-thinking-exp:free"),
)

Image with Detail Level

Some models support detail level parameters for controlling image analysis quality:

// Request high-detail analysis (more expensive, more detailed)messages:= []openrouter.Message{
openrouter.CreateUserMessageWithImageDetail(
"Describe this image in detail.",
"https://example.com/image.jpg",
"high", // Options: "low", "high", or "auto"
),
}
response, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("google/gemini-2.0-flash-thinking-exp:free"),
)

Detail level options:

  • "low" - Faster and cheaper, suitable for general understanding
  • "high" - More detailed analysis at higher cost
  • "auto" - Let the model decide based on image size (default)

Base64-Encoded Images

For local files or private images:

// Automatically encode and send a local imagemessage, err:=openrouter.CreateUserMessageWithBase64Image(
"What's in this image?",
"path/to/image.jpg",
)
iferr!=nil {
log.Fatal(err)
}
messages:= []openrouter.Message{message}
response, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("google/gemini-2.0-flash-thinking-exp:free"),
)

Multiple local images:

message, err:=openrouter.CreateUserMessageWithBase64Images(
"Compare these images",
"path/to/image1.jpg",
"path/to/image2.png",
)

Manual base64 encoding:

// Encode image file to base64 data URLdataURL, err:=openrouter.EncodeImageToBase64("path/to/image.jpg")
iferr!=nil {
log.Fatal(err)
}
// Or encode bytes directlyimageBytes:= []byte{...}
dataURL:=openrouter.EncodeImageBytesToBase64(imageBytes, "image/jpeg")

Content Builder for Complex Messages

For messages with interleaved text and images:

content:=openrouter.NewContentBuilder().
AddText("Here's the first image:").
AddImage("https://example.com/image1.jpg").
AddText("And here's the second with high detail:").
AddImageWithDetail("https://example.com/image2.jpg", "high").
AddText("What are the differences?")
messages:= []openrouter.Message{
content.BuildMessage("user"),
}
response, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("google/gemini-2.0-flash-thinking-exp:free"),
)

Supported Image Formats

  • PNG (image/png)
  • JPEG (image/jpeg)
  • WebP (image/webp)
  • GIF (image/gif)

Model Support

Most modern vision models support image inputs, including:

  • Google Gemini models (gemini-2.0-flash-thinking-exp, etc.)
  • OpenAI GPT-4 Vision models
  • Anthropic Claude 3 models
  • And many others

Check the OpenRouter models page for the latest list of vision-capable models.

Best Practices

  • Use URLs when possible - they're more efficient than base64 encoding
  • Image URLs must be publicly accessible
  • The number of images per request varies by model and provider
  • Some providers may have size limits on images
  • Pricing may vary based on image size and detail level
  • For production use, consider the OpenRouter documentation's recommendations about image placement in messages

See the image-inputs example for more comprehensive examples.

PDF Inputs (File Support)

The library provides comprehensive support for sending PDF files to models through the OpenRouter API. PDF files can be sent via URLs or base64-encoded data. This feature works with any model on OpenRouter, with automatic fallback to PDF parsing when models don't have native file support.

Basic PDF from URL

// Send a PDF via URLmessages:= []openrouter.Message{
openrouter.CreateUserMessageWithPDF(
"What are the main points in this document?",
"https://bitcoin.org/bitcoin.pdf",
"bitcoin.pdf",
),
}
response, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("anthropic/claude-sonnet-4"),
)

Local PDF Files

// Automatically encode and send a local PDFmessage, err:=openrouter.CreateUserMessageWithBase64PDF(
"Summarize this document",
"path/to/document.pdf",
"document.pdf",
)
iferr!=nil {
log.Fatal(err)
}
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("google/gemma-3-27b-it"),
)

PDF Parsing Engines

OpenRouter provides three PDF processing engines with different cost/quality tradeoffs:

message:=openrouter.CreateUserMessageWithPDF(
"Extract key concepts from this document",
"https://example.com/document.pdf",
"document.pdf",
)
// Configure the PDF parsing engineplugin:=openrouter.CreateFileParserPlugin("pdf-text")
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("google/gemma-3-27b-it"),
openrouter.WithPlugins(plugin),
)

Available engines:

  • "pdf-text" - Free, best for well-structured PDFs with clear text content
  • "mistral-ocr" - $0.0004 per 1K pages, best for scanned documents with images/OCR needs
  • "native" - Uses model's native file support (charged as input tokens)
  • "" (empty) - Auto-selects native support first, then defaults to pdf-text

Reusing File Annotations

File annotations allow you to avoid re-parsing the same PDF in follow-up requests, saving processing time and costs:

// First request with PDFfirstMessage:=openrouter.CreateUserMessageWithPDF(
"What are the main concepts in this paper?",
"https://example.com/document.pdf",
"document.pdf",
)
resp1, err:=client.ChatComplete(ctx, []openrouter.Message{firstMessage},
openrouter.WithModel("google/gemma-3-27b-it"),
)
// Follow-up request - include the assistant's response with annotationsfollowUpMessages:= []openrouter.Message{
firstMessage,
resp1.Choices[0].Message, // Contains file annotationsopenrouter.CreateUserMessage("Can you elaborate on the first point?"),
}
resp2, err:=client.ChatComplete(ctx, followUpMessages,
openrouter.WithModel("google/gemma-3-27b-it"),
)
// PDF is NOT re-parsed - saves processing time and costs!

Multiple Files

You can send multiple files (PDFs, images, etc.) in a single request:

files:= []openrouter.File{
{
Filename: "document1.pdf",
FileData: "https://example.com/doc1.pdf",
},
{
Filename: "document2.pdf",
FileData: "https://example.com/doc2.pdf",
},
{
Filename: "chart.png",
FileData: "https://example.com/chart.png",
},
}
message:=openrouter.CreateUserMessageWithFiles(
"Compare these documents and analyze the chart",
files,
)
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("anthropic/claude-sonnet-4"),
)

Content Builder with PDFs

For complex messages with PDFs, images, and text:

content:=openrouter.NewContentBuilder().
AddText("Analyze this document:").
AddPDF("https://example.com/document.pdf", "document.pdf").
AddText("And compare with this image:").
AddImage("https://example.com/chart.png").
Build()
message:= openrouter.Message{
Role: "user",
Content: content,
}
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("anthropic/claude-sonnet-4"),
)

Manual Base64 Encoding

// Encode PDF file to base64 data URLdataURL, err:=openrouter.EncodePDFToBase64("path/to/document.pdf")
iferr!=nil {
log.Fatal(err)
}
// Or encode bytes directlypdfBytes:= []byte{...}
dataURL:=openrouter.EncodePDFBytesToBase64(pdfBytes)

Best Practices

  • Use URLs when possible - More efficient than base64 encoding
  • Use pdf-text for digital PDFs - It's free and works well for most documents
  • Reuse file annotations - Include the assistant's response with annotations in follow-up requests
  • Use mistral-ocr only for scanned documents - More expensive but necessary for image-based PDFs
  • Check model support - Some models have native file support which may be more cost-effective

Supported File Types

While this section focuses on PDFs, the file input API supports:

  • PDFs (application/pdf)
  • Images (image/png, image/jpeg, etc.)
  • And potentially other file types as OpenRouter expands support

See the pdf-inputs example for more comprehensive examples.

Text File Inputs

Send text-based files (code, configuration, documentation) to models using inline content. Text files are sent directly as UTF-8 text, not base64-encoded, for efficiency and token optimization.

Single Text File

// Send a code file for reviewmessage, err:=openrouter.CreateUserMessageWithTextFile(
"Review this code for bugs:",
"/path/to/code.py",
)
iferr!=nil {
log.Fatal(err)
}
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("anthropic/claude-sonnet-4"),
)

Multiple Text Files

// Compare or analyze multiple filesmessage, err:=openrouter.CreateUserMessageWithTextFiles(
"Compare these configuration files and identify differences:",
"/path/to/config1.yaml",
"/path/to/config2.yaml",
)
iferr!=nil {
log.Fatal(err)
}
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("openai/gpt-4"),
)

Content Builder with Text Files

// Build complex messages with multiple text filesbuilder:=openrouter.NewContentBuilder()
builder.AddText("I have several files to review:")
builder, err:=builder.AddTextFile("/path/to/main.go")
iferr!=nil {
log.Fatal(err)
}
builder.AddText("And here's the test file:")
builder, err=builder.AddTextFile("/path/to/main_test.go")
iferr!=nil {
log.Fatal(err)
}
builder.AddText("Do these files work together correctly?")
message:=builder.BuildMessage("user")
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("anthropic/claude-sonnet-4"),
)

Direct Text Content (Without File I/O)

// Send text content directly without reading from a filemessage:=openrouter.CreateUserMessageWithTextContent(
"Analyze this JSON structure:",
`{ "name": "example", "version": "1.0.0", "dependencies": {}}`,
"package.json",
)
response, err:=client.ChatComplete(ctx, []openrouter.Message{message},
openrouter.WithModel("openai/gpt-4o-mini"),
)

Supported File Formats

Common text formats

  • .txt - Plain text
  • .md - Markdown
  • .json - JSON files
  • .csv - CSV files

Code files

  • .js, .jsx - JavaScript
  • .ts, .tsx - TypeScript
  • .py - Python
  • .go - Go
  • .java - Java
  • .rs - Rust
  • .c, .cpp, .h - C/C++
  • .rb - Ruby
  • .php - PHP
  • .swift - Swift
  • .kt - Kotlin

Configuration files

  • .yaml, .yml - YAML
  • .toml - TOML
  • .xml - XML
  • .ini - INI
  • .env - Environment files

And many more! See text-file-inputs example for complete details.

Key Features

  • Inline delivery - Text sent directly (not base64), optimizing tokens
  • UTF-8 validation - All files must contain valid UTF-8 text
  • Filename context - Files are sent with filename headers for context
  • All models supported - Works with any text-capable model on OpenRouter
  • Format validation - Unsupported formats rejected with clear errors
  • Builder integration - Seamlessly mix text files with other content types

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 responseweatherSchema:=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 completionresponse, 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 schemavarweatherDatamap[string]interface{}
json.Unmarshal([]byte(response.Choices[0].Message.Content.(string)), &weatherData)

Simplified JSON Mode

// For simpler cases, use JSON mode without a strict schemaresponse, 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 toostream, err:=client.ChatCompleteStream(ctx, messages,
openrouter.WithModel("openai/gpt-4o"),
openrouter.WithJSONSchema("response", true, schema),
)
varfullContentstringforevent:=rangestream.Events() {
iflen(event.Choices) >0&&event.Choices[0].Delta!=nil {
ifcontent, ok:=event.Choices[0].Delta.Content.(string); ok {
fullContent+=content
}
}
}
// Parse the complete JSON responsevarresultmap[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 tooltools:= []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 toolsmessages:= []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 responseiflen(response.Choices[0].Message.ToolCalls) >0 {
// Process tool callsfor_, toolCall:=rangeresponse.Choices[0].Message.ToolCalls {
// Parse argumentsvarargsmap[string]interface{}
json.Unmarshal([]byte(toolCall.Function.Arguments), &args)
// Execute the tool (your implementation)result:=executeWeatherTool(args)
// Add tool result to messagesmessages=append(messages, response.Choices[0].Message)
messages=append(messages, openrouter.Message{
Role: "tool",
Content: result,
ToolCallID: toolCall.ID,
})
}
// Get final response with tool resultsfinalResponse, _:=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 usageresponse, _:=client.ChatComplete(ctx,
openrouter.WithMessages(messages),
openrouter.WithTools(tools),
openrouter.WithToolChoice("none"),
)
// Force specific tool usageresponse, _:=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:=falseresponse, _:=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),
)
vartoolCalls []openrouter.ToolCallforevent:=rangestream.Events() {
// Parse streaming datavardatamap[string]interface{}
json.Unmarshal([]byte(event.Data), &data)
ifchoices, ok:=data["choices"].([]interface{}); ok&&len(choices) >0 {
choice:=choices[0].(map[string]interface{})
// Check for tool calls in deltaifdelta, ok:=choice["delta"].(map[string]interface{}); ok {
iftoolCallsDelta, ok:=delta["tool_calls"].([]interface{}); ok {
// Accumulate tool call information// See examples/tool-calling/streaming.go for complete implementation
}
}
// Check finish reasoniffinishReason, ok:=choice["finish_reason"].(string); ok {
iffinishReason=="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

MCP Tool Conversion

The library provides utilities for converting MCP (Model Context Protocol) tool definitions to OpenRouter's OpenAI-compatible format. This enables seamless integration with MCP servers and clients.

Converting MCP Tools

// Define MCP tools (as received from an MCP server)mcpTools:= []openrouter.MCPTool{
{
Name: "read_file",
Description: "Read the contents of a file from the filesystem",
InputSchema: &openrouter.MCPInputSchema{
Type: "object",
Properties: map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "The path to the file to read",
},
},
Required: []string{"path"},
},
},
}
// Convert to OpenRouter formattools:=openrouter.ConvertMCPTools(mcpTools)
// Use with chat completionresponse, err:=client.ChatComplete(ctx, messages,
openrouter.WithModel("openai/gpt-4o"),
openrouter.WithTools(tools...),
)

Parsing MCP Tools from JSON

// Parse MCP tools from JSON (as received from an MCP server)mcpToolsJSON:= []byte(`[ { "name": "get_weather", "description": "Get the current weather for a location", "inputSchema": { "type": "object", "properties": { "location": {"type": "string", "description": "City name"} }, "required": ["location"] } }]`)
mcpTools, err:=openrouter.ParseMCPToolsFromJSON(mcpToolsJSON)
iferr!=nil {
log.Fatal(err)
}
// Convert and usetools:=openrouter.ConvertMCPTools(mcpTools)

Handling MCP Tool Results

// Convert MCP tool result to string for tool responsemcpResult:= openrouter.MCPToolResult{
Content: []openrouter.MCPContent{
{Type: "text", Text: "File contents: Hello, World!"},
},
}
resultStr:=openrouter.ConvertToolResultToMCP(mcpResult)
// Use in tool response messagemessages=append(messages, openrouter.Message{
Role: "tool",
Content: resultStr,
ToolCallID: toolCall.ID,
})

MCP Types

The library provides the following MCP types:

  • MCPTool: Represents an MCP tool definition (name, description, inputSchema)
  • MCPInputSchema: JSON Schema for tool parameters (type, properties, required)
  • MCPToolResult: Result from tool execution (content array, isError)
  • MCPContent: Content item in responses (type, text, data, mimeType)

Functions

  • ConvertMCPTool(mcpTool MCPTool) Tool - Convert single MCP tool
  • ConvertMCPTools(mcpTools []MCPTool) []Tool - Convert multiple tools
  • ConvertToolResultToMCP(result MCPToolResult) string - Convert result to string
  • ParseMCPToolFromJSON(data []byte) (MCPTool, error) - Parse single tool from JSON
  • ParseMCPToolsFromJSON(data []byte) ([]MCPTool, error) - Parse multiple tools from JSON

See the mcp-tools example for complete usage examples.

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 suffixresponse, 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 pluginwebPlugin:=openrouter.NewWebPlugin() // Uses defaults: auto engine, 5 resultsresponse, err:=client.ChatComplete(ctx,
openrouter.WithModel("openai/gpt-4o"),
openrouter.WithPlugins(webPlugin),
openrouter.WithMessages(messages),
)
// Custom web plugin configurationwebPlugin:=openrouter.NewWebPluginWithOptions(
openrouter.WebSearchEngineExa, // Force Exa search10, // 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 modelswebPlugin:= openrouter.Plugin{
ID: "web",
Engine: string(openrouter.WebSearchEngineNative),
}
// Force Exa search for all modelswebPlugin:= 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 responsecitations:=openrouter.ParseAnnotations(response.Choices[0].Message.Annotations)
for_, citation:=rangecitations {
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

Responses API [BETA]

⚠️WARNING: BETA API - EXPECT BREAKING CHANGES

The Responses API is in beta and may have breaking changes at any time. Do not rely on this API for production workloads. The API structure, parameters, and behavior may change without notice as OpenRouter continues to develop this feature.

For stable production use, consider using the Chat Completions API instead.

The library supports OpenRouter's new Responses API, which provides an OpenAI-compatible stateless API with enhanced capabilities including reasoning, tool calling, web search integration, and streaming.

Basic Usage

// Simple string inputresp, err:=client.CreateResponse(ctx, "What is 2+2?",
openrouter.WithResponsesModel("openai/gpt-4o-mini"),
openrouter.WithResponsesMaxOutputTokens(100),
)
iferr!=nil {
log.Fatal(err)
}
fmt.Println(resp.GetTextContent())

Structured Input

// Use structured input for multi-turn conversationsinput:= []openrouter.ResponsesInputItem{
openrouter.CreateResponsesSystemMessage("You are a helpful assistant."),
openrouter.CreateResponsesUserMessage("What is the capital of France?"),
}
resp, err:=client.CreateResponse(ctx, input,
openrouter.WithResponsesModel("openai/gpt-4o-mini"),
openrouter.WithResponsesMaxOutputTokens(200),
)

Reasoning

// Enable reasoning for complex problemsresp, err:=client.CreateResponse(ctx, "Solve this step by step: 15 * 17",
openrouter.WithResponsesModel("openai/o4-mini"),
openrouter.WithResponsesMaxOutputTokens(500),
openrouter.WithResponsesReasoningEffort(openrouter.ReasoningEffortMedium),
)
// Check for reasoning summaryifsummary:=resp.GetReasoningSummary(); len(summary) >0 {
fori, step:=rangesummary {
fmt.Printf("%d. %s\n", i+1, step)
}
}

Reasoning effort levels: ReasoningEffortMinimal, ReasoningEffortLow, ReasoningEffortMedium, ReasoningEffortHigh

Tool Calling

// Define tools using the flat ResponsesTool structure// Note: Responses API uses a different tool format than Chat Completions APIweatherTool:=openrouter.CreateResponsesTool(
"get_weather",
"Get weather for a location",
map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
"required": []string{"location"},
},
)
resp, err:=client.CreateResponse(ctx, "What's the weather in Tokyo?",
openrouter.WithResponsesModel("openai/gpt-4o-mini"),
openrouter.WithResponsesTools(weatherTool),
)
// Check for function callscalls:=resp.GetFunctionCalls()
iflen(calls) >0 {
for_, call:=rangecalls {
fmt.Printf("Function: %s, Args: %s\n", call.Name, call.Arguments)
}
}

Web Search

// Enable web search for real-time informationresp, err:=client.CreateResponse(ctx, "What are the latest AI news?",
openrouter.WithResponsesModel("openai/gpt-4o-mini"),
openrouter.WithResponsesWebSearch(3), // Get up to 3 search results
)
// Check for citationsannotations:=resp.GetAnnotations()
for_, ann:=rangeannotations {
ifann.Type=="url_citation" {
fmt.Printf("Source: %s\n", ann.URL)
}
}

Streaming

stream, err:=client.CreateResponseStream(ctx, "Write a haiku about programming.",
openrouter.WithResponsesModel("openai/gpt-4o-mini"),
openrouter.WithResponsesMaxOutputTokens(100),
)
iferr!=nil {
log.Fatal(err)
}
deferstream.Close()
varlastContentstringforevent:=rangestream.Events() {
content:=event.GetTextContent()
// Only print the new delta (GetTextContent returns cumulative content)iflen(content) >len(lastContent) {
fmt.Print(content[len(lastContent):])
lastContent=content
}
}
iferr:=stream.Err(); err!=nil {
log.Fatal(err)
}

Key Differences from Chat API

FeatureChat CompletionsResponses API
Endpoint/chat/completions/responses
InputArray of messagesString or structured array
Responsechoices arrayoutput array with typed items
ReasoningNot availableConfigurable effort levels
Web SearchVia :online suffixVia plugins parameter

See the responses example for complete usage examples.

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
  • responses/ - [BETA] Responses API examples with reasoning, tools, and streaming
  • advanced/ - Advanced features like rate limiting and custom configuration
  • videos/ - Submit, poll, and download a video generation job
  • tts/ - Create speech audio from text via /audio/speech
  • rerank/ - Rerank documents by relevance to a query
  • workspaces/ - Manage workspaces (Management API key required)
  • list-organization-members/ - List members of your organization
  • broadcast-webhook/ - Parse OTLP JSON payloads from the Broadcast webhook
  • oauth-pkce/ - Exchange an OAuth PKCE auth code for an API key

To run an example:

# Set your API keyexport 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
# Run responses API examples [BETA]
go run examples/responses/main.go
# Run workspaces example (requires a Management key)
go run examples/workspaces/main.go

Documentation

Task-indexed recipes live under docs/recipes/. A few pointers to the newer endpoints:

For detailed API documentation and usage examples, see DOCUMENTATION.md. Building agent code against the SDK? Start with AGENTS.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

Security policy

Stars

4 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages