Plandex with a Custom OpenAI-Compatible Provider

In short: Custom providers are declared in a JSON file, not an environment variable: OPENAI_API_BASE only re-points the built-in OpenAI provider. In the file, baseUrl carries the /v1 while apiKeyEnvVar holds the name of a variable rather than the key itself, and how much of the file is honoured depends on whether you run self-hosted, cloud with your own keys, or cloud with integrated models.

Plandex is a terminal coding engine built for tasks that span many files and many steps. It splits a large task into subtasks, implements each one, and accumulates the changes in a sandbox you review before they touch your files. Pointing it at your own OpenAI-shaped endpoint is straightforward once you know which of the two mechanisms you are using, because Plandex has two and they do not overlap.

Custom providers live in a JSON file, not in an environment variable. OPENAI_API_BASE only re-points the built-in OpenAI provider; it does nothing for a provider you declared yourself. In the file, baseUrl includes the /v1 — the opposite of Spring AI, where the version prefix belongs in a separate property. And apiKeyEnvVar is the name of a variable, not the key itself, which is the single most common way to end up with a configuration that looks complete and fails authentication.

You need a key before the code below runs. Create an account, generate a key, and copy the base URL (https://aicomp.ai/v1). Create one free →
Check current rates → Free to sign up · $1 minimum top-up · No prepayment

This page covers both mechanisms, where the file has to live, why a correct file can still be ignored on hosted plans, and what a month of long-running agent work costs once the context is counted.

Two mechanisms that do not overlap

Plandex ships with a set of built-in providers — OpenAI, OpenRouter, Anthropic, Google AI Studio and Vertex, Azure OpenAI, AWS Bedrock, DeepSeek, Perplexity and Ollama — plus a custom provider you define yourself.

There are two ways to aim the built-in OpenAI provider somewhere else:

export OPENAI_API_KEY=...
export OPENAI_API_BASE=https://gw.example.com/v1   # optional; re-points the built-in OpenAI provider

That is a redirect of an existing provider. It does not create one, and it does not apply to anything you declared in the models file.

The second mechanism is the custom models file:

plandex models custom      # or \models custom inside the REPL

The first run writes a template file and opens it in your editor. It has a $schema line pointing at https://plandex.ai/schemas/models-input.schema.json, so editors that understand JSON Schema give you completion and inline validation while you type. The file has three sections: providers, models and modelPacks.

A provider entry looks like this:

{
  "providers": [
    {
      "name": "my-gateway",
      "baseUrl": "https://gw.example.com/v1",
      "apiKeyEnvVar": "MY_GATEWAY_API_KEY"
    }
  ]
}

name is your own identifier. baseUrl is the endpoint and it must be OpenAI-compatible — including the version segment, so it ends in /v1. apiKeyEnvVar names the environment variable that holds the key; you export that variable in your shell and Plandex reads it. For a local server that does not authenticate, skipAuth replaces the key requirement, and extraAuthVars adds further variables when a gateway wants more than one header.

apiKeyEnvVar takes a name, not a key

This is the trap. The field is called *EnvVar, and the natural thing to paste into it is the key you were given:

{ "name": "my-gateway", "baseUrl": "https://gw.example.com/v1",
  "apiKeyEnvVar": "sk-live-abc123" }

Now Plandex looks for an environment variable literally named sk-live-abc123, finds nothing, and sends an unauthenticated request. There is no complaint about a malformed credential — you get the same 401 you would get from a wrong key, and the file looks right when you read it back.

The correct pairing is a name in the file and a value in the shell:

export MY_GATEWAY_API_KEY=sk-live-abc123

How much of the file is honoured depends on the plan

A correct file can be silently inert, and the reason is not in the file. Support for custom configuration is tiered by how you run Plandex:

So the same file that works against a self-hosted server does nothing on a cloud plan with integrated models. If a declared provider never appears, check which of the three you are on before re-reading the JSON for the fourth time.

Models, packs, and the command that selects them

Declaring a provider is not the same as using it. A models entry maps a model onto a provider and declares what it can do, and a modelPacks entry groups models into the roles Plandex fills — one model for planning, another for implementation, and so on.

Selection happens through plandex set-model, which updates the current plan, or plandex set-model default, which updates the organisation-level default. Run with no argument it opens a picker; run with --file it loads a JSON configuration. If you ever set a model explicitly, a later release that changes the default pack will not move you — you have to run set-model again to pick up the new default.

That matters for cost as much as for quality: the pack decides which model sees the large planning context and which one writes the small per-file diffs, and those are very different token shapes.

Across 219 catalogue models that publish both rates, the median output rate is 4.0× the input rate, and 68% of them (150 models) charge at least 4× more for output than input. The other 624 raw catalogue entries are dropped by cleaning — test entries, dated snapshots and non-chat variants — not for a missing rate. This is why an agent workload is decided on the output column, not the input one everyone quotes.

Why the input side is the whole bill

Plandex keeps the relevant files in context and keeps them updated as the work proceeds, so every subtask carries the state of the task so far. A long plan re-reads that context repeatedly. Output, by contrast, is one diff at a time.

A day of work on this page's workload is 400k input and 45k output tokens — 8M input and 0.9M output across 20 working days. With a ratio that wide, the input rate decides the month and the output rate is rounding; why output pricing dominates the bill explains the cases where that inverts.

Per 1M input / output tokens and a 20-day month
ModelGateway rate
in / out per 1M tokens
One day20 days
claude-opus-5$2.5 / $12.5$1.56$31.25
gpt-6-sol$1.00 / $5.00$0.62$12.50
deepseek-v4-pro$0.66 / $1.98$0.35$7.06
glm-5.3$0.70 / $2.2$0.38$7.58
MiniMax-M3$0.15 / $0.60$0.09$1.74

One day = 400k input + 45k output on this page's workload. 20 days = 8M input and 0.9M output. Rates checked 2026-10-10 — gateway rates move with upstream promotions, so verify the current number in your dashboard before committing to a budget.

Verifying the route

  1. Confirm which mechanism you are using. OPENAI_API_BASE only moves the built-in OpenAI provider. A declared custom provider comes from the models file, and only on plans that honour it.
  2. Print the variable, not the file. echo $MY_GATEWAY_API_KEY. If that is empty, apiKeyEnvVar is pointing at a name nobody set.
  3. Check the path in the gateway log. baseUrl must end in /v1; a doubled /v1/v1 means the version segment is in both the file and the gateway's own routing.
  4. Run plandex set-model and pick the pack that uses your provider. Declaring a provider is not selecting it.
  5. Start a deliberately small plan — one file, one change. It exercises provider, key, model id and pack together in a minute, and it tells you which of the four failed before you have paid for a large context.

FAQ

FAQ

I set OPENAI_API_BASE and my custom provider still is not used

They are independent. The environment variable redirects the built-in OpenAI provider only. A provider declared in the models file has to be attached to a model, and that model has to be in the pack you selected with set-model.

Does baseUrl end with /v1?

Yes. The field is the full endpoint root including the version segment, so https://gw.example.com/v1 is right and https://gw.example.com is not. This is the reverse of clients that append the version path for you.

Why does my gateway log show requests with no key?

apiKeyEnvVar is set to the key itself rather than to the name of a variable that holds it. Put a name in the file and export the value in your shell.

My provider is correct but never appears in the picker

Check the tier. On a cloud plan with your own keys, custom models must use built-in providers; on a cloud plan with integrated models, only custom model packs over built-in models are honoured. Self-hosted is the only mode where a custom provider is always available.

Can I point Plandex at a local server that has no key?

Yes — use skipAuth on the provider instead of apiKeyEnvVar, and leave the key variable unset.

Is Plandex cheaper per task than a chat-based agent?

Not per task. The sandbox and the branching mean the same work is attempted more than once, and every attempt re-reads the accumulated context. It is cheaper per accepted change, because you review before applying. For the opposite shape — one shot, no review sandbox — see Aider, and for a CLI that reaches the same endpoint through a different variable name, see Crush.

Related

Get API access