ClientOptions.APIKeyEnv
Go · package github.com/muthuishere/toolnexus/golang · SPEC §8 · golang/client.go
type ClientOptions struct { // APIKey is sent as-is. Wins over everything else. APIKey string // APIKeyEnv is the NAME of the env var to read the key from (never the value). APIKeyEnv string // ...}The client decides which key goes to which host, in this order:
APIKeyset ⇒ only it.- Else
APIKeyEnvset ⇒ the env var it names. - Else only the env var that belongs to the
BaseURLhost, over https:api.openai.com→OPENAI_API_KEY,api.anthropic.com→ANTHROPIC_API_KEY,openrouter.ai(and subdomains) →OPENROUTER_API_KEY. - Any other host, or plain http, with neither set ⇒ the run fails with
no API key: set APIKey or APIKeyEnv …. Nothing is sent.
When to use it
Section titled “When to use it”Set APIKeyEnv whenever the BaseURL is not one of the three known hosts — a local llama.cpp or
vLLM server, a corporate gateway, a proxy — and the key lives in the environment. Set APIKey when
the host already has the value in hand (a secret manager, a test). For the three known hosts you
can set neither: the matching env var is read automatically.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”Each example uses a local httptest.Server or a capturing transport — no network, fake keys only.
1. The smallest useful call — a local server with APIKeyEnv
Section titled “1. The smallest useful call — a local server with APIKeyEnv”package main
import ( "context" "fmt" "log" "net/http" "net/http/httptest" "os"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { os.Setenv("MY_LOCAL_LLM_KEY", "YOUR_KEY_HERE")
var auth string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { auth = r.Header.Get("Authorization") w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":"hi"}}]}`)) })) defer srv.Close()
client := toolnexus.CreateClient(toolnexus.ClientOptions{ BaseURL: srv.URL, Style: toolnexus.StyleOpenAI, Model: "local", APIKeyEnv: "MY_LOCAL_LLM_KEY", // the NAME, not the value }) res, err := client.Run(context.Background(), "hello", nil) if err != nil { log.Fatal(err) } if auth != "Bearer YOUR_KEY_HERE" { log.Fatalf("unexpected Authorization header %q", auth) } fmt.Println("ok:", res.Text)}2. The realistic case — a local host with no key fails before sending
Section titled “2. The realistic case — a local host with no key fails before sending”A provider key in the environment is never sent to a host it does not belong to.
package main
import ( "context" "fmt" "log" "net/http" "net/http/httptest" "os" "strings"
toolnexus "github.com/muthuishere/toolnexus/golang")
func main() { os.Setenv("OPENAI_API_KEY", "YOUR_KEY_HERE")
hits := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { hits++ w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":"hi"}}]}`)) })) defer srv.Close()
client := toolnexus.CreateClient(toolnexus.ClientOptions{ BaseURL: srv.URL, Style: toolnexus.StyleOpenAI, Model: "local", }) _, err := client.Run(context.Background(), "hello", nil) if err == nil || !strings.Contains(err.Error(), "no API key") { log.Fatalf("expected a no-API-key error, got %v", err) } if hits != 0 { log.Fatal("the local server was called — OPENAI_API_KEY must never reach it") } fmt.Println("ok: refused,", hits, "requests sent")}3. The full surface — the host picks its own env var
Section titled “3. The full surface — the host picks its own env var”With several provider keys set, https://api.openai.com gets OPENAI_API_KEY and nothing else.
A capturing HTTPClient stands in for the network.
package main
import ( "context" "fmt" "io" "log" "net/http" "os" "strings"
toolnexus "github.com/muthuishere/toolnexus/golang")
type capture struct{ auth, host string }
func (c *capture) RoundTrip(r *http.Request) (*http.Response, error) { c.auth, c.host = r.Header.Get("Authorization"), r.URL.Host return &http.Response{ StatusCode: 200, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: io.NopCloser(strings.NewReader(`{"choices":[{"message":{"role":"assistant","content":"hi"}}]}`)), Request: r, }, nil}
func main() { os.Setenv("OPENROUTER_API_KEY", "YOUR_OPENROUTER_KEY_HERE") os.Setenv("OPENAI_API_KEY", "YOUR_OPENAI_KEY_HERE")
cap := &capture{} client := toolnexus.CreateClient(toolnexus.ClientOptions{ BaseURL: "https://api.openai.com/v1", Style: toolnexus.StyleOpenAI, Model: "gpt-4o-mini", HTTPClient: &http.Client{Transport: cap}, }) if _, err := client.Run(context.Background(), "hello", nil); err != nil { log.Fatal(err) } if cap.host != "api.openai.com" || cap.auth != "Bearer YOUR_OPENAI_KEY_HERE" { log.Fatalf("wrong key for host %s: %q", cap.host, cap.auth) } fmt.Println("ok:", cap.host, "got OPENAI_API_KEY only")}See also
Section titled “See also”CreateClient— The unified client: system prompt, skills injection, parallel and chained tool calls, retries, memory.ListModels— the same key rules apply when listing a provider’s models.Client.Run— Send a prompt, let the loop call tools until the model stops, get a RunResult.Hooks— Intercept before/after model calls and tool calls: audit, redact, veto, or rewrite.