Skip to content

reVrost/go-openrouter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

98 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go Openrouter

Go Reference Go Report Card codecov

This library provides unofficial Go client for Openrouter API

Installation

go get github.com/revrost/go-openrouter

Getting an Openrouter API Key:

  1. Visit the openrouter website at https://openrouter.ai/docs/quick-start.
  2. If you don't have an account, click on "Sign Up" to create one. If you do, click "Log In".
  3. Once logged in, navigate to your API key management page.
  4. Click on "Create new secret key".
  5. Enter a name for your new key, then click "Create secret key".
  6. 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

Features

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

Usage

Chat completion

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)
}

Streaming chat completion

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))
	}
}

Chat completion with model fallback

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 Required
  • 408 Request Timeout
  • 429 Too Many Requests
  • 500 Internal Server Error
  • 502 Bad Gateway
  • 503 Service Unavailable
  • 504 Gateway Timeout
  • 524 Infrastructure Timeout
  • 529 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.

Response caching

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()

Audio speech and transcription

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)

Other examples:

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))
	}
}
More examples in `examples/` folder.

Frequently Asked Questions

Contributing

Contributing Guidelines, we hope to see your contributions!

Sponsor this project

Packages

 
 
 

Contributors

Languages