# How connection_tester.py Validates AI Provider Credentials with Minimal API Calls

> Learn how connection_tester.py validates AI provider credentials efficiently with a single GET request, bypassing costly inference and minimizing API calls.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: how-to-guide
- Published: 2026-06-06

---

**[`connection_tester.py`](https://github.com/lfnovo/open-notebook/blob/main/connection_tester.py) proves API keys and endpoints by sending a single lightweight GET request to each provider's model-listing endpoint, skipping expensive inference entirely.**

Open Notebook is an open-source project that unifies multiple AI providers behind a single interface. In [`open_notebook/ai/connection_tester.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/connection_tester.py), the application validates credentials using minimal API calls before any costly inference runs. This design keeps credential checks fast, bandwidth-light, and free of per-token charges while still guaranteeing that the supplied configuration actually works.

## Why Minimal API Calls Are Used for Credential Validation

Full chat-completion or embedding requests consume tokens and add latency. Instead, the module issues the smallest possible HTTP request that still requires authentication: listing available models. A successful response proves the endpoint is reachable and the key is valid, while a failure surfaces configuration errors immediately.

## Provider-Specific Validation Methods in connection_tester.py

### Azure OpenAI

For Azure, `_test_azure_connection` (lines 40-71) sends a GET request to `{endpoint}/openai/models?api-version={version}`. This endpoint is authenticated but stateless, so a `200` response confirms both the endpoint and API key are functional. If the caller omits values, the function derives defaults from `AZURE_*` environment variables (lines 51-54).

### Ollama

The `_test_ollama_connection` helper (lines 97-113) hits `{base_url}/api/tags`. Because this route returns the catalog of locally available models, a successful `200` proves the Ollama server is running and reachable without executing inference.

### OpenAI-Compatible Servers

For generic OpenAI-compatible backends, `_test_openai_compatible_connection` (lines 32-45) calls GET `{base_url}/models`, optionally injecting an `Authorization: Bearer` header. A `200` response validates the custom base URL and token without running a model.

### Generic Providers

Major hosted providers such as OpenAI, Anthropic, Google, and Groq are **not** validated via an early network call in this module. Instead, [`connection_tester.py`](https://github.com/lfnovo/open-notebook/blob/main/connection_tester.py) defers to the provider-specific `ModelManager`—defined in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py)—when `test_individual_model` is invoked later in the lifecycle.

## Request Flow and Response Interpretation

The validation sequence in [`open_notebook/ai/connection_tester.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/connection_tester.py) follows a strict five-step pattern:

1. **Select the helper** based on the provider name, such as `azure`, `ollama`, or `openai_compatible`.
2. **Derive defaults** from environment variables when the caller supplies empty values.
3. **Issue a single GET request** through `httpx.AsyncClient` with a sharp 10-second timeout.
4. **Interpret the status code:**
   - `200` → success; the function extracts a short list of model names for a friendly confirmation message.
   - `401` → returned as "Invalid API key".
   - `403` → returned as "API key lacks required permissions".
   - Other codes → mapped to a generic failure message.
5. **Return a `(bool, str)` tuple** representing success and a human-readable message that [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) propagates to the client.

## Error Handling Without Noise

All three network helpers wrap their `httpx` calls in exception handlers for `httpx.ConnectError`, `httpx.TimeoutException`, and generic `Exception`. Raw errors are funneled through `_normalize_error_message` (lines 36-53) so the UI receives clean, actionable text rather than stack traces.

## Practical Code Examples

Validate Azure OpenAI credentials directly or let the helper read environment variables:

```python
from open_notebook.ai.connection_tester import _test_azure_connection

ok, msg = await _test_azure_connection(
    endpoint="https://my-azure.openai.azure.com",
    api_key="sk-xxxx",
    api_version="2024-10-21",
)
print(ok, msg)   # → True "Connected. 5 models: gpt-35-turbo, …"

```

Check a local Ollama server:

```python
from open_notebook.ai.connection_tester import _test_ollama_connection

ok, msg = await _test_ollama_connection("http://localhost:11434")
print(ok, msg)   # → True "Connected. 12 models available: llama, …"

```

Verify an OpenAI-compatible endpoint such as Together or another proxy:

```python
from open_notebook.ai.connection_tester import _test_openai_compatible_connection

ok, msg = await _test_openai_compatible_connection(
    base_url="https://api.together.xyz",
    api_key="together-xxxx",
)
print(ok, msg)   # → True "Connected. 8 models available: meta-llama/…"

```

## Summary

- [`open_notebook/ai/connection_tester.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/connection_tester.py) validates credentials by sending one lightweight GET request per provider instead of running inference.
- Azure, Ollama, and OpenAI-compatible servers each expose a model-listing endpoint that proves authentication without consuming tokens.
- Hosted generic providers skip this phase and rely on `ModelManager` in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) for later per-model testing.
- Every helper uses `httpx.AsyncClient` with a 10-second timeout and normalizes errors through `_normalize_error_message`.
- The calling router in [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) receives a simple `(bool, str)` tuple and surfaces it to the user.

## Frequently Asked Questions

### What HTTP method does connection_tester.py use to validate credentials?

The module exclusively uses GET requests to read-only model-listing endpoints. This avoids side effects, token consumption, and latency while still requiring valid authentication.

### Why doesn't the module validate generic providers like Anthropic or Google with a network call?

[`connection_tester.py`](https://github.com/lfnovo/open-notebook/blob/main/connection_tester.py) intentionally defers validation for generic hosted providers to the provider-specific `ModelManager` class in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py). The `test_individual_model` method handles those checks later, keeping the initial credential endpoint lightweight and provider-agnostic.

### How does the validation flow handle timeouts or unreachable endpoints?

Each helper catches `httpx.ConnectError` and `httpx.TimeoutException` alongside generic exceptions. These are passed through `_normalize_error_message` (lines 36-53) to produce clean user-facing strings instead of raw tracebacks.

### Which Open Notebook API routes consume connection_tester.py?

The primary consumer is [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py), which exposes a credentials endpoint that invokes the tester. [`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py) wraps the result and translates failures into appropriate HTTP errors before returning them to the client.