Invalid API key in opencode: why the key is rejected and how to check

The Invalid API key error in opencode almost always comes down to four causes. We test the key outside opencode and find where the chain breaks.

5 min · · errors · IDE

You connected the gateway, started opencode, sent the first request and got "Invalid API key" or just "doesn't work". The key was copied from the dashboard and looks right. This article explains why invalid api key in opencode shows up with a working key and walks through a step-by-step check: from a request without opencode to the config and environment variables.

The addresses and key format below are for Zukko: the key starts with fetch_ and the opencode address is https://api.zukko.pro/v1. If you use another service the logic of the check is the same; substitute your own values.

Step 1. Test the key without opencode

Before editing the config, find out whether the key is at fault. Make a direct request:

bash
curl -i https://api.zukko.pro/v1/models \
  -H "Authorization: Bearer fetch_your_key"

Read the first line of the response. Below are the gateway codes you meet both in this check and later in normal work (403 and the model-name 404 show up on requests to a specific model):

ResponseMeaning
200 and a list of modelsthe key works, the problem is in opencode
401the key is wrong: typo, truncated, revoked, or simply a different key
402the key is fine, but the balance is too low
403the key is fine, but the model is not allowed for it (model_not_allowed_for_key)
404wrong address or model name
429the key's daily or monthly limit is used up

On 200 go to step 2. On 401, create a new key on the keys page and retry: the full key is shown only once, at creation, so losing its tail while copying is easier than it sounds.

Step 2. Make sure the key went to the right provider

In opencode a key is tied to a provider. You enter it with the /connect command in the interface and it is saved in ~/.local/share/opencode/auth.json (this is described in the opencode documentation). A typical mistake: a fetch_... key is pasted into a provider that looks at another server, for example "OpenCode Zen" or OpenAI, which have their own keys. For them your fetch_... really is invalid.

Check:

bash
opencode auth list

The command shows which providers have stored credentials. Then open the model list (/models) and see which provider the selected model belongs to. If the request goes to a provider whose baseURL is not overridden to point at the gateway, it will travel to the official server carrying your gateway key, and you will be refused.

If you set the key both in /connect and in opencode.json (the apiKey field), there are two sources. Which one wins in your version is worth checking in the opencode documentation, and it is safer to keep a single source.

Step 3. Check baseURL: it needs /v1

For opencode through the anthropic provider the address is given with /v1. This differs from Claude Code and the official Anthropic SDK, where /v1 is not needed. Without /v1, requests go to /messages and return 404. That 404 is easy to mistake for a key problem, especially when the interface shows a generic error text.

A working config, either global ~/.config/opencode/opencode.json or opencode.json in the project root:

bash
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "anthropic": {
      "options": {
        "baseURL": "https://api.zukko.pro/v1",
        "apiKey": "{env:ZUKKO_API_KEY}"
      },
      "models": {
        "claude-sonnet-5-5": {},
        "claude-opus-5-5": {}
      }
    }
  }
}

The {env:NAME} construct substitutes an environment variable and is described in the opencode config documentation. If the model you need is missing from /models, make sure its id in the models block matches GET /v1/models exactly. How opencode builds the model catalog for a provider is something to confirm in its documentation.

Step 4. The environment variable is not picked up

If you use {env:ZUKKO_API_KEY} and the variable is empty, opencode gets an empty key. This is the most common reason behind "but the key is right". Check:

bash
echo "${ZUKKO_API_KEY:0:8}"

It should print fetch_... (the first eight characters). Empty output means the variable is not in the current environment. Typical causes:

  • the variable is set in ~/.zshrc but the terminal was opened before the edit: run source ~/.zshrc or open a new one;
  • you use a different shell (bash instead of zsh) and edited the wrong file;
  • opencode is launched from an app or an editor that does not read shell files: set the variable where the process starts;
  • quotes, a space or a line break came along when pasting from a messenger.

To test, temporarily put the key straight into apiKey in the config, start opencode and see whether the error is gone. If so, the environment is the culprit. Then restore the substitution and keep the key out of files that end up in git.

Step 5. Alternative: a provider over the OpenAI-compatible API

If you need more than Claude, say gpt-6.1-sol or deepseek-v4.1-flash, you can define a separate provider. According to the opencode documentation, OpenAI-compatible servers use the @ai-sdk/openai-compatible package:

bash
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "zukko": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Zukko",
      "options": {
        "baseURL": "https://api.zukko.pro/v1",
        "apiKey": "{env:ZUKKO_API_KEY}"
      },
      "models": {
        "gpt-6.1-sol": { "name": "GPT-6.1 Sol" },
        "deepseek-v4.1-flash": { "name": "DeepSeek V4.1 Flash" }
      }
    }
  }
}

The provider id (zukko) must match what you entered in /connect (the "Other" entry) if you use that route. This is stated in the Troubleshooting section of the opencode providers page.

When the error is not about the key

Once curl returns 200 and the config is checked, look at the neighboring causes:

  • 402: the balance ran out. Top it up in the dashboard; the status is visible under "Usage".
  • 403 model_not_allowed_for_key: the key has a list of allowed models and you picked one outside it.
  • 429 key_daily_limit_reached or key_monthly_limit_reached: the daily or monthly limit of this particular key was hit.
  • 503 model_disabled: the model is temporarily switched off, pick another from GET /v1/models.

For the general picture of connection errors see Error 401 invalid API key and Connecting opencode, and for the detailed setup see the opencode documentation.

FAQ

The key works in curl but not in opencode. Where do I start?

Start with opencode auth list and the provider of the selected model. Then make sure baseURL contains /v1 and that the environment variable your config refers to is not empty.

Why do I see a key error instead of a 404 when /v1 is missing?

The client interface may show a generic error text regardless of the response code. So test the key separately with curl -i first: the status code shows where the problem is.

Can I store the key right in opencode.json?

Technically yes, but the file often lives in the project repository. An environment variable or a separate file outside the repository is safer. If a key did end up in git, revoke it in the dashboard and create a new one.

Do I need a separate key for opencode?

It is recommended. A dedicated key with a daily dollar limit and a list of allowed models limits the damage if an agent loops or the key leaks. You can create one in the dashboard in a minute.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

Invalid API key in opencode: causes and checks · Zukko