# Models and providers

> Providers, wire dialects, key lookup, default and auxiliary model, effort, retries and failover.

Source: https://nodecode.ai/docs/models
Updated: 2026-10-06

A provider is an endpoint, a key and the wire dialect it speaks. Providers live in `~/.nodecode/config.jsonc`.

## Providers

```jsonc
"providers": {
  "anthropic": { "sdk": "anthropic", "api_key": "sk-ant-..." },
  "local": {
    "sdk": "openai-completions",
    "base_url": "http://127.0.0.1:8000/v1",
    "models": [{ "id": "qwen3-27b" }]
  }
}
```

Every provider key is listed in [Config](https://nodecode.ai/docs/config). `/connect` saves a key for a provider from the models.dev catalog.

## Dialects

`sdk` names the wire dialect. Nodecode never probes the endpoint for it.

| `sdk` | Path added to `base_url` | Default endpoint |
| --- | --- | --- |
| `openai-completions` | `/chat/completions` | `https://opencode.ai/zen/v1/chat/completions` |
| `openai-responses` | `/responses` | `https://api.openai.com/v1/responses` |
| `anthropic` | `/messages` | `https://api.anthropic.com/v1/messages` |
| `google` | Set per model | `https://generativelanguage.googleapis.com/v1beta` |

| Case | Dialect used |
| --- | --- |
| `sdk` is set | That dialect. An unknown value leaves the provider unusable. |
| No `sdk`, the id is a dialect name | That dialect. Example: `"anthropic": {}`. |
| No `sdk`, any other id | Refused. The refusal names the key to set. |
| No config entry | The dialect of the id, or of its models.dev catalog entry. |
| `models[].sdk` is set | That dialect, for that model only. |

## base_url

| Rule | Detail |
| --- | --- |
| Composition | Trailing slashes are trimmed. The dialect path is appended. |
| Version segment | Include it. Write `https://host/v1`. |
| No `base_url`, id `openai-completions`, `anthropic` or `google` | The default endpoint of that dialect. |
| No `base_url`, id `openai-responses` or `openai` | `https://api.openai.com/v1/responses`. |
| No `base_url`, a models.dev catalog provider | The catalog's `api` address. |
| No `base_url`, anything else | Refused. Set `providers.<id>.base_url`. |
| `NODECODE_ENDPOINT` | Overrides every rule above. It is the full endpoint URL. |

## Keys

Nodecode takes the first key found, in this order.

| Order | Source |
| --- | --- |
| 1 | `NODECODE_API_KEY` |
| 2 | `providers.<id>.api_key` in the config |
| 3 | `~/.nodecode/auth.json`, at `api_keys.<id>.key`. `/connect` writes it. |
| 4 | A cell's credential hook |
| 5 | `<ID>_API_KEY`. The id is uppercased, `-` becomes `_`. |
| 6 | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY` or `GOOGLE_API_KEY`, by dialect |
| 7 | `ANTHROPIC_AUTH_TOKEN`, for the provider id `anthropic` only |
| 8 | The literal string `public` |

> **Command keys.** A key from source 1 or 2 that starts with `!` is a shell command. Nodecode runs it with `sh -lc` every time it resolves the key.

| `!` command | Result |
| --- | --- |
| Success | The trimmed output is the key. |
| Runs past 10 seconds | Resolution fails. |
| Non-zero exit or empty output | Resolution fails. |

A provider with no key anywhere sends `public` as its key. An `auth.json` that is unreadable, invalid or over 1 MiB stops resolution.

## Default and auxiliary model

```jsonc
"general": {
  "default_model":   { "provider": "anthropic", "model": "claude-opus-5", "effort": "high" },
  "auxiliary_model": { "provider": "local", "model": "qwen3-27b" }
}
```

| Key | Used for | When unset |
| --- | --- | --- |
| `general.default_model` | New sessions. | The build default applies. The footer reads `run /setup to add a model`. |
| `general.auxiliary_model` | Side work, such as naming a session. | The session's own model, at its weakest effort rung. |

Both take `provider`, `model` and an optional `effort`. `provider` and `model` are required. The older string form of `default_model` is refused.

## Session pinning

A model pick pins the session. The pin survives restarts.

| Event | Result |
| --- | --- |
| `/models` pick | Pins this session. Writes `general.default_model`. |
| A session that already ran | Keeps its own model. |
| `(select-model nil)` in eval | Clears the pin. The session takes the default at its next turn. |
| Attached shells | Their footers follow the pick. |

## /models and /model-aux

| Command | Does |
| --- | --- |
| `/models` | Opens the catalog picker. Headless: prints the current target. |
| `/models MODEL` | Picks a model on the current provider. |
| `/models PROVIDER MODEL` | Picks a provider and a model. |
| `/models list [PROVIDER]` | Prints the catalog, or one provider's models. |
| `/models refresh` | Drops the cached listings, then opens the picker. |
| `/model-aux` | Same forms. Writes `general.auxiliary_model`. Pins no session. |

The picker lists each provider from `GET <base_url>/models`.

| Cache | File | Kept |
| --- | --- | --- |
| Provider listings | `~/.cache/nodecode/provider-models.json` | 600 seconds. A failed listing: 60 seconds. |
| models.dev catalog | `~/.cache/nodecode/models.dev/api.json` | Path override: `NODECODE_MODELS_CATALOG`. |

## /think and effort

The ladder, weakest first: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. A model offers the rungs it declares.

| Command | Does |
| --- | --- |
| `/think` | Opens the reasoning controls. Headless: prints the effort and the ladder. |
| `/think RUNG` | Sets the effort for this session and every new session. Applies from the next turn. |
| `/think default` | Uses the model's own reasoning. |
| `/effort` | Alias of `/think`. |

| Source | Detail |
| --- | --- |
| `general.default_model.effort` | The default effort. `/think` writes it when `default_model` is set. |
| `NODECODE_REASONING_EFFORT` | Outranks the config default. |
| A session's `/think` pick | Outranks both. |

| Dialect | Effort on the wire |
| --- | --- |
| `openai-completions`, `openai-responses` | Sent as written. |
| `google` | Clamped to `high`. |
| `anthropic` | An adaptive effort or a thinking budget, by what the model declares. |

## Retries

Set these per provider, under `providers.<id>`.

| Key | Default | Counts |
| --- | --- | --- |
| `request_max_retries` | 4 | Failures before any stream bytes. Counted per stream attempt. |
| `stream_max_retries` | 5 | Failures after the response started. |
| `stream_idle_timeout_ms` | 120000 | Milliseconds with no byte before the attempt fails. |
| `stream_rerun_ceiling_seconds` | 120 | A stream cut after this much output is continued. `0`: always re-run. |

The two retry budgets multiply. A stream that keeps sending bytes has no time limit.

| HTTP status | Retried |
| --- | --- |
| None: network error, timeout, cut stream, empty response | Yes |
| 408, 409, 425, 429 | Yes |
| 500 and above | Yes |
| 400, 401, 403, 404, 413, 422 and every other status | No |

| Delay rule | Value |
| --- | --- |
| `Retry-After` header | Used as sent, capped at 60 seconds. |
| No header | 0.2 seconds, doubling to 8 seconds, with 10% jitter. |
| Status 503 | Starts at 5 seconds, capped at 60 seconds. |
| Refused connection | Spends the request budget at once. |

A context overflow is handled apart: 413, or 400 with an overflow message. Nodecode evicts old history and retries, up to 8 times. See [Context](https://nodecode.ai/docs/context).

## Failover

```jsonc
"failover": {
  "chain": [
    { "provider": "local", "model": "qwen3-27b" }
  ]
}
```

| Rule | Detail |
| --- | --- |
| Start | The retries of the selected model are spent on a retryable failure. |
| Order | The round re-runs on the next entry of the chain. |
| On a chain entry | A failure that no retry cures moves to the next entry. |
| Scope | One turn. The next turn starts on the selected model. |
| Key | Each entry resolves its own key. `NODECODE_API_KEY` is not used for it. |
| Bad entry | An entry without `provider` or `model` is skipped with a warning. |
| No chain | No failover. |

## Environment

`NODECODE_PROVIDER`, `NODECODE_MODEL`, `NODECODE_API_KEY`, `NODECODE_ENDPOINT` and `NODECODE_REASONING_EFFORT` override the config for one process. See [Environment variables](https://nodecode.ai/docs/environment).
