# Models

The merged model catalog and how to extend it.

`--list-models` and `/model` show the merged catalog across every provider. Built-in entries cover Claude, GPT/Codex, Gemini/Gemma, Kimi/Moonshot, DeepSeek, Groq-hosted models, OpenRouter-routed models, Bedrock and Vertex model IDs, Azure deployments, Copilot models, and more.

For supported cloud discovery providers, zot also merges live IDs from their model-list endpoints, cached for six hours in `$ZOT_HOME/models-cache.json`. Speculative catalog entries are included too; they start working as soon as the upstream provider enables them.

## Continuous browser model picker

The browser client for [Continuous execution](/docs/continuous) uses the host's active catalog and host-side connections, not the browser's credentials or the website's public model catalog. Updated hosts offer models across configured providers, with provider names shown in each row. Search by provider name or model ID, select a model, then click Save in session settings to apply the provider and model together while retaining conversation history.

Selecting a model closes the dropdown, not session settings. Older hosts retain single-provider selection. Listing models does not execute stored API key commands or guarantee access under an upstream account's policies. The picker does not provide login flows or live model discovery controls.

## Copilot account availability

GitHub Copilot uses a separate authenticated `/models` query at startup to filter existing catalog entries by account availability. This is an in-memory filter, not a source of new catalog entries and not part of the six-hour disk cache. The website's model catalog remains a public list, not an account-specific availability check.

Discovery runs in the background. Before it succeeds, catalog entries remain visible; `--list-models` can exit before the account filter arrives. A failed refresh retains the previous snapshot, or the unfiltered catalog if there is no snapshot. Restart zot after changing accounts. Custom entries do not bypass GitHub's policies or the runtime picker filter. See [Providers](/docs/providers) for Copilot setup, model retirements, and plan exceptions.

## OpenRouter presets

When OpenRouter credentials are available, zot fetches the account's active presets and lists them in `/model` as `@preset/<slug>`. Presets are refreshed even while the public model cache is fresh, so account changes do not wait for the six-hour catalog refresh.

> **Preset capabilities**
>
> A preset can select one model, multiple fallback models, or change remotely. zot therefore treats its context window, output limit, and reasoning support as unknown instead of inventing values. Context percentages, percentage-based auto-compaction, and minimum-context compatibility checks are unavailable for preset entries. OpenRouter still applies the preset's own model and generation configuration when a request runs.

## When model IDs change

Provider catalogs move quickly. zot removes built-in entries when an upstream route is stale or unavailable, but a live-discovered ID can reappear automatically if your API key can see it. If a pinned model stops resolving, run `zot --list-models` or open `/model` and pick a currently available ID.

For custom or private deployments, keep the stable deployment name in `$ZOT_HOME/models.json` instead of depending on a speculative public catalog entry.

## Reasoning levels

Use `/reasoning` in the interactive TUI to set the reasoning level for subsequent model calls. The selector only shows distinct levels supported by the active model. Models without reasoning support only offer `off`. The current level is also shown in the model picker, and `--reasoning off|minimum|low|medium|high|xhigh|max` sets it from the command line.

Available levels are derived from the model's provider protocol and optional `reasoningLevelMap` overrides. The `max` tier is separate from `xhigh` and is available only where the active model can represent it or an explicit model override enables it. zot clamps a configured level to the nearest supported value when you switch models, so the selector and the provider request always use the same capability mapping.

For Copilot's Gemini and Kimi routes, zot does not send a reasoning-effort setting. These models can reason, but the endpoint manages that behavior. The selector's `off` value does not guarantee that server-side thinking is disabled.

## Custom models

Add custom models with `$ZOT_HOME/models.json`. User entries take precedence over both baked-in and live-discovered models. This file registers models but does not select a default. Save the `provider` and `model` selection in `$ZOT_HOME/config.json`, as shown in `Settings`.

```json title="$ZOT_HOME/models.json"
{
  "providers": {
    "openrouter": {
      "models": [
        {
          "id": "my-custom-model",
          "name": "My Custom Model",
          "reasoning": true,
          "reasoningLevelMap": { "minimum": "low", "max": "" },
          "contextWindow": 200000,
          "maxTokens": 8192
        }
      ]
    }
  }
}
```

