Skip to content

Connect external providers

hal0 can route requests to external upstreams — third-party OpenAI-compatible APIs like OpenAI, Anthropic, OpenRouter, Google AI Studio, Ollama, or any custom endpoint. You wire them up by editing /etc/hal0/upstreams.toml (the source of truth) and then writing the credential and testing reachability through the API or dashboard.

The integration catalog lists the known provider templates the “add upstream” form draws from:

Terminal window
curl http://localhost:8080/api/providers/catalog

It includes OpenAI, Anthropic, OpenRouter, Google AI Studio, Ollama, and a generic custom OpenAI-compatible entry, each with its default base URL and auth style.

Add a [[upstream]] block to /etc/hal0/upstreams.toml:

[[upstream]]
name = "openrouter"
kind = "remote"
url = "https://openrouter.ai/api/v1"
auth_style = "bearer"
auth_value_env = "OPENROUTER_API_KEY"
timeout_seconds = 300.0
advertise_models = true

The fields:

  1. name — a unique id you’ll reference everywhere else.

  2. kindremote for an external provider (slot is reserved for local container slots, which register themselves).

  3. url — the base URL (required, non-empty).

  4. auth_style — how the key is sent. Hand-written upstreams.toml entries use bearer, header, or none. The built-in cloud presets add two more, applied automatically when you pick them: anthropic (x-api-key + anthropic-version, for Anthropic) and google_query (?key= on the URL, for Google AI Studio).

  5. auth_value_env — the name of the environment variable that holds the secret. The key itself is never written to TOML — only the env-var name lives here.

  6. timeout_seconds — request timeout (default 300).

  7. advertise_models — whether this upstream’s models appear in the aggregated model list (default true).

The secret lives in /etc/hal0/api.env (a systemd EnvironmentFile), not in the TOML. Write it through the API, which binds the credential to the upstream’s declared auth_value_env so you can’t accidentally write a key the upstream won’t read:

Terminal window
curl -X POST http://localhost:8080/api/providers/openrouter/credentials \
-H 'content-type: application/json' \
-d '{"key":"OPENROUTER_API_KEY","value":"sk-or-..."}'

The key must match the upstream’s auth_value_env exactly and be a valid ALL_CAPS env-var name. The endpoint writes the value atomically to api.env with 0600 permissions, updates the running process environment so the change takes effect immediately, and never echoes the secret back — the response carries "value": "***REDACTED***".

Terminal window
# remote providers only
curl http://localhost:8080/api/providers
# all upstreams (remote + local slots)
curl http://localhost:8080/api/upstreams
# one upstream
curl http://localhost:8080/api/upstreams/openrouter

Each entry reports auth_configured (whether the env-var is set) but never the secret value.

Terminal window
curl -X POST http://localhost:8080/api/upstreams/openrouter/test

This probes the upstream’s /models endpoint (appended to the configured base URL) and returns a reachability report: {ok, status, latency_ms, models_count, error?}. An unhealthy result also fires a dashboard footer event so the outage is visible. The dashboard’s Providers tab exposes the same probe behind a “test connection” button.

Credential writes through the API apply to the running process immediately. Edits to upstreams.toml (adding, removing, or retiming an upstream) are read at startup — restart hal0-api or reload upstreams to pick them up. The credential-write response includes that hint.