This library provides unofficial Go client for Openrouter API
go get github.com/revrost/go-openrouter
- Visit the openrouter website at https://openrouter.ai/docs/quick-start.
- If you don't have an account, click on "Sign Up" to create one. If you do, click "Log In".
- Once logged in, navigate to your API key management page.
- Click on "Create new secret key".
- Enter a name for your new key, then click "Create secret key".
- Your new API key will be displayed. Use this key to interact with the openrouter API.
Note: Your API key is sensitive information. Do not share it with anyone.
For deepseek models, sometimes its better to use openrouter integration feature and pass in your own API key into the control panel for better performance, as openrouter will use your API key to make requests to the underlying model which potentially avoids shared rate limits.
⚡BYOK (Bring your own keys) gets 1 million free requests per month! https://openrouter.ai/announcements/1-million-free-byok-requests-per-month
https://openrouter.ai/docs/api-reference/overview
- Chat Completion
- Completion
- Streaming
- Embeddings
- Reasoning
- Tool calling
- Structured outputs
- Prompt caching
- Response caching
- Web search
- Multimodal [Images, PDFs, Audio]
- Text-to-speech
- Speech-to-text transcription
- Usage fields
package main
import (
"context"
"fmt"
openrouter "github.com/revrost/go-openrouter"
)
func main() {
client := openrouter.NewClient(
"your token",
openrouter.WithXTitle("My App"),
openrouter.WithHTTPReferer("https://myapp.com"),
)
resp, err := client.CreateChatCompletion(
context.Background(),
openrouter.ChatCompletionRequest{
Model: "deepseek/deepseek-chat-v3.1:free",
Messages: []openrouter.ChatCompletionMessage{
openrouter.UserMessage("Hello!"),
},
},
)
if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return
}
fmt.Println(resp.Choices[0].Message.Content)
}func main() {
ctx := context.Background()
client := openrouter.NewClient(os.Getenv("OPENROUTER_API_KEY"))
stream, err := client.CreateChatCompletionStream(
context.Background(), openrouter.ChatCompletionRequest{
Model: "qwen/qwen3-235b-a22b-07-25:free",
Messages: []openrouter.ChatCompletionMessage{
openrouter.UserMessage("Hello, how are you?"),
},
Stream: true,
},
)
require.NoError(t, err)
defer stream.Close()
for {
response, err := stream.Recv()
if err != nil && err != io.EOF {
require.NoError(t, err)
}
if errors.Is(err, io.EOF) {
fmt.Println("EOF, stream finished")
return
}
json, err := json.MarshalIndent(response, "", " ")
require.NoError(t, err)
fmt.Println(string(json))
}
}Use CreateChatCompletionWithFallback when you want the client to try a backup
model if OpenRouter returns a fallbackable error for the primary model.
The fallback decision is handled by the library. You only provide fallback models, in the order they should be tried.
package main
import (
"context"
"fmt"
"os"
openrouter "github.com/revrost/go-openrouter"
)
func main() {
ctx := context.Background()
client := openrouter.NewClient(os.Getenv("OPENROUTER_API_KEY"))
resp, err := client.CreateChatCompletionWithFallback(
ctx,
openrouter.ChatCompletionRequest{
Model: "deepseek/deepseek-v4-flash",
Messages: []openrouter.ChatCompletionMessage{
openrouter.UserMessage("Summarize today's market news in one paragraph."),
},
},
"xiaomi/mimo-v2-flash",
)
if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return
}
fmt.Println(resp.Choices[0].Message.Content)
}By default, chat completion fallback is triggered for these OpenRouter error codes, based on the documented chat completion errors in OpenRouter's OpenAPI spec:
402 Payment Required408 Request Timeout429 Too Many Requests500 Internal Server Error502 Bad Gateway503 Service Unavailable504 Gateway Timeout524 Infrastructure Timeout529 Provider Overloaded
The client checks both the HTTP status code and the OpenRouter API error code,
because provider errors can surface the useful code in the JSON error body.
Fallback is not triggered for request or auth errors such as 400, 401,
404, 413, or 422.
For streaming, fallback can only happen before a stream is returned:
stream, err := client.CreateChatCompletionStreamWithFallback(
ctx,
openrouter.ChatCompletionRequest{
Model: "deepseek/deepseek-v4-flash",
Messages: []openrouter.ChatCompletionMessage{
openrouter.UserMessage("Write a short investor update."),
},
},
"xiaomi/mimo-v2-flash",
)
if err != nil {
fmt.Printf("ChatCompletionStream error: %v\n", err)
return
}
defer stream.Close()If you need custom fallback rules, use the policy API:
resp, err := client.CreateChatCompletionWithFallbackPolicy(
ctx,
request,
openrouter.ChatCompletionFallbackPolicy{
Models: []string{"anthropic/claude-sonnet-4.5"},
ErrorCodes: []int{402, 429},
},
)DefaultChatCompletionFallbackErrorCodes returns a copy of the library default
code list if you want to inspect or extend it.
OpenRouter response caching can be enabled per request for supported endpoint requests, including chat completions, streaming chat completions, and embeddings. This client also forwards the same headers on legacy completion requests for compatibility. Cache options are sent as HTTP headers and are not included in the JSON request body.
enabled := true
ttl := 3600
resp, err := client.CreateChatCompletion(ctx, openrouter.ChatCompletionRequest{
Model: "openai/gpt-4o-mini",
Messages: []openrouter.ChatCompletionMessage{
openrouter.UserMessage("Summarize this transcript."),
},
ResponseCache: &openrouter.ResponseCacheConfig{
Enabled: &enabled,
TTLSeconds: &ttl,
},
})
if err != nil {
fmt.Printf("ChatCompletion error: %v\n", err)
return
}
if resp.ResponseCache != nil && resp.ResponseCache.Status == openrouter.ResponseCacheStatusHit {
fmt.Printf("cache hit, age=%d seconds\n", *resp.ResponseCache.AgeSeconds)
}Use Clear: true to send X-OpenRouter-Cache-Clear: true for a request:
resp, err := client.CreateEmbeddings(ctx, openrouter.EmbeddingsRequest{
Model: "openai/text-embedding-3-small",
Input: "hello",
ResponseCache: &openrouter.ResponseCacheConfig{
Clear: true,
},
})Streaming responses expose cache metadata from the initial response headers:
stream, err := client.CreateChatCompletionStream(ctx, openrouter.ChatCompletionRequest{
Model: "openai/gpt-4o-mini",
Messages: []openrouter.ChatCompletionMessage{
openrouter.UserMessage("Write a short update."),
},
ResponseCache: &openrouter.ResponseCacheConfig{
Enabled: &enabled,
},
})
if err != nil {
fmt.Printf("ChatCompletionStream error: %v\n", err)
return
}
defer stream.Close()
metadata := stream.ResponseCacheMetadata()Use CreateSpeech for OpenRouter's dedicated text-to-speech endpoint. The
response is raw audio bytes, so a small example can write them directly to a
file.
speech, err := client.CreateSpeech(ctx, openrouter.SpeechRequest{
Model: "elevenlabs/eleven-turbo-v2",
Input: "Hello from go-openrouter.",
Voice: "alloy",
ResponseFormat: openrouter.SpeechResponseFormatMp3,
})
if err != nil {
fmt.Printf("CreateSpeech error: %v\n", err)
return
}
if err := os.WriteFile("speech.mp3", speech.Audio, 0o644); err != nil {
fmt.Printf("write speech file error: %v\n", err)
return
}Use CreateTranscription for speech-to-text. Audio input must be base64
encoded; NewTranscriptionInputAudioFromFile handles that for local files.
inputAudio, err := openrouter.NewTranscriptionInputAudioFromFile("meeting.wav")
if err != nil {
fmt.Printf("audio error: %v\n", err)
return
}
transcription, err := client.CreateTranscription(ctx, openrouter.TranscriptionRequest{
Model: "openai/whisper-large-v3",
InputAudio: inputAudio,
Language: "en",
})
if err != nil {
fmt.Printf("CreateTranscription error: %v\n", err)
return
}
fmt.Println(transcription.Text)JSON Schema for function calling
{
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["location"]
}
}Using the jsonschema package, this schema could be created using structs as such:
FunctionDefinition{
Name: "get_current_weather",
Parameters: jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"location": {
Type: jsonschema.String,
Description: "The city and state, e.g. San Francisco, CA",
},
"unit": {
Type: jsonschema.String,
Enum: []string{"celsius", "fahrenheit"},
},
},
Required: []string{"location"},
},
}The Parameters field of a FunctionDefinition can accept either of the above styles, or even a nested struct from another library (as long as it can be marshalled into JSON).
Structured Outputs
func main() {
ctx := context.Background()
client := openrouter.NewClient(os.Getenv("OPENROUTER_API_KEY"))
type Result struct {
Location string `json:"location"`
Temperature float64 `json:"temperature"`
Condition string `json:"condition"`
}
var result Result
schema, err := jsonschema.GenerateSchemaForType(result)
if err != nil {
log.Fatalf("GenerateSchemaForType error: %v", err)
}
request := openrouter.ChatCompletionRequest{
Model: openrouter.DeepseekV3,
Messages: []openrouter.ChatCompletionMessage{
{
Role: openrouter.ChatMessageRoleUser,
Content: openrouter.Content{Text: "What's the weather like in London?"},
},
},
ResponseFormat: &openrouter.ChatCompletionResponseFormat{
Type: openrouter.ChatCompletionResponseFormatTypeJSONSchema,
JSONSchema: &openrouter.ChatCompletionResponseFormatJSONSchema{
Name: "weather",
Schema: schema,
Strict: true,
},
},
}
pj, _ := json.MarshalIndent(request, "", "\t")
fmt.Printf("request :\n %s\n", string(pj))
res, err := client.CreateChatCompletion(ctx, request)
if err != nil {
fmt.Println("error", err)
} else {
b, _ := json.MarshalIndent(res, "", "\t")
fmt.Printf("response :\n %s", string(b))
}
}Contributing Guidelines, we hope to see your contributions!