api_key_env
Python · package toolnexus · SPEC §8 · python/src/toolnexus/client.py
create_client( *, base_url: str, style: ClientStyle, model: str, api_key: str | None = None, # the key itself — used as-is, always wins api_key_env: str | None = None, # the NAME of an env var to read the key from ...,) -> Client
# list_models(base_url=..., api_key=..., api_key_env=...) follows the same rule.Which key the client sends, and to whom, in this order:
api_keyset ⇒ exactly that key.- else
api_key_envset ⇒ the value of the env var with that name. - else ONLY the env var that belongs to the
base_urlhost, and only over https:api.openai.com→OPENAI_API_KEY,api.anthropic.com→ANTHROPIC_API_KEY,openrouter.ai(and subdomains) →OPENROUTER_API_KEY. - anything else — a local server, a proxy, plain
http://— raisesRuntimeError("No API key: set api_key or api_key_env …")on the first request.
There is no cross-provider fallback: an OpenRouter key is never sent to api.openai.com, and no
provider key is ever sent to a host you happen to point base_url at. The key’s value never
appears in an error or a log line.
When to use it
Section titled “When to use it”- You point the client at a local model server (llama.cpp, Ollama, vLLM) or a corporate proxy:
pass
api_key(any placeholder the server accepts) orapi_key_env="MY_PROXY_KEY". - Your key lives in an env var whose name is not the provider default (for example per-tenant
keys):
api_key_envnames it, so the value never passes through your code.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. The smallest useful call — a local server with an explicit key
Section titled “1. The smallest useful call — a local server with an explicit key”import asyncio
from toolnexus import create_client, create_toolkit
class Recorder: def __init__(self): self.headers = []
def post(self, url, headers, payload, timeout): self.headers.append(headers) return {"choices": [{"message": {"role": "assistant", "content": "ok"}}]}
async def main(): tk = await create_toolkit(builtins=False) rec = Recorder() client = create_client( base_url="http://localhost:8080/v1", style="openai", model="local-model", api_key="YOUR_KEY_HERE", http_transport=rec, ) res = await client.run("hi", tk) assert res.text == "ok" assert rec.headers[0]["Authorization"] == "Bearer YOUR_KEY_HERE" print("ok: explicit key sent to the local server")
asyncio.run(main())2. The realistic case — a proxy key read from a named env var
Section titled “2. The realistic case — a proxy key read from a named env var”import asyncioimport os
from toolnexus import create_client, create_toolkit
class Recorder: def __init__(self): self.headers = []
def post(self, url, headers, payload, timeout): self.headers.append(headers) return {"choices": [{"message": {"role": "assistant", "content": "ok"}}]}
async def main(): os.environ["MY_PROXY_KEY"] = "YOUR_KEY_HERE" # set by your deployment, not your code tk = await create_toolkit(builtins=False) rec = Recorder() client = create_client( base_url="https://llm-proxy.internal.example/v1", style="openai", model="gpt-4o-mini", api_key_env="MY_PROXY_KEY", http_transport=rec, ) await client.run("hi", tk) assert rec.headers[0]["Authorization"] == "Bearer YOUR_KEY_HERE" print("ok: api_key_env read at request time")
asyncio.run(main())3. The full surface — a provider key is never sent to a host it does not belong to
Section titled “3. The full surface — a provider key is never sent to a host it does not belong to”import asyncioimport os
from toolnexus import create_client, create_toolkit
class Recorder: def __init__(self): self.calls = 0
def post(self, url, headers, payload, timeout): self.calls += 1 return {"choices": [{"message": {"role": "assistant", "content": "ok"}}]}
async def main(): os.environ["OPENAI_API_KEY"] = "YOUR_KEY_HERE" tk = await create_toolkit(builtins=False) rec = Recorder() client = create_client( base_url="http://localhost:8080/v1", style="openai", model="local-model", http_transport=rec, retries=0, ) try: await client.run("hi", tk) raise AssertionError("expected No API key") except RuntimeError as e: assert "No API key" in str(e) and "YOUR_KEY_HERE" not in str(e) assert rec.calls == 0 # nothing was sent print("ok: OPENAI_API_KEY was not sent to localhost")
asyncio.run(main())See also
Section titled “See also”create_client— The unified client: system prompt, skills injection, parallel and chained tool calls, retries, memory.list_models— List a provider’s models under the same key rules.Client.run— Send a prompt, let the loop call tools until the model stops, get a RunResult.