Each top-level provider key is a zot provider id, for example `openai`, `anthropic`, `ollama`, `openrouter`, `groq`, `amazon-bedrock`, or `google-vertex`. Supported model fields are `id` (required), `name`, `reasoning`, `reasoningLevelMap`, `contextWindow`, `maxTokens`, `baseUrl`, `priceInput`, `priceOutput`, `priceCacheRead`, and `priceCacheWrite`.

`reasoningLevelMap` is optional. Protocol defaults apply when it is omitted. Use `minimum`, `low`, `medium`, `high`, `xhigh`, or `max` as keys for model-specific exceptions. Map a key to another level when the inputs are equivalent, use an identity mapping such as `"max": "max"` to enable a level beyond the protocol default, or map it to an empty string or `off` to remove that level. The effective mapping controls both `/reasoning` and provider requests.

## Custom providers

A top-level provider key that is not a built-in id defines a custom provider. Give it a provider-level `baseUrl` and an `api` wire format (`openai` for OpenAI-compatible Chat Completions, the default, `openai-responses` for the OpenAI Responses API, or `anthropic` for the Anthropic Messages API). All models under that key are first-class: they show up in `--list-models`, `/model`, and `/login`.

```json title="$ZOT_HOME/models.json"
{
  "providers": {
    "my-company": {
      "baseUrl": "https://llm.mycompany.com/v1",
      "api": "openai",
      "models": [
        {
          "id": "company-llm-v2",
          "name": "Company LLM v2",
          "contextWindow": 128000,
          "maxTokens": 32000
        },
        {
          "id": "company-llm-fast",
          "name": "Company LLM Fast",
          "baseUrl": "https://fast-llm.mycompany.com/v1"
        }
      ]
    }
  }
}
```

A model-level `baseUrl` overrides the provider-level `baseUrl` for that model, so a single custom provider can fan out across more than one endpoint. If `api` is omitted it defaults to `openai`; an unrecognized value falls back to `openai` with a warning.

> **Built-in models stay visible**
>
> Adding a `models.json` no longer hides the built-in catalog. Your custom providers and models are merged on top of the baked-in and live-discovered entries.

## Credentials for custom providers

`models.json` never stores secrets. For a custom provider, supply its API key through `/login`, the `--api-key` flag, a provider environment variable, or a command in `$ZOT_HOME/auth.json`. The env var is derived from the provider id in upper snake case, so `my-company` reads `MY_COMPANY_API_KEY` and `local-proxy` reads `LOCAL_PROXY_API_KEY`.

To retrieve the key for the `my-company` provider above from a password manager, add a matching entry to `$ZOT_HOME/auth.json`:

```json title="$ZOT_HOME/auth.json"
{
  "additional_api_key_creds": {
    "my-company": {
      "api_key_command": {
        "program": "op",
        "args": ["read", "op://Work/OpenAI/credential"],
        "timeout_ms": 120000
      }
    }
  }
}
```

The provider IDs in `models.json` and `auth.json` must match. `/login` also lists custom providers alongside the built-in cloud providers and stores their keys in `$ZOT_HOME/auth.json`. Because many self-hosted gateways do not expose a model-list endpoint, custom provider keys are accepted and saved without a verification probe; an invalid key surfaces on the first model call.

```shell
# With the command-backed key above
zot --provider my-company --model company-llm-v2

# Or use an environment variable
MY_COMPANY_API_KEY=sk-... zot --provider my-company --model company-llm-v2
```

## Live discovery for custom providers

A custom provider can list its models from an OpenAI-compatible `/models` endpoint instead of, or in addition to, a static `models` array. Set `"discover": true` at the provider level. Discovery is off by default, so existing configurations never gain network traffic, and it requires a provider-level `baseUrl`. A `discover` flag without one is ignored with a warning.

```json title="$ZOT_HOME/models.json"
{
  "providers": {
    "m4": {
      "baseUrl": "http://127.0.0.1:48000/v1",
      "discover": true
    },
    "m5": {
      "baseUrl": "http://127.0.0.1:48001/v1",
      "discover": true,
      "models": [
        { "id": "big-model", "contextWindow": 200000, "maxTokens": 16000 }
      ]
    }
  }
}
```

zot requests `<baseUrl>/models`, or `<baseUrl>/v1/models` when the base URL has no trailing version segment, with a 3 second timeout. It refreshes in the background on startup, when `/model` opens, and when launching with a discovered model that is not yet in the catalog. The API key from the environment or `auth.json` is sent as a bearer token when present. A missing key is not an error because local servers commonly accept unauthenticated requests. Background refresh never executes an `api_key_command`; opening `/model` does.

Only model IDs are read from the response. Discovered models default to a 32,768 token context and 4,096 output tokens, have zero pricing, and no reasoning controls. IDs containing `embed` are skipped. Entries in the provider's `models` array override discovered metadata for the same ID, which is how to raise limits or enable reasoning for a specific model.

The discovered list stays in memory, separate from the six-hour cloud model cache. A failed refresh keeps the previous snapshot for that provider and `/model` shows the error while other providers stay usable. A persisted selection of a discovered model survives restarts even though the model is not in `models.json`. The `/models` wire format is assumed to be OpenAI style ([[{"data": [{"id": ...}]}]]) regardless of the provider's `api` setting.

> **Credential still required for listing**
>
> As with every custom provider, `/model` only lists it once a credential exists. For a keyless local server, store any placeholder value through `/login` or the derived environment variable. `--list-models` prints the catalog before the background refresh completes, so discovered models may be missing there.

> **Requires a zot build with custom provider discovery**
>
> Older builds ignore the `discover` key and only use the static `models` array.

## LM Studio discovery and metadata

Register a server URL through `/login` to enable discovery for the `lmstudio` provider. See [Providers](/docs/providers) for setup and the build requirement. Without a registered URL, zot does not query LM Studio. This local catalog is in-memory only, not part of the six-hour disk cache.

- Native discovery excludes entries explicitly marked as embedding models. The `/v1/models` fallback only supplies IDs, so embedding filtering is best-effort.
- Loaded models use their instance IDs and configured context lengths. Unloaded models use their keys and advertised maximum context lengths.
- Quantization-specific keys are preserved. Zot does not expand the native API's separate variant list into additional choices.
- Missing context metadata defaults to 32,768 tokens. Output defaults to at most 4,096 tokens, or a quarter of the context window for smaller models. These values are estimates, not server guarantees.
- Discovered models have zero API pricing. Token usage still comes from inference responses, and hardware or electricity costs are not estimated.
- Reasoning controls are not enabled automatically from LM Studio's capability metadata. This does not mean server-side reasoning is disabled.

Override metadata under `lmstudio` in `$ZOT_HOME/models.json`. User entries take precedence over discovered entries. For an unloaded model, set the context window to the limit LM Studio will actually load it with if that is smaller than its advertised maximum:

```json title="$ZOT_HOME/models.json"
{
  "providers": {
    "lmstudio": {
      "models": [
        {
          "id": "my-local-model",
          "contextWindow": 32768,
          "maxTokens": 4096
        }
      ]
    }
  }
}
```

This file only overrides model metadata. The registered server URL and optional API key remain in `$ZOT_HOME/auth.json`. Discovery does not change your selected model, the coding workflow, or built-in pricing for other providers.

## Local and OpenAI-compatible endpoints

For Ollama and generic single-model servers that speak OpenAI chat completions (vLLM, LocalAI, a single-model llama.cpp server, or a private gateway), use the `ollama` provider and set `baseUrl` when the endpoint is not zot's default local Ollama URL. This provides inference only and remains available for manual LM Studio configuration on older zot builds. For automatic LM Studio discovery, register the distinct `lmstudio` provider through `/login`. For llama.cpp router management, configure the distinct `llama.cpp` provider through `/login` and use `/llama`.

```json title="$ZOT_HOME/models.json"
{
  "providers": {
    "ollama": {
      "models": [
        {
          "id": "my-local-model",
          "name": "My Local Model",
          "baseUrl": "http://localhost:1234/v1",
          "contextWindow": 32768,
          "maxTokens": 8192
        }
      ]
    }
  }
}
```

Then pick it with `/model` or run it directly:

```shell
zot --provider ollama --model my-local-model
```

## Save a remote Ollama endpoint

For one run against a remote Ollama server:

```shell
zot --provider ollama --model qwen3.5:4b --base-url http://ollama-server:11434/v1
```

To persist that endpoint, add this model to `$ZOT_HOME/models.json`:

```json title="$ZOT_HOME/models.json"
{
  "providers": {
    "ollama": {
      "models": [
        {
          "id": "qwen3.5:4b",
          "baseUrl": "http://ollama-server:11434/v1",
          "contextWindow": 32768,
          "maxTokens": 8192
        }
      ]
    }
  }
}
```

Select it by default with these fields in `$ZOT_HOME/config.json`:

```json title="$ZOT_HOME/config.json"
{
  "provider": "ollama",
  "model": "qwen3.5:4b"
}
```

The model ID, including its tag, must match a model installed on that server. `--base-url` overrides the model's endpoint for one run. Set `contextWindow` and `maxTokens` to limits the model and server configuration actually support. These fields inform zot's budgets, they do not configure Ollama's context size or install models.

Only connect to trusted servers. Do not expose an unauthenticated Ollama endpoint to the public internet. Use HTTPS for remote endpoints that require a token.

## Same Ollama model on two hosts

Catalog identity is the pair `(provider, model ID)`. Repeating the same ID under `ollama` does not create separately selectable hosts, even with different display names. Use a distinct custom provider ID for each endpoint and keep the actual Ollama model ID unchanged:

```json title="$ZOT_HOME/models.json"
{
  "providers": {
    "ollama-home": {
      "baseUrl": "http://localhost:11434/v1",
      "api": "openai",
      "discover": true,
      "models": [
        { "id": "qwen3.5:4b", "contextWindow": 32768, "maxTokens": 8192 }
      ]
    },
    "ollama-server": {
      "baseUrl": "http://ollama-server:11434/v1",
      "api": "openai",
      "discover": true,
      "models": [
        { "id": "qwen3.5:4b", "contextWindow": 32768, "maxTokens": 8192 }
      ]
    }
  }
}
```

Select either host:

```shell
zot --provider ollama-home --model qwen3.5:4b
zot --provider ollama-server --model qwen3.5:4b
```

To use the second host by default, set `provider` to `ollama-server` and `model` to `qwen3.5:4b` in `config.json`. Discovery-enabled custom providers can run against unauthenticated servers without a key. For picker visibility, see the credential note under Live discovery for custom providers above.

`discover` lists models already installed on the server. It does not download models into Ollama. Static model entries are optional with discovery enabled, but let you override the discovered metadata defaults. The built-in `ollama` provider has no dedicated `/login` endpoint-setup flow or automatic discovery. Use explicit model IDs, static entries, or discovery-enabled custom providers instead.

## API keys for custom local endpoints

`models.json` does not store credentials. It only describes models and their optional `baseUrl`. Named custom providers can read keys from `--api-key`, provider environment variables, or `$ZOT_HOME/auth.json` written by `/login`. The built-in `ollama` provider uses `--api-key` or a placeholder credential, not a saved Ollama key from `auth.json`.

If your local or private OpenAI-compatible endpoint requires a bearer token, pass it at launch:

```shell
zot --provider ollama \
  --model my-local-model \
  --base-url http://localhost:1234/v1 \
  --api-key "$LOCAL_API_KEY"
```

> **Naming a custom provider**
>
> Use the `ollama` provider only for OpenAI-compatible local servers without their own credentials. For an authenticated gateway, prefer a named custom provider (see above) so its key works through `/login` and the matching environment variable instead of requiring `--api-key` on every launch.

If a private endpoint uses a self-signed TLS certificate, add `--insecure` to skip certificate verification. It applies to the explicit `--base-url` endpoint, to a `baseUrl` defined for a user model in `models.json`, and to models discovered from a custom provider's `baseUrl`, so built-in providers, `/login` auth, and model discovery requests keep normal TLS verification.

```shell
zot --provider ollama \
  --model my-local-model \
  --base-url https://my-llm.internal/v1 \
  --api-key "$LOCAL_API_KEY" \
  --insecure
```

For unauthenticated Ollama on the default URL, no key is needed:

```shell
ollama pull qwen3.5:4b
zot --provider ollama --model qwen3.5:4b
```
